ARTICLE DETAIL

资讯详情

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

企业知识库增量同步:用内容哈希做变更检测与去重

企业知识库增量同步:用内容哈希做变更检测与去重

知识库做到一定规模后,全量重建会变成一件负担。每次同步都重新解析所有文档,耗时、耗算力,还会覆盖人工修订过的条目。更麻烦的是去重:同一份内容在网盘、邮件附件、共享盘里各存一份,文件名不同、修改时间不同,靠路径判断根本认不出来。

这篇文章给一个可落地的增量方案:抽取正文、归一化、算内容哈希、按哈希比对,只处理真正变了的内容。

一、先定义「内容变没变」

不能拿文件修改时间当判据。复制、下载、解压都会刷新时间戳,但内容没变。也不能只比文件大小,改一个字大小可能不变。

可靠的做法是对正文做归一化后求哈希。归一化包含四步:统一换行、压缩连续空白、统一全角半角、去掉页眉页脚这类版式噪声。归一化之后,同一份内容无论从哪种格式导出,哈希都应当一致。

下面是一段 Node 实现,依赖内置模块,不需要额外安装:

const crypto = require('crypto'); function normalize(text) { return text .replace(/\r\n?/g, '\n') .replace(/[\u3000\s]+/g, ' ') .replace(/[\uFF01-\uFF5E]/g, ch => String.fromCharCode(ch.charCodeAt(0) - 0xFEE0)) .trim(); } function contentHash(text) { return crypto.createHash('sha256') .update(normalize(text), 'utf8') .digest('hex'); }

归一化之后再做全文哈希,可以从根上识别「换个文件名但内容相同」的副本。

二、再区分「改了一点」与「换了内容」

全文哈希能判断相等,却判断不了相似。一份文档只改了一个段落,全文哈希全变,按「变了就重建」处理,等于每次都重建。

这里要再加一层:段落级指纹。把正文按空行切成块,对每块求哈希,用集合比较。

function blockFingerprints(text) { return new Set( normalize(text) .split(/\n{2,}/) .map(b => b.trim()) .filter(b => b.length > 20) .map(b => contentHash(b)) ); } function diffRatio(oldText, newText) { const a = blockFingerprints(oldText); const b = blockFingerprints(newText); let same = 0; for (const h of b) if (a.has(h)) same++; return 1 - same / Math.max(b.size, 1); }

diffRatio小于 0.2 视为局部修订,只更新变动的段落;大于 0.6 视为新文档,整篇重建。中间区间进入人工复核队列,避免误判。

三、同步流程的五个状态

增量同步的关键不是算法,而是状态管理。每条内容在库里应当有明确状态,流程才能断点续跑。

| 状态 | 含义 | 进入条件 | 后续动作 |
|---|---|---|---|
| new | 新发现 | 哈希不在索引 | 解析并入库 |
| changed | 内容变更 | 哈希存在但全文不同 | 按差异比例分流 |
| same | 未变化 | 全文哈希一致 | 跳过 |
| missing | 来源消失 | 扫描不到源文件 | 标记待确认,不直接删 |
| conflict | 多源冲突 | 同标题不同哈希 | 人工复核 |

missing不要直接删。源文件消失可能是网盘同步延迟,也可能是权限变更导致的读不到。直接删会造成知识库缺口,而且很难恢复。

四、索引表怎么设计

索引表至少要存五列:内容哈希、来源路径、最近扫描时间、状态、版本号。哈希作为主键,一个哈希对应多条来源路径,这样天然支持多源去重。

版本号用来支持回滚。每次内容变更生成一个新版本,旧版本保留但标记失效。回填到问答系统时只取有效版本,历史版本仍可追溯。

CREATE TABLE kb_index ( hash TEXT PRIMARY KEY, paths TEXT NOT NULL, status TEXT NOT NULL, version INTEGER NOT NULL DEFAULT 1, scanned_at TEXT NOT NULL );

索引与正文分表存储。正文放在对象存储或文件系统,索引只存元数据,扫描与比对都走索引,速度会快很多。

五、两个容易忽略的边界

一是编码。老文档常见 GBK 与 UTF-8 混用,读进来先探测编码再解码,否则同一份内容会算出两个哈希。Node 侧可以用iconv-lite,Python 侧用chardet加codecs。

二是表格与图片。表格里的信息很多,但归一化时容易被压成一行,导致不同表格算出相同哈希。建议表格单独抽取为结构化文本再参与哈希,图片则只记录引用,不参与内容比对。

做到这一步,增量同步的准确率基本可控。剩下的工作是定期跑一次全量校验,确认索引与正文一致。增量解决效率,全量解决正确性,两者不能互相替代。

(本文由一支长期做企业内容系统的技术团队整理,欢迎同行交流指正。)

返回列表