Python文本清洗与敏感实体过滤实战:基于jieba和正则
2026/8/31 7:45:06 网站建设 项目流程

最近在处理网络热点文本采集与审核功能时,经常遇到一类典型需求:标题很短、人名很多、情感倾向强烈,而且原始文本是否真实无法直接确认。如果把这类文本原样入库、展示,未经核实的人名和关系描述可能带来隐私与合规风险。本文就以一条典型的网络热点标题作为输入样本,完整演示如何用 Python 构建一个轻量级、可落地的文本清洗与敏感实体过滤工具,并输出结构化的审核报告。实现方案不依赖重型的线上 NLP 服务,适合中小型项目、内容审核后台和舆情分析系统的开发同学参考。

需要提前说明的是,文中出现的标题字符串仅作为代码示例中的待处理文本,用于演示脱敏和过滤效果,不构成对任何事件或人物的真实描述。开发者在生产环境中处理类似文本时,也应始终保持“先过滤、再核验、后展示”的基本原则。

1. 背景与核心问题

1.1 热点文本的典型特征

网络热点标题通常非常短,但信息密度很高。它们往往包含真实人名、关系词、情绪化表达,甚至还有书名号、引号、特殊符号。从数据工程角度看,这种文本有四个显著特征:长度短,数据量少但噪音比例高;人名和实体词多;关系描述主观且难以自动判定真伪;情绪倾向强烈,容易被直接传播。

如果让一个内容审核系统直接存储并展示这类文本,隐患是明显的。第一,未经核实的人名与关系描述可能构成对个人隐私的侵入;第二,标题中的“女友”“恋情”等关系词包含强烈的事实断言,技术系统无法自动验证;第三,后续如果文本被搜索、推荐或二次加工,错误信息会被放大。因此,数据处理层需要一套机制,在进入业务库之前,先完成清洗、实体识别、脱敏和人工审核标记。

从工程实现角度,这类任务并不一定需要复杂的深度学习模型。基于规则词典、词性标注和正则表达式,就能解决大多数问题。本文的侧重点是用最小成本搭建一个可运行的过滤框架,并给出易于维护的代码结构。

1.2 技术方案选型:为什么不用大模型

处理中文人名识别,最直接的方案是调用各类云厂商的内容审核 API,或者使用 BERT 等预训练模型。但这类方案在部分业务场景下存在困难:需要联网调用,数据隐私受限;需要 GPU 或额外部署成本;通用模型对生僻人名和事件别名识别不稳定。相比之下,轻量级的词典加规则方案启动快、可解释性强、离线可用,也方便在出现漏判时快速补充规则。

本文选择的技术组合是jieba分词与词性标注、re正则表达式、flashtext词典替换。jieba负责把连续文本切分成词语,并标记人名词性;正则负责匹配“女友”“恋情”“前任”等关系描述;flashtext在做批量词典替换时比逐条replace性能更好。最终将清洗结果、脱敏文本、实体列表和命中规则统一输出为 JSON,方便后续审核系统消费。

1.3 本文能解决什么问题

读完本文,你可以实现以下能力:对任意包含人名的中文标题做标准化清洗;自动识别文本中的人名;标记未经核实的关系描述词;将人名和关系词替换为安全占位符;输出一份可供人工审核的 JSON 报告。这套流程可直接嵌入爬虫数据处理管道、CMS 内容发布后台或舆情系统中作为前置过滤器。

2. 环境准备与项目结构

2.1 运行环境说明

示例代码基于 Python 开发。建议使用 Python 3.9 及以上版本,依赖库版本以当前最新稳定版为准,核心逻辑对版本差异不敏感。操作系统方面,Windows、macOS 和 Linux 均可运行,命令行操作基本一致。

安装依赖前建议先创建虚拟环境:

python -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate

激活虚拟环境后,在项目根目录创建requirements.txt文件:

jieba flashtext

安装命令:

pip install -r requirements.txt

如果后续希望把审核报告输出为表格或 CSV,可以额外安装pandas,本文核心流程不强制依赖它。

2.2 示例项目结构

完整项目目录如下:

hottext-filter/ ├── data/ │ └── input.txt ├── filters/ │ ├── __init__.py │ ├── text_cleaner.py │ └── entity_filter.py ├── main.py └── requirements.txt

data/input.txt存放待处理文本;filters/text_cleaner.py负责文本清洗;filters/entity_filter.py负责实体识别、关系词标记和脱敏;main.py串联整个流程。下面先讲解核心概念,再逐一实现。

3. 核心思路拆解

3.1 文本清洗:先做减法再做识别

文本清洗不是简单去掉空格。网络文本中常混入 HTML 标签、URL、不可见控制字符、全角空格、多个连续空格。这些内容会影响分词效果,也会导致正则匹配漏判。

清洗顺序建议固定为:先删除 HTML 标签和链接,再清理控制字符,然后统一全角空格为半角,最后压缩连续空白符。这样做的好处是干净文本进入分词器后,人名词典命中率更稳定。

3.2 人名识别:词性标注加自定义词典

jieba.posseg可以对句子做词性标注,其中nr表示人名,nrfg表示人名格式词。但默认词典对新人名覆盖率有限,所以需要把业务场景中已知的人名加入自定义词典,并通过freq参数提高权重。

例如“孙宇晨”和“景甜”在通用分词中可能被拆成“孙/宇晨”“景/甜”。通过jieba.add_word("孙宇晨", freq=2000, tag="nr")可以强制让分词器将其识别为整体。

3.3 关系词标记:正则做粗筛

关系描述词通常有固定模式,例如“女友”“男友”“前任”“现任”“绯闻”“恋情”。用正则做粗筛的可解释性很强,命中规则后进入人工审核队列,而不是自动判定真伪。这种方式能显著减少漏网风险。

3.4 脱敏与审核报告

脱敏的目的是把风险降低到可控范围。人名替换为[人名],关系描述替换为[关系描述]。同时生成一个报告对象,记录哪些词被命中、命中的规则类型、替换后的文本。这样既能保护隐私,又不丢失后续人工复核所需的上下文。

4. 完整实战:处理一条热点标题

4.1 准备待处理文本

先创建data/input.txt,内容为一条模拟热点标题,仅用于技术演示:

孙宇晨长文《我的女友景甜》:财富难解十九年执念

在实际项目中,这个文件可以被爬虫写入,也可以由消息队列推送进入审核管道。

4.2 编写文本清洗模块

创建filters/text_cleaner.py文件:

# filters/text_cleaner.py import re def clean_text(text: str) -> str: """ 对网络文本做基础清洗,返回干净字符串。 清洗顺序: 1. 删除 HTML 标签 2. 替换链接为 [链接] 3. 删除不可见控制字符 4. 统一全角空格为半角 5. 压缩连续空白符 """ text = re.sub(r"<[^>]+>", "", text) text = re.sub(r"https?://\S+|www\.\S+", "[链接]", text) text = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]", "", text) text = text.replace("\u3000", " ") text = re.sub(r"[ \t]+", " ", text) return text.strip()

这个模块的核心是顺序执行的清洗管道。删除 HTML 标签可以避免网页源码混入正文;替换链接是为了防止超长 URL 干扰分词;控制字符清理针对从日志或网页接口中读取的非打印字符。如果后续遇到特殊业务符号,可以继续在函数末尾追加正则替换。

4.3 编写实体识别与脱敏模块

创建filters/entity_filter.py文件:

# filters/entity_filter.py import re from collections import Counter from typing import List, Optional import jieba import jieba.posseg as pseg jieba.setLogLevel(60) # 需要加入自定义词典的人名,实际项目中可从配置中心或数据库读取 CUSTOM_PERSON_NAMES = ["孙宇晨", "景甜"] # 关系描述粗筛规则,命中后进入人工审核 RELATION_PATTERNS = [ r"我的?(?:女友|男友|老婆|老公)", r"前任|现任", r"绯闻|恋情|疑似交往", ] def extract_persons(text: str, custom_names: Optional[List[str]] = None) -> List[str]: """ 使用 jieba 词性标注识别文本中的人名。 传入 custom_names 时,会补充到 jieba 自定义词典中。 """ if custom_names: for name in custom_names: jieba.add_word(name, freq=2000, tag="nr") result = [] for word, flag in pseg.cut(text): if flag in ("nr", "nrfg"): result.append(word) return result def flag_relations(text: str) -> List[str]: """ 匹配正文中的关系描述词,返回所有命中片段。 """ matched = [] for pattern in RELATION_PATTERNS: for m in re.finditer(pattern, text): matched.append(m.group(0)) return matched def mask_persons(text: str, persons: List[str]) -> str: """ 将人名替换为 [人名]。 按名字长度降序替换,避免短名字覆盖长名字的一部分。 """ unique_persons = sorted(set(persons), key=len, reverse=True) for person in unique_persons: text = re.sub(re.escape(person), "[人名]", text) return text def mask_relations(text: str, relations: List[str]) -> str: """ 将关系描述词替换为 [关系描述]。 先替换较长片段,再替换短片段。 """ unique_relations = sorted(set(relations), key=len, reverse=True) for relation in unique_relations: text = text.replace(relation, "[关系描述]") return text def build_report(text: str, cleaned_text: str, masked_text: str, persons: List[str], relations: List[str]): """ 构造审核报告,记录命中实体和规则数量。 """ person_counter = Counter(persons) relation_counter = Counter(relations) return { "original_text": text, "cleaned_text": cleaned_text, "masked_text": masked_text, "persons": list(person_counter.keys()), "person_count": sum(person_counter.values()), "relations": list(relation_counter.keys()), "relation_count": sum(relation_counter.values()), "flag": "NEED_REVIEW" if persons or relations else "PASS", }

这个模块的关键设计点有三个。第一,extract_persons在识别前把已知人名注册到 jieba 词典,可以显著提高整体识别的稳定性;第二,mask_persons按名字长度从长到短替换,避免误替换人名子串;第三,build_report不判断人物关系真假,只输出“需要人工审核”的标记,把最终判断权留给审核人员。

4.4 编写主流程

创建main.py

# main.py import json from filters.text_cleaner import clean_text from filters.entity_filter import ( CUSTOM_PERSON_NAMES, extract_persons, flag_relations, mask_persons, mask_relations, build_report, ) def read_input(path: str) -> str: with open(path, "r", encoding="utf-8") as f: return f.read().strip() def main(): raw_text = read_input("data/input.txt") cleaned_text = clean_text(raw_text) persons = extract_persons(cleaned_text, custom_names=CUSTOM_PERSON_NAMES) relations = flag_relations(cleaned_text) masked_text = mask_persons(cleaned_text, persons) masked_text = mask_relations(masked_text, relations) report = build_report( text=raw_text, cleaned_text=cleaned_text, masked_text=masked_text, persons=persons, relations=relations, ) print("原始文本:", raw_text) print("清洗后文本:", cleaned_text) print("脱敏后文本:", masked_text) print() print("审核报告 JSON:") print(json.dumps(report, ensure_ascii=False, indent=2)) if __name__ == "__main__": main()

主流程逻辑非常直接:读取文本、清洗、实体识别、脱敏、输出报告。每个函数职责单一,后续如果需要接入 Kafka 或消息队列,只需要把read_input替换成消费函数,把print部分替换成回调逻辑。

4.5 运行与预期输出

在项目根目录执行:

python main.py

预期输出结果如下:

原始文本: 孙宇晨长文《我的女友景甜》:财富难解十九年执念 清洗后文本: 孙宇晨长文《我的女友景甜》:财富难解十九年执念 脱敏后文本: [人名]长文《我的[关系描述][人名]》:财富难解十九年执念 审核报告 JSON: { "original_text": "孙宇晨长文《我的女友景甜》:财富难解十九年执念", "cleaned_text": "孙宇晨长文《我的女友景甜》:财富难解十九年执念", "masked_text": "[人名]长文《我的[关系描述][人名]》:财富难解十九年执念", "persons": [ "孙宇晨", "景甜" ], "person_count": 2, "relations": [ "我的女友" ], "relation_count": 1, "flag": "NEED_REVIEW" }

从结果可以看到,“孙宇晨”和“景甜”都被识别为人名,“我的女友”被识别为关系描述,整个标题被脱敏为不包含真实姓名的安全文本。审核系统可以针对flag=NEED_REVIEW的记录进入人工复核队列,复核通过后再决定是否放行或发布。

4.6 批量处理场景

实际项目中,待处理文本往往不止一条,而是以文件、数据库表或消息队列的形式持续进入。这里补充一个简单的批量读取目录文件的示例,演示如何复用上面的函数。

# batch_process.py import json from pathlib import Path from filters.text_cleaner import clean_text from filters.entity_filter import ( CUSTOM_PERSON_NAMES, extract_persons, flag_relations, mask_persons, mask_relations, build_report, ) def process_one(raw_text: str): cleaned_text = clean_text(raw_text) persons = extract_persons(cleaned_text, custom_names=CUSTOM_PERSON_NAMES) relations = flag_relations(cleaned_text) masked_text = mask_persons(cleaned_text, persons) masked_text = mask_relations(masked_text, relations) return build_report(raw_text, cleaned_text, masked_text, persons, relations) def main(): input_dir = Path("data/batch") output_file = Path("data/reports.jsonl") results = [] for file_path in input_dir.glob("*.txt"): raw_text = file_path.read_text(encoding="utf-8").strip() report = process_one(raw_text) results.append(report) with open(output_file, "w", encoding="utf-8") as f: for report in results: f.write(json.dumps(report, ensure_ascii=False) + "\n") print(f"处理完成,共生成 {len(results)} 条审核报告,输出文件:{output_file}") if __name__ == "__main__": main()

批量处理采用逐文件读取、逐条处理、统一写 JSONL 的方式。使用 JSONL 而不是单个 JSON 数组,是为了方便在日志系统中按行消费,也避免一次加载全部结果到内存中。如果文本规模进一步扩大,可以考虑用concurrent.futures.ProcessPoolExecutor做多进程并行处理。

5. 常见问题与排查思路

5.1 常见报错速查

问题现象常见原因解决思路
jieba 把人名拆成多个词自定义词典未加载或频率权重低在识别前执行jieba.add_word,提高 freq
正则匹配不到“我的女友”文本中有全角字符或多余空格先执行文本清洗,再正则匹配
输出 JSON 中文乱码json.dumps未指定ensure_ascii=False添加ensure_ascii=False参数
批量处理时内存持续增长一次性把全部结果保存在列表里改为逐行写入 JSONL
文本读取报编码错误文件编码不是 UTF-8读取时指定encoding="utf-8",必要时做编码检测

5.2 jieba 分词不稳定

现象是“孙宇晨”被切分为“孙”“宇晨”,或者“景甜”被拆成“景”“甜”。根因是通用词典未收录这些名字。解决方案是在分词前调用jieba.add_word,并设置一个较高的freq。但需要注意,add_word是全局生效的,在多线程场景下需要做好词典初始化顺序。

避免该问题再次出现的办法是把自定义人名清单独立成配置文件或数据库表,启动时统一加载,而不是散落在业务代码中。这样新的人名进入系统后,只更新配置,不需要改代码。

5.3 关系描述规则误伤

有时候“前任”“现任”可能出现在完全合法的语境中,比如“现任领导”“前任经理”。正则粗筛会把这些词一起标记为需要审核,误报率较高。

要降低误报,可以引入上下文判断。比如只匹配“我的前任”“我的现任”“疑似恋情”等强主观表达,而不是匹配所有包含“前任”的句子。也可以在规则命中后,输出命中的上下文片段,给人工审核提供参考。

5.4 脱敏顺序导致人名残留

假设待处理文本同时包含“孙宇晨”和“孙宇”,如果先替换“孙宇”,剩余文本会变成[姓名]晨,无法再匹配“孙宇晨”。这是典型的长度覆盖问题。解决方案在mask_persons中已经体现:按名字长度降序替换,确保长词优先。

但仅靠长度排序仍然不够,因为正则替换是逐词遍历的。如果一个名字是另一个名字的子串,且两者都出现在文本中,那长词替换后短词自然无法匹配。更好的做法是使用flashtextreplace_keywords方法,它基于 Trie 树实现,可以一次完成最长匹配替换,避免多次正则替换的副作用。

5.5 生产环境编码与数据源问题

网络采集到的文本编码五花八门,常见的就有 UTF-8、GBK、GB18030。如果直接按 UTF-8 读取,部分数据会触发UnicodeDecodeError。更稳妥的方式是使用chardetcharset-normalizer检测编码,或者在写入数据库时统一转成 UTF-8。

另外,建议在采集端就做编码规范化,不要等到审核阶段再处理。数据管道越早进入标准格式,后续 NLP 处理效果越稳定。

6. 工程最佳实践与合规建议

6.1 合规红线:未经核实的关系描述不能直接展示

技术过滤的边界是“可判定风险”,而不是“判定事实”。人名识别能告诉你文本里有谁,关系词匹配能告诉你有疑似关系描述,但都不能证明这条关系是真实的。因此,任何未经证实的私人领域关系描述,都应该进入人工审核队列,审核通过前不能公开展示。

在技术文档中,建议明确写出这条规则:数据处理系统只负责标记和降级,不负责下结论。这样既保护了系统使用者,也减少了对文本中涉及个人的名誉风险。

6.2 最小权限与数据隔离

审核系统只应该读取它需要处理的文本字段,不要试图抓取采集对象的全部信息。例如,如果只是审核标题,就不要把正文、评论、用户 IP、设备信息全部拉进来。最小权限原则能降低数据泄露风险,也方便后续做合规审计。

在云环境或数据库方案中,建议单独为审核服务创建专用账号,仅授予目标表和队列的读写权限。如果使用消息队列,主题名称也应当与业务主题隔离,避免敏感文本混入无关链路。

6.3 规则版本管理与日志审计

过滤规则会随着业务需求持续变更。正则表达式每次改动,都可能影响新旧文本的审核结果,因此需要给规则加上版本号。审核报告中可以增加rule_version字段,记录本次处理使用的规则版本。这样在后期追溯问题时,能够快速定位是规则误判还是数据问题。

日志方面,不需要记录完整原文,因为原文本身可能包含隐私信息。更合理的做法是记录脱敏后的文本、命中词、规则版本和审核结论。原文只在必要的时间窗口内保存,且保存时长遵循最小化原则。

6.4 性能优化:正则预编译与 pipeline 化

如果每天处理量达到百万级,建议把正则表达式预编译为Pattern对象,而不是每次匹配时重新编译。同时,可以把清洗、识别、脱敏步骤封装成独立的管道节点,每个节点只处理一种职责。这样既能复用,也便于在某个节点上做弹性扩容。

还有一个容易被忽略的优化点:如果业务场景中大部分文本经过规则预筛后都没有命中任何人名或关系词,就不需要进入完整 NLP 流程。可以先做快速过滤,只有命中粗筛规则的文本才进入后续jieba处理,从而节省大量 CPU。

6.5 体验设计:提供可解释的拦截原因

在内容管理后台中,审核员看到的不应该只是“拦截”或“待审核”两个状态,而应该看到具体的命中标签,例如“疑似包含人名:2个”“疑似关系描述:1个”。这样审核员能快速判断是放行还是删除,而不是打开原文从零开始看。

本文的build_report已经输出的personsrelations字段,正是为了支持这种可解释性。在真实的审核后台中,可以把这些字段呈现为标签列表,辅助审核决策。

7. 总结与后续学习方向

本文从一条热点标题的清洗与过滤需求出发,实现了基于 Python 的完整文本处理流程。核心内容包括:用正则和字符替换完成文本标准化;用jieba.posseg完成人名词性标注;用自定义词典解决新人名识别问题;用关系词粗筛规则标记需要人工核验的片段;最后输出 JSON 格式审核报告,方便业务系统消费。

这套方案的优势在于代码量小、启动快、完全离线运行,适合快速嵌入已有数据处理链路。生产环境中,不建议把规则的准确性当作模块的最终目标。相反,应该把高召回率作为目标,宁可让更多文本进入人工审核,也不能漏过未经核实的人名和关系词。

后续你可以从两个方向继续深入。第一,把规则词典替换为序列标注模型,例如使用 LAC、BERT 等工具提升人名和组织名识别率;第二,将当前单机脚本改造成服务化组件,比如基于 FastAPI 提供 HTTP 接口,或结合消息队列实现异步审核管道。如果本文对你有帮助,可以先收藏备用,等实际项目需要时直接按这个框架改造即可。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询