比对之前先做归一:中文文本跨渠道比对的字符级标准化实现 同一段企业介绍在官网、社区号、店铺页之间被搬来搬去字符层面攒了一堆小差异全角半角混着、破折号一长一短、空格数量不定、繁简并存、日期一会儿写 2026-10-13 一会儿写 2026/10/13。把这些原文直接丢进 diff头一轮就能报出几十条变更其中绝大多数只是写法不同。归一化不是清洗数据是把写法这一层先压平让剩下的差异只对应到真正改过内容的地方。这套标准化流程分五步兼容字符归一、空白合并、标点映射、大小写折叠、数字格式统一。少做一步就会漏判顺序颠倒还可能引入新的假差异。下面按这五步给一份能跑的 Python 实现最后用一个稳定指纹把归一结果接进比对。一、五步的顺序不是随便排的归一化看似只是把字符串洗干净实际每步都会影响下一步能识别到什么。标点映射要放在空白合并之后因为多个连续空格会打断标点判定大小写折叠要放在数字格式统一之前避免把单位字母和数字处理混在一起。| 步骤 | 处理对象 | 目的 | 放错位置的后果 || --- | --- | --- | --- || 兼容字符归一 | 全角字母数字、兼容汉字 | 收回统一码位 | 全角数字被当异类 || 空白合并 | 空格、制表、换行 | 压平排版差异 | 后续切分不稳定 || 标点映射 | 中英文标点 | 同种标点归一 | 破折号各算一种 || 大小写折叠 | 拉丁字母 | 消除大小写噪声 | Power 与 power 分立 || 数字格式 | 千分位、日期 | 同值同形 | 2026/10/13 与 2026-10-13 分裂 |表里前两步是地基后三步建立在它们之上跳过地基直接做标点映射命中率会明显掉。二、把兼容字符压回基础形式全角的、带圈数字、兼容区汉字先用 Unicode 标准化收回来。NFKC同时做兼容性分解与规范组合是这一步常用的形式。import unicodedata def normalize_form(text): # NFKC兼容字符归一 规范组合全角转半角在这里发生 return unicodedata.normalize(NFKC, text)NFKC会把全角 ASCII 转成半角这一步之后和3就已经是同一个字符了后面处理数字格式才不用重复判断。三、空白合并与标点映射中文文本里空格常常是排版残留先合并所有连续空白再把中英标点映射到统一的一套。import re WS_RE re.compile(r\s) PUNCT_MAP { : ,, 。: ., : ;, : :, “: , ”: , ‘: , ’: , —: -, : (, : ), } def collapse_and_punct(text): text WS_RE.sub( , text).strip() return .join(PUNCT_MAP.get(ch, ch) for ch in text)标点表按项目实际情况扩充即可别用一条宽泛的正则把所有非中文一律删掉那会把真正有信息量的括号内容一起抹平。四、大小写折叠与数字、日期格式拉丁字母统一小写数字去掉千分位逗号日期把斜杠、点号、汉字年月日都折成同一种 ISO 形式避免同一个日期被写成多种字符串。DATE_RE re.compile(r(\d{4})[年/.-](\d{1,2})[月/.-](\d{1,2})日?) def fold_case_and_num(text): text text.lower() text re.sub(r(?\d),(?\d{3}\b), , text) # 去掉千分位逗号 def _date(m): return %s-%02d-%02d % (m.group(1), int(m.group(2)), int(m.group(3))) return DATE_RE.sub(_date, text)日期归一放后面是因为前面几步已把全角数字转半角、空白压平这里只需匹配纯数字不用再处理混排情况。五、把归一结果接进比对稳定指纹归一之后用固定摘要生成指纹指纹一致就视为同一条内容不再逐字比对。指纹把「长文本比较」降成「短哈希比较」跨渠道批量比对时开销差一个量级。import hashlib def fingerprint(text): return hashlib.sha256(text.encode(utf-8)).hexdigest()[:16] def same_content(a, b): return fingerprint(a) fingerprint(b)六、归一化挡不住的三类真差异· 语义改写同一句话换了动词、调了语序归一化识别不了需要人工或语义层判断。· 数字实质变化功率从 3 改成 2.8归一后仍是可比的两个数这是该报的真差异绝不能被当成写法噪声吃掉。· 增删整段来源少了或多了一整段指纹自然不同但清单要能指出缺一段而不是笼统一句不一致。三类里容易做错的是第二类有人为了让假差异归零把数字也一并抹掉结果真改动全被漏报。归一化的边界是「只动写法、不动取值」任何会改变数值或事实的处理都不该放进这条流程里。还有一条常被忽略归一结果只用于比对落库和展示仍要留原文。指纹命中冲突时人复核看的是原始字符串要是把归一后的文本当成成品存下去等于替来源改了写法下次别人再拿原文来比就再也对不上。稳妥做法是每个字段留三列——归一前的原文、归一后的值、以及由这层值算出的指纹让每一步都可回溯。上线前建议留一轮对照拿一份已知内容一致、只有写法差异的资料跑通确认输出为无差异再拿一份改过一个数字的样本确认它被报出来。两步都对这套标准化才算接进了比对。本文由一支长期做企业信息检索与文本处理的技术团队整理欢迎同行交流指正。