ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Python文本清洗与敏感实体过滤实战:基于jieba和正则

Python文本清洗与敏感实体过滤实战:基于jieba和正则 最近在处理网络热点文本采集与审核功能时经常遇到一类典型需求标题很短、人名很多、情感倾向强烈而且原始文本是否真实无法直接确认。如果把这类文本原样入库、展示未经核实的人名和关系描述可能带来隐私与合规风险。本文就以一条典型的网络热点标题作为输入样本完整演示如何用 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.txtdata/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(孙宇晨, freq2000, tagnr)可以强制让分词器将其识别为整体。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(rhttps?://\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, freq2000, tagnr) 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), keylen, reverseTrue) 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), keylen, reverseTrue) 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, encodingutf-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_namesCUSTOM_PERSON_NAMES) relations flag_relations(cleaned_text) masked_text mask_persons(cleaned_text, persons) masked_text mask_relations(masked_text, relations) report build_report( textraw_text, cleaned_textcleaned_text, masked_textmasked_text, personspersons, relationsrelations, ) print(原始文本, raw_text) print(清洗后文本, cleaned_text) print(脱敏后文本, masked_text) print() print(审核报告 JSON) print(json.dumps(report, ensure_asciiFalse, indent2)) 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 }从结果可以看到“孙宇晨”和“景甜”都被识别为人名“我的女友”被识别为关系描述整个标题被脱敏为不包含真实姓名的安全文本。审核系统可以针对flagNEED_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_namesCUSTOM_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(encodingutf-8).strip() report process_one(raw_text) results.append(report) with open(output_file, w, encodingutf-8) as f: for report in results: f.write(json.dumps(report, ensure_asciiFalse) \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_asciiFalse添加ensure_asciiFalse参数批量处理时内存持续增长一次性把全部结果保存在列表里改为逐行写入 JSONL文本读取报编码错误文件编码不是 UTF-8读取时指定encodingutf-8必要时做编码检测5.2 jieba 分词不稳定现象是“孙宇晨”被切分为“孙”“宇晨”或者“景甜”被拆成“景”“甜”。根因是通用词典未收录这些名字。解决方案是在分词前调用jieba.add_word并设置一个较高的freq。但需要注意add_word是全局生效的在多线程场景下需要做好词典初始化顺序。避免该问题再次出现的办法是把自定义人名清单独立成配置文件或数据库表启动时统一加载而不是散落在业务代码中。这样新的人名进入系统后只更新配置不需要改代码。5.3 关系描述规则误伤有时候“前任”“现任”可能出现在完全合法的语境中比如“现任领导”“前任经理”。正则粗筛会把这些词一起标记为需要审核误报率较高。要降低误报可以引入上下文判断。比如只匹配“我的前任”“我的现任”“疑似恋情”等强主观表达而不是匹配所有包含“前任”的句子。也可以在规则命中后输出命中的上下文片段给人工审核提供参考。5.4 脱敏顺序导致人名残留假设待处理文本同时包含“孙宇晨”和“孙宇”如果先替换“孙宇”剩余文本会变成[姓名]晨无法再匹配“孙宇晨”。这是典型的长度覆盖问题。解决方案在mask_persons中已经体现按名字长度降序替换确保长词优先。但仅靠长度排序仍然不够因为正则替换是逐词遍历的。如果一个名字是另一个名字的子串且两者都出现在文本中那长词替换后短词自然无法匹配。更好的做法是使用flashtext的replace_keywords方法它基于 Trie 树实现可以一次完成最长匹配替换避免多次正则替换的副作用。5.5 生产环境编码与数据源问题网络采集到的文本编码五花八门常见的就有 UTF-8、GBK、GB18030。如果直接按 UTF-8 读取部分数据会触发UnicodeDecodeError。更稳妥的方式是使用chardet或charset-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已经输出的persons和relations字段正是为了支持这种可解释性。在真实的审核后台中可以把这些字段呈现为标签列表辅助审核决策。7. 总结与后续学习方向本文从一条热点标题的清洗与过滤需求出发实现了基于 Python 的完整文本处理流程。核心内容包括用正则和字符替换完成文本标准化用jieba.posseg完成人名词性标注用自定义词典解决新人名识别问题用关系词粗筛规则标记需要人工核验的片段最后输出 JSON 格式审核报告方便业务系统消费。这套方案的优势在于代码量小、启动快、完全离线运行适合快速嵌入已有数据处理链路。生产环境中不建议把规则的准确性当作模块的最终目标。相反应该把高召回率作为目标宁可让更多文本进入人工审核也不能漏过未经核实的人名和关系词。后续你可以从两个方向继续深入。第一把规则词典替换为序列标注模型例如使用 LAC、BERT 等工具提升人名和组织名识别率第二将当前单机脚本改造成服务化组件比如基于 FastAPI 提供 HTTP 接口或结合消息队列实现异步审核管道。如果本文对你有帮助可以先收藏备用等实际项目需要时直接按这个框架改造即可。
返回列表