Python文字审核模型

这是文本自动审核服务,包含敏感词检测、规则识别、风险打分和人工复审逻辑。

```python

-*- coding: utf-8 -*-

"""

文本自动审核引擎

  • 敏感词 DFA 多模匹配(分级加权)

  • 正则规则(联系方式/广告/灌水)

  • 抗绕过:全角、零宽字符、干扰符

  • 输出:pass / review / reject + 命中明细 + 打码文本

"""

from future import annotations

import re

import unicodedata

from dataclasses import dataclass, field, asdict

from typing import Iterable

---------------- 常量 ----------------

PASS = "pass" # 通过

REVIEW = "review" # 转人工复审

REJECT = "reject" # 拒绝

MAX_SCORE = 100

DEFAULT_REJECT_THRESHOLD = 80

DEFAULT_REVIEW_THRESHOLD = 30

敏感词等级 -> 风险分

SCORE_BY_LEVEL = {1: 15, 2: 45, 3: 100}

---------------- 词库 ----------------

⚠️ 生产环境请替换成你自己的词库,并从 DB / Redis 加载、支持热更新

DEFAULT_WORDS: dictstr, int = {

level 3 ------ 严重违规,命中即拒绝

"赌博": 3, "博彩": 3, "私彩": 3, "六合彩": 3,

"毒品": 3, "冰毒": 3, "大麻": 3, "迷药": 3,

"枪支": 3, "弹药": 3, "假币": 3, "假钞": 3,

"代开发票": 3, "办证": 3, "假证": 3,

"色情": 3, "成人网站": 3, "一夜情": 3, "约炮": 3,

"黑客": 3, "代考": 3, "包过": 3,

level 2 ------ 广告 / 引流

"刷单": 2, "兼职刷单": 2, "刷信誉": 2,

"加微信": 2, "私聊我": 2, "私信我": 2,

"贷款": 2, "套现": 2, "花呗套现": 2, "无抵押": 2,

"高薪招聘": 2, "日结": 2, "返利": 2,

level 1 ------ 轻度

"傻逼": 1, "去死": 1, "滚蛋": 1,

}

---------------- 正则规则 ----------------

@dataclass(frozen=True)

class RegexRule:

name: str

pattern: re.Pattern

score: int

max_hits: int = 3 # 同一规则最多计分次数,避免刷分

DEFAULT_REGEX_RULES: listRegexRule = [

RegexRule("手机号", re.compile(r"(?<!\d)13-9\d{9}(?!\d)"), 40),

RegexRule("QQ号", re.compile(r"(?:qq|扣扣|企鹅)\s*::?\s*\d{5,12}", re.I), 35),

RegexRule("微信号", re.compile(r"(?:微信|weixin|vx|v信|薇信|威信)\s*::?\s*a-zA-Za-zA-Z0-9_-{4,19}"), 35),

RegexRule("网址", re.compile(r"(?:https?://|www\.)\\w\\-.+\.a-zA-Z{2,}(?:/\S*)?", re.I), 30),

RegexRule("邮箱", re.compile(r"\\w.+-+@\\w-+\.a-zA-Z{2,}"), 25),

RegexRule("银行卡号", re.compile(r"(?<!\d)\d{16,19}(?!\d)"), 50),

RegexRule("灌水重复", re.compile(r"(.)\1{7,}"), 20, 1),

]

---------------- 文本归一化 ----------------

_ZERO_WIDTH = re.compile(r"\\u200b-\\u200f\\u202a-\\u202e\\u2060\\ufeff")

常见用于绕过检测的干扰符号

_INTERFERENCE = re.compile(

r"\\s\\\*\\.\\-_\~·、,,。!!??@#¥$%\^\&+\|=//\\\\\\\[\\【】()()<>《》\"'""''::;;]+"

)

def normalize(text: str) -> str:

"""全角转半角 + 去掉零宽字符。注意:可能改变字符串长度。"""

text = unicodedata.normalize("NFKC", text)

return _ZERO_WIDTH.sub("", text)

def deobfuscate(text: str) -> str:

"""去掉空格、星号等干扰符,用于检测 '赌 博' / '赌*博' 这类变体。"""

return _INTERFERENCE.sub("", text)

---------------- 结果模型 ----------------

@dataclass

class Hit:

kind: str # 命中类型:sensitive_word / rule:xxx / remote:xxx

detail: str # 命中的具体内容

score: int # 风险分

start: int = -1 # 在归一化文本中的位置(仅本地规则有效)

end: int = -1

@dataclass

class AuditResult:

action: str

score: int

hits: listHit = field(default_factory=list)

normalized_text: str = ""

filtered_text: str = "" # 命中部分打码后的文本

def to_dict(self) -> dict:

return {

"action": self.action,

"score": self.score,

"hits": asdict(h) for h in self.hits,

"filtered_text": self.filtered_text,

}

def mask_spans(text: str, spans: Iterabletuple\[int, int]) -> str:

"""把命中的位置替换成 *"""

chars = list(text)

n = len(chars)

for s, e in spans:

for k in range(max(0, s), min(n, e)):

charsk = "*"

return "".join(chars)

---------------- DFA 敏感词匹配 ----------------

class DFAFilter:

"""基于 Trie 的多模匹配,最长匹配优先。时间复杂度 O(n * 最长词长)。"""

slots = ("root",)

def init(self) -> None:

self.root: dict = {}

def add(self, word: str) -> None:

node = self.root

for ch in word:

node = node.setdefault(ch, {})

node"\\x00" = True # 结束标记

def build(self, words: Iterablestr) -> "DFAFilter":

for w in words:

w = normalize(w).strip()

if w:

self.add(w)

return self

def match(self, text: str) -> listtuple\[int, int, str]:

"""返回 (start, end, word)"""

hits: listtuple\[int, int, str] = \[\]

n = len(text)

i = 0

while i < n:

node = self.root

j = i

last_end = -1

while j < n:

nxt = node.get(textj)

if nxt is None:

break

node = nxt

j += 1

if "\x00" in node:

last_end = j # 记录最近一次完整匹配

if last_end > i:

hits.append((i, last_end, texti:last_end))

i = last_end # 跳过已匹配部分

else:

i += 1

return hits

---------------- 审核引擎 ----------------

class TextModerator:

def init(

self,

words: dictstr, int | None = None,

regex_rules: listRegexRule | None = None,

reject_threshold: int = DEFAULT_REJECT_THRESHOLD,

review_threshold: int = DEFAULT_REVIEW_THRESHOLD,

) -> None:

self.words = self._normalize_words(DEFAULT_WORDS if words is None else words)

self.dfa = DFAFilter().build(self.words)

self.regex_rules = DEFAULT_REGEX_RULES if regex_rules is None else regex_rules

self.reject_threshold = reject_threshold

self.review_threshold = review_threshold

@staticmethod

def _normalize_words(words: dictstr, int) -> dictstr, int:

out: dictstr, int = {}

for k, v in words.items():

k = normalize(k).strip()

if k:

outk = v

return out

def reload_words(self, words: dictstr, int) -> None:

"""热更新词库(可对接 Redis 订阅 / 定时拉取)"""

self.words = self._normalize_words(words)

self.dfa = DFAFilter().build(self.words)

---------- 决策 ----------

def decide(self, score: int, hits: listHit) -> str:

if any(h.score >= MAX_SCORE for h in hits):

return REJECT

if score >= self.reject_threshold:

return REJECT

if score >= self.review_threshold:

return REVIEW

return PASS

---------- 主流程 ----------

def audit(self, text: str) -> AuditResult:

if not text or not text.strip():

return AuditResult(action=PASS, score=0)

norm = normalize(text)

hits: listHit = \[\]

spans: listtuple\[int, int] = \[\]

score = 0

1) 敏感词

for start, end, word in self.dfa.match(norm):

level = self.words.get(word, 2)

s = SCORE_BY_LEVEL.get(level, 45)

hits.append(Hit("sensitive_word", word, s, start, end))

spans.append((start, end))

score += s

2) 抗绕过:去掉干扰符后再匹配一次(位置无法映射回原文,故不打码)

stripped = deobfuscate(norm)

if stripped != norm:

for _, _, word in self.dfa.match(stripped):

hits.append(Hit("sensitive_word_obfuscated", word, 60))

score += 60

3) 正则规则

for rule in self.regex_rules:

count = 0

for m in rule.pattern.finditer(norm):

if count >= rule.max_hits:

break

count += 1

hits.append(Hit(f"rule:{rule.name}", m.group(), rule.score, m.start(), m.end()))

spans.append((m.start(), m.end()))

score += rule.score

score = min(score, MAX_SCORE)

return AuditResult(

action=self.decide(score, hits),

score=score,

hits=hits,

normalized_text=norm,

filtered_text=mask_spans(norm, spans),

)

单例,供接口层复用

moderator = TextModerator()

```

审核流程与操作说明

这块代码把自动审核拆成了三道关卡,下面具体说说每一步在干什么。

· 敏感词分级检测:命中敏感词时按等级加权扣分,3 级词汇直接触发拒绝,2 级和 1 级累积风险分。

· 格式规则识别:内置手机号、QQ号、微信号、网址等正则,同一规则最多计分 3 次,避免刷分。

· 抗绕过处理:先做全角转半角、去掉零宽字符,再把文字里的空格、星号等干扰符去掉后二次匹配,防止"赌 博""赌*博"这类变体。

· 自动决策:根据最终得分落在 0,30)、\[30,80)、\[80,100 区间,分别给出通过、人工复审、拒绝三种结果,并输出命中明细和打码后的文本。


优化建议: 建议您将 DEFAULT_WORDS 里的示例词替换为自己的词库,并接入数据库或 Redis 支持热更新;也可按需调整拒绝和复审的分数阈值。

文章仅供参考用。

相关推荐
wuyk5551 小时前
Python网络爬虫入门到实战 第02章:HTTP/HTTPS协议超通俗精讲(GET/POST、请求头、响应码、爬虫核心基础)
爬虫·python·http
Cc.Y1 小时前
Java零基础入门:方法(函数)深度掌握
java·开发语言
老王爱玩车1 小时前
工具函数——清空输入缓冲区
c语言·开发语言·学习
昔我往昔2 小时前
init文件介绍
python·flask
奋发向前wcx2 小时前
CSP-J复赛模拟赛1 王晨旭补题 2026.10.2
java·开发语言
周杰伦fans2 小时前
轻量模型高并发推理优化技巧
开发语言·人工智能·c#
Frank_refuel2 小时前
【C++项目】手撕JRpc框架(单机怎么优化到分布式思想)
开发语言·c++
虫无涯2 小时前
大模型联动 CodeQL + Coverity 完整落地方案
人工智能·python·大模型·llm·codeql·coverity
段一凡-华北理工大学2 小时前
大模型与智能体在工业的应用~系列文章12:大模型 × 数字孪生 × 智能体的融合图景
大数据·人工智能·python·深度学习·大语言模型·python开发