feat: load external chinese spam rule lists
This commit is contained in:
@@ -21,6 +21,7 @@ COPY config.py .
|
|||||||
COPY db/ db/
|
COPY db/ db/
|
||||||
COPY handlers/ handlers/
|
COPY handlers/ handlers/
|
||||||
COPY services/ services/
|
COPY services/ services/
|
||||||
|
COPY rules/ rules/
|
||||||
COPY web/ web/
|
COPY web/ web/
|
||||||
COPY knowledge/ knowledge/
|
COPY knowledge/ knowledge/
|
||||||
COPY main.py .
|
COPY main.py .
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
# 中文垃圾广告/黑灰产关键词词库(可追加网上词库,一行一个词)
|
||||||
|
# 格式:词条 或 词条|分数
|
||||||
|
|
||||||
|
# 赌博/博彩
|
||||||
|
菠菜|100
|
||||||
|
赌博|100
|
||||||
|
博彩|100
|
||||||
|
赌场|100
|
||||||
|
棋牌|100
|
||||||
|
彩票|100
|
||||||
|
开奖|90
|
||||||
|
中奖|80
|
||||||
|
下注|100
|
||||||
|
百家乐|100
|
||||||
|
时时彩|100
|
||||||
|
六合彩|100
|
||||||
|
赌球|100
|
||||||
|
盘口|90
|
||||||
|
|
||||||
|
# 色情/擦边
|
||||||
|
约炮|100
|
||||||
|
外围|100
|
||||||
|
楼凤|100
|
||||||
|
裸聊|100
|
||||||
|
成人视频|100
|
||||||
|
一夜情|100
|
||||||
|
上门服务|100
|
||||||
|
|
||||||
|
# 诈骗/灰产
|
||||||
|
刷单|100
|
||||||
|
返佣|70
|
||||||
|
传销|100
|
||||||
|
资金盘|100
|
||||||
|
杀猪盘|100
|
||||||
|
跑分|100
|
||||||
|
洗钱|100
|
||||||
|
代收款|100
|
||||||
|
接码|90
|
||||||
|
猫池|90
|
||||||
|
办证|100
|
||||||
|
假证|100
|
||||||
|
代开发票|100
|
||||||
|
|
||||||
|
# 赚钱/兼职广告
|
||||||
|
日赚|75
|
||||||
|
日入|75
|
||||||
|
每天赚|75
|
||||||
|
一天赚|75
|
||||||
|
轻松赚|75
|
||||||
|
稳赚|75
|
||||||
|
躺赚|70
|
||||||
|
月入|70
|
||||||
|
月赚|70
|
||||||
|
副业|35
|
||||||
|
兼职日结|60
|
||||||
|
手机操作|45
|
||||||
|
无需经验|45
|
||||||
|
宝妈|35
|
||||||
|
学生党|35
|
||||||
|
小白可带|65
|
||||||
|
手把手带|65
|
||||||
|
带你赚|70
|
||||||
|
带你做|65
|
||||||
|
|
||||||
|
# 引流/联系方式
|
||||||
|
加微信|50
|
||||||
|
微信号|50
|
||||||
|
vx|45
|
||||||
|
v信|45
|
||||||
|
薇信|45
|
||||||
|
加我|40
|
||||||
|
私聊|45
|
||||||
|
私信|45
|
||||||
|
联系我|45
|
||||||
|
看主页|45
|
||||||
|
看签名|45
|
||||||
|
主页有|45
|
||||||
|
点头像|45
|
||||||
|
进群|45
|
||||||
|
拉群|45
|
||||||
|
频道|35
|
||||||
|
|
||||||
|
# 币圈/投资/贷款
|
||||||
|
USDT|50
|
||||||
|
泰达币|50
|
||||||
|
虚拟币|50
|
||||||
|
币圈|45
|
||||||
|
搬砖|50
|
||||||
|
套利|50
|
||||||
|
贷款|45
|
||||||
|
套现|55
|
||||||
|
黑户|60
|
||||||
|
秒批|55
|
||||||
|
包过|55
|
||||||
|
下款|55
|
||||||
|
投资带单|70
|
||||||
|
股票老师|60
|
||||||
|
期货老师|60
|
||||||
|
外汇老师|60
|
||||||
|
量化跟单|65
|
||||||
|
|
||||||
|
# 营销诱导
|
||||||
|
免费领|45
|
||||||
|
免费送|45
|
||||||
|
限时领|45
|
||||||
|
点击领取|45
|
||||||
|
扫码领取|45
|
||||||
|
红包|30
|
||||||
|
福利|30
|
||||||
|
返现|40
|
||||||
|
代理|35
|
||||||
|
加盟|35
|
||||||
|
招商|35
|
||||||
|
招代理|45
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# 中文垃圾广告正则库(可追加网上规则,一行一个正则)
|
||||||
|
# 格式:正则 或 正则|分数
|
||||||
|
(?:日赚|日入|每天赚|一天赚|轻松赚|稳赚|躺赚).{0,10}(?:\d{2,}|几百|上千|过千|一千|几千|上万|万元?|k|K|w|W)|90
|
||||||
|
(?:月入|月赚|月收益|月收入).{0,10}(?:\d{3,}|过万|上万|几万|万元?|w|W)|85
|
||||||
|
(?:赚钱|收益|收入|副业|项目|路子).{0,20}(?:加我|私聊|看主页|看签名|进群|联系我|微信|vx|QQ)|85
|
||||||
|
(?:\d{3,}|几百|上千|过千|几千|上万|万元?|k|K|w|W).{0,20}(?:加我|私聊|看主页|看签名|进群|联系我|微信|vx|QQ)|80
|
||||||
|
(?:兼职|副业|日结|周结|在家做|手机操作|无需经验).{0,18}(?:赚钱|收入|结算|工资|项目|单)|75
|
||||||
|
(?:USDT|U\s?S\s?D\s?T|泰达币|虚拟币|币圈|搬砖|套利).{0,18}(?:赚钱|收益|项目|加我|看主页|进群)|85
|
||||||
|
(?:贷款|借款|套现|信用卡|花呗|白条|提额|下款|黑户|征信).{0,18}(?:秒批|当天|无视|包过|渠道)|85
|
||||||
|
(?:t\.me|telegram\.me|tg://|飞机群).{0,40}(?:join|group|channel|\+|进群|频道)|95
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
@@ -26,6 +27,63 @@ class Rule:
|
|||||||
score: int
|
score: int
|
||||||
|
|
||||||
|
|
||||||
|
ROOT_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
RULES_DIR = ROOT_DIR / "rules"
|
||||||
|
|
||||||
|
|
||||||
|
def load_keyword_rules(path: Path, default_score: int = 45) -> list[Rule]:
|
||||||
|
"""加载外部关键词词库:词条 或 词条|分数。"""
|
||||||
|
rules: list[Rule] = []
|
||||||
|
if not path.exists():
|
||||||
|
return rules
|
||||||
|
for line in path.read_text(encoding="utf-8").splitlines():
|
||||||
|
raw = line.strip()
|
||||||
|
if not raw or raw.startswith("#"):
|
||||||
|
continue
|
||||||
|
if "|" in raw:
|
||||||
|
word, score_text = raw.rsplit("|", 1)
|
||||||
|
try:
|
||||||
|
score = int(score_text.strip())
|
||||||
|
except ValueError:
|
||||||
|
word, score = raw, default_score
|
||||||
|
else:
|
||||||
|
word, score = raw, default_score
|
||||||
|
word = word.strip()
|
||||||
|
if word:
|
||||||
|
rules.append(Rule(f"外部关键词:{word}", re.escape(word), score))
|
||||||
|
return rules
|
||||||
|
|
||||||
|
|
||||||
|
def load_regex_rules(path: Path, default_score: int = 70) -> list[Rule]:
|
||||||
|
"""加载外部正则库:正则 或 正则|分数。"""
|
||||||
|
rules: list[Rule] = []
|
||||||
|
if not path.exists():
|
||||||
|
return rules
|
||||||
|
for i, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
||||||
|
raw = line.strip()
|
||||||
|
if not raw or raw.startswith("#"):
|
||||||
|
continue
|
||||||
|
if "|" in raw:
|
||||||
|
pattern, score_text = raw.rsplit("|", 1)
|
||||||
|
try:
|
||||||
|
score = int(score_text.strip())
|
||||||
|
except ValueError:
|
||||||
|
pattern, score = raw, default_score
|
||||||
|
else:
|
||||||
|
pattern, score = raw, default_score
|
||||||
|
try:
|
||||||
|
re.compile(pattern)
|
||||||
|
rules.append(Rule(f"外部正则:{i}", pattern, score))
|
||||||
|
except re.error as e:
|
||||||
|
logger.warning(f"⚠️ 跳过无效外部正则 {path}:{i}: {e}")
|
||||||
|
return rules
|
||||||
|
|
||||||
|
|
||||||
|
EXTERNAL_KEYWORD_RULES = load_keyword_rules(RULES_DIR / "spam_keywords.txt")
|
||||||
|
EXTERNAL_REGEX_RULES = load_regex_rules(RULES_DIR / "spam_regex.txt")
|
||||||
|
logger.info(f"🧩 已加载外部垃圾规则:关键词 {len(EXTERNAL_KEYWORD_RULES)} 条,正则 {len(EXTERNAL_REGEX_RULES)} 条")
|
||||||
|
|
||||||
|
|
||||||
# 高危黑产:单项命中即可高度可疑或直接垃圾
|
# 高危黑产:单项命中即可高度可疑或直接垃圾
|
||||||
HARD_RULES = [
|
HARD_RULES = [
|
||||||
Rule("博彩赌博", r"菠菜|赌博|博彩|赌场|棋牌|彩票|开奖|中奖|赔率|下注|百家乐|时时彩|六合彩", 100),
|
Rule("博彩赌博", r"菠菜|赌博|博彩|赌场|棋牌|彩票|开奖|中奖|赔率|下注|百家乐|时时彩|六合彩", 100),
|
||||||
@@ -114,28 +172,31 @@ def local_classify(text: str) -> tuple[bool | None, str, int]:
|
|||||||
hard_hits = []
|
hard_hits = []
|
||||||
score_hits = []
|
score_hits = []
|
||||||
combo_hits = []
|
combo_hits = []
|
||||||
|
external_hits = []
|
||||||
normal_hits = []
|
normal_hits = []
|
||||||
|
|
||||||
for target in targets:
|
for target in targets:
|
||||||
hard_hits.extend(hit_rules(target, HARD_RULES))
|
hard_hits.extend(hit_rules(target, HARD_RULES))
|
||||||
score_hits.extend(hit_rules(target, SCORE_RULES))
|
score_hits.extend(hit_rules(target, SCORE_RULES))
|
||||||
combo_hits.extend(hit_rules(target, COMBO_RULES))
|
combo_hits.extend(hit_rules(target, COMBO_RULES))
|
||||||
|
external_hits.extend(hit_rules(target, EXTERNAL_KEYWORD_RULES))
|
||||||
|
external_hits.extend(hit_rules(target, EXTERNAL_REGEX_RULES))
|
||||||
normal_hits.extend(hit_rules(target, NORMAL_RULES))
|
normal_hits.extend(hit_rules(target, NORMAL_RULES))
|
||||||
|
|
||||||
# 去重
|
# 去重
|
||||||
dedup = lambda xs: list({x.name: x for x in xs}.values())
|
dedup = lambda xs: list({x.name: x for x in xs}.values())
|
||||||
hard_hits, score_hits, combo_hits, normal_hits = map(dedup, [hard_hits, score_hits, combo_hits, normal_hits])
|
hard_hits, score_hits, combo_hits, external_hits, normal_hits = map(dedup, [hard_hits, score_hits, combo_hits, external_hits, normal_hits])
|
||||||
|
|
||||||
if hard_hits:
|
if hard_hits:
|
||||||
names = ",".join(r.name for r in hard_hits[:3])
|
names = ",".join(r.name for r in hard_hits[:3])
|
||||||
return True, f"硬规则命中: {names}", 100
|
return True, f"硬规则命中: {names}", 100
|
||||||
|
|
||||||
spam_score = sum(r.score for r in score_hits) + sum(r.score for r in combo_hits)
|
spam_score = sum(r.score for r in score_hits) + sum(r.score for r in combo_hits) + sum(r.score for r in external_hits)
|
||||||
normal_score = sum(r.score for r in normal_hits)
|
normal_score = sum(r.score for r in normal_hits)
|
||||||
|
|
||||||
# 广告组合是核心:即使很短也要先判垃圾
|
# 广告组合是核心:即使很短也要先判垃圾
|
||||||
if spam_score >= 75:
|
if spam_score >= 75:
|
||||||
names = ",".join(r.name for r in (score_hits + combo_hits)[:4])
|
names = ",".join(r.name for r in (score_hits + combo_hits + external_hits)[:5])
|
||||||
return True, f"广告规则评分 {spam_score}: {names}", spam_score
|
return True, f"广告规则评分 {spam_score}: {names}", spam_score
|
||||||
|
|
||||||
# 纯短消息只有在没有广告分时才放行
|
# 纯短消息只有在没有广告分时才放行
|
||||||
@@ -152,7 +213,7 @@ def local_classify(text: str) -> tuple[bool | None, str, int]:
|
|||||||
return False, "无广告信号", 0
|
return False, "无广告信号", 0
|
||||||
|
|
||||||
# 模糊样本交给 AI
|
# 模糊样本交给 AI
|
||||||
names = ",".join(r.name for r in (score_hits + combo_hits)[:4])
|
names = ",".join(r.name for r in (score_hits + combo_hits + external_hits)[:5])
|
||||||
return None, f"疑似广告评分 {spam_score}: {names}", spam_score
|
return None, f"疑似广告评分 {spam_score}: {names}", spam_score
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user