企业 RAG 文档入库实战:Word、PDF、Markdown 解析与清洗 一套知识库明明已经接入了几千份文档用户问“MQTT 采集模块怎么配置”召回结果里却出现“上次编辑者”“您的浏览器不支持 video 标签”和论坛回复楼层号。向量库没有坏Embedding 也没有突然失效真正的问题发生在更早的地方我们把网页残留、页眉页脚和正文一起当成了知识。很多 RAG 教程从切块和向量化开始但企业文档入库的第一场硬仗其实是数据治理。参考案例中团队面对来自 Confluence、Word、Markdown 和内部论坛的混合资料清洗工作占据了大量精力。这个案例数字属于原作者不能当成通用结论但它揭示的问题非常普遍解析成功只代表文件能够被读取不代表内容已经适合检索。这篇不讲“调用一个 Loader 就完成入库”而是把文档处理拆成一条可以实现、测试和审计的 Java 流水线Extract → Normalize → Clean → Validate → Export最终目标不是得到一大段字符串而是得到一份来源清楚、结构稳定、清洗过程可解释、失败后能够重跑的知识资产。一、先把文档噪声分成六类文档噪声不能只按“有没有乱码”判断。更实用的做法是按产生原因分类因为每一类噪声需要不同的处理策略。1. 页面模板噪声这类内容来自文档系统而不是作者正文Confluence 页面 ID、下载次数、更新时间“上一篇 / 下一篇”“附件下载”“返回目录”“您的浏览器不支持 video 标签”论坛楼层、回复按钮、用户签名。它们的危险之处不是占空间而是会反复出现在大量文档中。Embedding 模型会把这些高频模板当成语料特征导致本来无关的文档在向量空间里靠得更近。2. 页眉、页脚与重复块PDF 导出后章节标题、公司名称、保密标识、页码可能在每一页重复。假设一份 80 页手册每页都有“内部资料请勿外传”直接抽取后这句话会出现 80 次。它既浪费 Token又可能挤占真正正文的权重。3. 版式噪声双栏 PDF、跨页表格、浮动文本框和 Word 图文混排经常造成阅读顺序错乱。例如左栏第一段可能和右栏第二段拼在一起表头留在上一页数据行落到下一页。纯文本看起来“每个字都提取出来了”语义却已经断裂。4. 编码与字符噪声包括不可见控制字符、零宽空格、错误换行、连续空白、全角半角混用以及 OCR 带来的相似字符错误。它们会影响关键词检索、去重、Hash 和规则匹配。5. 结构性内容标题、列表、表格、代码块和图片占位符并不是噪声。错误的清洗脚本如果“只保留汉字和标点”很可能把最有价值的配置表、错误码和代码样例一起删掉。6. 业务失效内容过期制度、旧版本接口和重复草稿在语法上完全正常却可能给出错误答案。它们需要版本号、生效时间、状态和来源权限治理不能靠正则表达式解决。二、三种格式不能共用一个解析假设Apache Tika 能通过统一接口检测并抽取上千种文件类型的文本与元数据非常适合搭建通用入口。但“统一入口”不等于“所有格式都只拿一段纯文本”。真正进入清洗前至少要保留格式特有的结构信息。Word段落、表格、页眉页脚都能单独访问Apache POI 的 XWPF 提供了对.docx段落、表格、页眉、页脚和图片等结构的访问。XWPFWordExtractor#getText()适合快速抽取但如果需要删除页眉、保留表格或记录图片位置就应该遍历IBodyElement而不是在最终字符串上猜结构。PDF文本层不等于阅读顺序PDFBox 的PDFTextStripper会提取文本并忽略格式。对于单栏、文字层完整的 PDF它是可靠的起点面对扫描件、复杂双栏、跨页表格时则需要 OCR、版面分析或人工隔离。一个很实用的规则是不要因为 PDFTextStripper 返回了非空字符串就判定解析成功。Markdown先解析 AST再处理节点CommonMark 把文档建模为标题、段落、列表、引用、代码块等块级结构以及链接、强调、图片等行内结构。用 commonmark-java 解析成 AST 后可以精确删除导航块、保留代码块和重写图片链接如果直接用正则删除#、[]()和反引号结构信息会被一起抹掉。因此统一模型应该长这样publicrecordParsedDocument(StringdocumentId,StringsourceUri,StringmediaType,ListBlockblocks,MapString,Objectmetadata,ListAssetRefassets,ListParseWarningwarnings){}publicrecordBlock(StringblockId,BlockTypetype,Stringtext,intorder,MapString,Objectattributes){}blocks保存顺序和类型assets保存图片等外部资源warnings则记录“疑似扫描件”“表格跨页”“字符替换”等不应该被静默吞掉的问题。三、五段式清洗流水线Extract只负责忠实读取抽取层的任务是把源文件转成统一结构尽量不做业务删除。它需要记录解析器版本、源文件 Hash、Media Type、页数、段落数和告警。如果这里就“顺便清洗”后面很难解释内容为什么消失。Normalize消除表示差异规范化处理可重复、低风险的变换例如Unicode 规范化\r\n与\r统一为\n连续空格与空行收敛全角空格转换标准化图片引用和代码块边界。Normalize 必须保持幂等同一份内容执行两次结果应与执行一次相同。Clean用命名规则删除确定噪声清洗规则不应该是一串散落在代码里的replaceAll。每条规则要有名称、版本、适用格式和命中记录publicinterfaceCleaningRule{Stringid();Stringversion();booleansupports(ParsedDocumentdocument);RuleResultapply(ParsedDocumentdocument);}publicrecordRuleResult(ParsedDocumentdocument,inthitCount,ListChangeEvidenceevidences){}例如remove-confluence-footer2可以删除确定的页脚模板同时保存删除前后片段。以后发现误删时能够准确定位是哪条规则造成的而不是重新检查几十个正则。Validate把“清洗完成”变成可判断状态验证层至少检查正文是否为空或过短字符保留率是否异常标题、表格、代码块是否仍然存在重复段落比例是否下降是否产生未闭合 Markdown 代码围栏告警数量是否超过阈值。不要设置一个“删除超过 30% 就失败”的万能阈值。论坛导出可能有大量模板噪声技术规范则可能几乎不能删。阈值应该按来源、格式或业务域配置。Export机器版和人读版分开用于向量化的 Markdown 需要结构稳定、图片引用可追踪供用户点击引用的版本则应尽量保留可读排版。把两个目标塞进同一个文件往往两边都做不好。一种稳妥的输出结构是artifacts/{documentId}/ ├── machine.md ├── readable.docx ├── assets/ ├── manifest.json └── audit.json其中manifest.json记录源文件、版本、Hash 和资源映射audit.json记录规则命中与验证结果。四、Java 中如何实现规则化清洗解析器可以通过策略表路由不要在一个方法里堆文件后缀判断publicfinalclassParserRegistry{privatefinalMapString,DocumentParserparsers;publicParsedDocumentparse(SourceFilesource){varparserOptional.ofNullable(parsers.get(source.mediaType())).orElseThrow(()-newUnsupportedMediaTypeException(source.mediaType()));returnparser.parse(source);}}规则链则应输出审计结果而不是只返回字符串publicCleanedDocumentclean(ParsedDocumentsource){varcurrentnormalizer.normalize(source);varauditsnewArrayListRuleAudit();for(varrule:rules){if(!rule.supports(current))continue;varresultrule.apply(current);currentresult.document();audits.add(newRuleAudit(rule.id(),rule.version(),result.hitCount(),result.evidences()));}returnvalidator.validate(current,audits);}这里有三个关键点rules顺序固定并版本化保证同版本可重放每一步都产生证据方便 Diff 和回滚验证失败返回隔离状态不把半成品继续送去 Embedding。五、规则与大模型怎么分工大模型适合发现模式不适合直接成为不可解释的删除器。合理流程是先对不同来源做分层抽样让模型帮助归纳可能的噪声模式工程师把高置信模式固化为确定规则全量运行后再次抽样验证。模型可以提示“论坛尾部经常出现固定回复结构”但最终删除动作应由可测试的规则完成。不建议把每份文档完整交给模型让模型返回“清洗后的全文”原因包括模型可能改写原意而不只是删除噪声长文成本和失败重试成本高输出难以精确 Diff同一输入在不同模型或参数下可能产生差异表格、代码和标识符容易被“顺手优化”。六、清洗结果如何验收只随机打开几份文件看一眼很难发现系统性问题。建议把验收拆成三层。单规则测试每条规则都准备“应该删除”“不应该删除”和“边界情况”样本。例如页脚规则必须证明它不会删除正文中正常出现的“上次编辑者”讨论。分层抽样不要从全库完全随机抽样。应该按来源、格式、长度、语言和告警类型分层确保扫描 PDF、超长 Word、表格密集文档都被覆盖。质量指标这里可以记录真实统计但不要为了图表好看编数字。建议指标包括解析成功率与隔离率各规则命中文档数、命中次数字符保留率分布重复段落率结构保留率标题、表格、代码块、图片引用人工抽检通过率清洗版本变化带来的 Diff 数量。任何指标异常都要能下钻到文档 ID 和规则证据。七、双版本输出与失败隔离批任务最怕两件事某个坏文件拖垮整批任务以及任务中断后从头开始。Spring Batch 提供 Job 重启、Skip、Retry 和统计能力适合处理大规模文档任务。工程上可以把错误分成三类可重试错误临时网络、对象存储超时可跳过错误损坏文件、受密码保护文件、暂不支持的格式必须终止错误清洗规则配置损坏、目标存储 Schema 不兼容。ItemProcessor需要幂等因为回滚或重启可能让同一条目再次执行。可以用sourceHash pipelineVersion作为处理键键相同且产物完整时直接复用规则版本变化时才重新处理。八、面试时怎么讲这个问题不要只说“使用 Tika 完成文档解析”。更有信息量的表达是我把多格式入库拆成 Extract、Normalize、Clean、Validate、Export 五段。Tika/POI/PDFBox 只负责抽取业务清洗使用版本化规则链每次删除都记录证据验证失败的文档进入隔离区不继续向量化。批任务用源文件 Hash 和流水线版本保证幂等与可重跑。接下来面试官通常会追问如何处理扫描 PDF、如何防止误删、规则升级如何重跑、表格图片怎么保留。只要系统里确实有warnings、audit、quarantine和版本键这些问题都有明确答案。上线前检查表每种来源和格式都有代表性样本抽取层保存源文件 Hash、解析器版本和告警Normalize 和 CleaningRule 均满足幂等每条删除规则都有名称、版本、反例测试和证据标题、表格、代码块和图片引用有结构保留检查验证失败的文档会隔离不会进入向量库批任务支持断点、重启、跳过和失败统计机器版与人读版分开输出并共享同一 manifest指标可以下钻到文档和规则而不是只有总数。总结企业 RAG 的入库不是“把文件读取成字符串”而是一条数据生产线。解析器解决的是“能不能读”规范化解决“表示是否一致”清洗规则解决“哪些内容不属于知识”验证解决“结果是否可用”审计与隔离则保证系统出现问题时能够解释和重跑。向量检索的上限很大一部分在 Embedding 之前就已经决定。先把文档变成可信资产再谈分块、召回和 Rerank顺序不能反。参考资料Spring AI ETL PipelineApache TikaApache POI XWPF Quick GuideApache POI Text ExtractionApache PDFBox PDFTextStripperCommonMark Specificationcommonmark-javaSpring Batch IntroductionSpring Batch Item Processing