一、为什么先聊”清洗 + 结构化”
企业上知识库,第一步往往不是选工具,而是处理手里那堆文档。现实里的公司资料长这样:产品手册是五年前 Word 导出的 PDF、客服话术散在企微群里、报价单是各种命名风格的 Excel、合同模板混着扫描件。
把这些直接丢给 AI,结果就是「垃圾进、垃圾出」:召回不准、答非所问、把旧价格当新价格。大模型对格式和噪声极其敏感,喂进去的每一处页眉页脚、乱码、重复段落,都会变成干扰信号。
「文档清洗 + 结构化」要解决的,就是把上面这堆非结构化、脏乱差的资料,变成 AI 能稳定读取、检索、引用的干净语料。它是知识库效果的天花板,也是落地成本的大头。
二、两个概念先分清
- 清洗(Cleaning):去掉噪声和错误——去重、去页眉页脚、修乱码、纠 OCR 错误、脱敏。目标是「干净」。
- 结构化(Structuring):把自由文本变成有层级、有元数据、可切分的格式(Markdown 标题树、JSON、表格转 CSV)。目标是「可被机器理解」。
两者顺序上通常先清洗后结构化,但实践中常交织进行。
三、常见源文档与典型痛点
| 来源 | 典型问题 | 处理重点 |
|---|---|---|
| Word / 富文本 | 样式不统一、图片表格混排、多余批注 | 抽纯文本 + 保留标题层级 |
| 扫描件无文字层、双栏错乱、表格提取丢列 | 先 OCR,再按区块解析 | |
| Excel | 合并单元格、多 Sheet、隐藏行列 | 逐表展开为行记录 |
| 聊天记录 | 噪声大、上下文弱、口语化 | 按话题聚合 + 抽取问答对 |
四、清洗流程(七步)
- 收集归档:按来源 / 部门 / 时间建目录,先别急着处理,避免漏文件。
- 格式统一:全部转成 Markdown 或纯文本,作为中间格式。Word 用
markitdown,PDF 用pdfplumber/Unstructured。 - 去重:先按内容哈希去完全重复,再按语义相似度(embedding 余弦)去近似重复。
- 降噪:删页眉页脚、页码、目录、广告、换行断裂的句子。
- 纠错:修正编码乱码、OCR 把「0」识别成「O」这类错误(金额、单号尤其要小心)。
- 脱敏:手机号、邮箱、身份证、合同金额、客户名做掩码或替换,再进知识库。
- 分块(Chunking):按语义或标题层级切成合理大小的片段,控制每个片段的 token 数。
五、结构化怎么做
- 保留文档树:用 Markdown 的
# / ## / ###保留原文档章节,检索时层级就是上下文。 - 抽取元数据:每篇文档附带
来源部门 / 文档类型 / 生效时间 / 版本,便于后续过滤检索。 - 表格结构化:把报价单、参数表从「图片里的表格」转成 CSV / JSON 行记录,模型查具体数值才准。
- 问答对抽取:从客服话术、FAQ 里抽
问题 → 标准回答成对存储,命中率高、幻觉少。
六、工具链(从零代码到轻代码)
零代码:Dify / Coze / FastGPT 自带文档导入与切分;markitdown 命令行一键转 Markdown。
轻代码(推荐):Python + python-docx(Word)、pdfplumber(PDF)、pandas(Excel)。下面给一段可复用的清洗 + 分块骨架:
import re, hashlib
def clean_text(text):
# 1. 统一空白与常见乱码
text = text.replace(" ", " ")
text = re.sub(r"n{3,}", "nn", text)
# 2. 去典型页眉页脚(按行规则,自行适配)
lines = [ln for ln in text.splitlines()
if not re.match(r"^(第s*d+s*页|www.)", ln.strip())]
return "n".join(lines).strip()
def dedup(items):
seen, out = set(), []
for it in items:
h = hashlib.md5(it.encode("utf-8")).hexdigest()
if h not in seen:
seen.add(h); out.append(it)
return out
def chunk(text, size=800, overlap=80):
# 简单按长度切分,真实场景建议按标题/句号切
if len(text) <= size:
return [text]
res, start = [], 0
while start < len(text):
end = min(start + size, len(text))
res.append(text[start:end])
start += size - overlap
return res
raw = open("source.txt", encoding="utf-8").read()
clean = clean_text(raw)
blocks = dedup(chunk(clean))
print(f"生成 {len(blocks)} 个分块")
说明:示例用长度切分便于理解;生产环境优先用「按 ## 标题 + 句子边界」的语义切分,overlap 保留跨块上下文。
七、分块策略(直接影响 RAG 效果)
| 策略 | 适用 | 注意 |
|---|---|---|
| 固定长度 | 通用、实现简单 | 容易把一句话拦腰截断 |
| 语义 / 标题切分 | 手册、SOP、合同 | 保留章节上下文,推荐 |
| 父子文档 | 长文档检索 | 检索命中子块,回答时回贴父块上下文 |
经验值:中文每块 500—1000 字、重叠 80—150 字较稳;表格 / 代码块尽量不切开。
八、质量验收清单
- 同一份文档不再重复出现;
- 页眉页脚、页码、乱码已清除;
- 金额、单号、手机号等敏感信息已脱敏;
- 章节层级用 Markdown 标题正确表达;
- 表格数值可被单独检索到;
- 随机抽 20 条做问答测试,回答能准确引用原文。
九、常见坑
- 扫描件直接入库:没有文字层,检索永远为空,必须先 OCR。
- 表格当成图片:参数表变成一张图,模型读不到具体数值。
- 过度切分:块太小,回答失去上下文,出现「断章取义」。
- 忘了脱敏:把客户合同原样喂给公有云模型,造成数据泄露。
把清洗与结构化做扎实,知识库的效果能上一个台阶;反之,后面再强的模型也救不回来。它是「AI 落地」里最枯燥、却最决定成败的一步。