探矿文档清洗实战:从乱码到高精度RAG检索
1. 先别急着上RAG,探矿文档的"乱"得先说清楚
做探矿业务相关的工作,手里攒下的资料十有八九是这样的:20世纪90年代打印的老区调报告、某个地质队扫描存档的钻孔柱状图、Word里排版混乱的物化探异常查证记录、还有从地方矿政系统网页上复制下来的公告信息。平时找资料靠人工翻档案柜倒还凑合,但一旦想把这些资料喂给RAG(检索增强生成)系统做智能问答,第一步就卡住了——乱码。
我接手这个项目时,最大的感触是:探矿领域的文档问题不是简单的"格式解析",而是"文字压根读不出来"和"读出来了却检索不准"的叠加。先说乱码的三个来源,分清楚了,后面清洗才知道往哪儿使劲。
一是编码错乱。很多历史文档是早期DOS环境下用GB2312、GBK甚至Big5编码存的,拿到现代环境下用UTF-8解析必然乱码。探矿数据里还有一个特殊场景:野外采集仪器导出的数据文件,编码方式完全取决于仪器厂商的固件设置,同一个型号的仪器在不同年份导出的文件编码都可能不一样。
二是字体与字形缺失。地质报告里大量使用了生僻字和专有符号,比如"矽卡岩"的"矽"、"铷"元素的"铷"、地层符号里面带上下标的希腊字母。字体不支持时,PDF解析出来全是方框或者问号。更麻烦的是,不少老文档用了自造字(在TrueType时代之前嵌入的私有字形),现在的解析引擎根本识别不了。
三是扫描件没有文字层。这个在探矿行业尤其严重。早期很多野外记录本、化探采样单、矿权登记表根本没有电子版,只有扫描图片或者照片。这类文档在RAG里根本检索不了,必须先过OCR。
除了乱码,还有一个比乱码更隐蔽的问题:版式和语义错位。RAG的检索粒度是chunk,如果chunk切得不对,内容再干净也检索不到。举个例子,一张区域地质调查报告里的"地层特征表",跨页被切开后,上半页的表格标题在下一条chunk里完全找不到对应关系,卷帘查询的时候就会漏掉关键地层描述。
所以,"从乱码到高精度检索"这个目标,实际上要解决三层问题:文字层可读、结构层清晰、检索层精准。这三层是递进关系,前面的没做好,后面的再好也没用。
2. 清洗流水线的总体设计:先分类,再对症下药
探矿业务数据的类型可以粗分为四类:TXT日志或数据文件、Word报告、PDF扫描与电子版、网页公告或在线数据库内容。我之前犯过一个错误:一上来就写了一个统一的清洗脚本,结果Word里面的表格被干掉了,PDF里的坐标被识别成特征词,网页里的页眉页脚变成了噪声。后来我才意识到,RAG清洗不是"一道工序",而是一条按文档类型分支的流水线。
2.1 文档四分类与处理优先级
先做好分类和优先级排序,再动手处理。
| 类型 | 典型样例 | 核心问题 | 优先级 |
|---|---|---|---|
| TXT类 | 野外仪器导出数据、日志文件、老式目录文本 | 编码误判、定界符混乱 | 高(体量大) |
| Word类 | 地勘报告、设计书、年度工作总结 | 样式丢失、表格嵌套、宏污染 | 高(格式复杂) |
| PDF类 | 区调报告扫描件、矿权图件附件 | 扫描无文字层、版式错乱 | 中(解析难度大) |
| 网页类 | 矿政公告、网上公示、行业数据库页面 | 页面噪声多、正文提取不完整 | 中(时效性强) |
处理优先级不按文件格式来,而按"业务查询频率"来。比如野外仪器导出的TXT文件,工区内的钻孔编录、物探数据文件是日常检索的高频对象,必须先清洗;一些早期扫描报告,查阅频率低,可以排后面。优先级排序能避免把有限的时间耗在低频历史文档上。
2.2 清洗链路的分层架构
我的清洗链路大致分成四层。这个结构建议直接复用,因为它天然对应了RAG管道的上游节点。
第1层:格式识别与分类。不看扩展名,用Magic Number和内容嗅探判断真实格式。探矿行业很多文件扩展名是错的(比如把PDF改成TXT方便邮件传输),这一步不能省。
第2层:编码归一化。统一成UTF-8,为所有下游组件建立一致的数据入口。这里的难点不是编码转换本身,而是编码的自动检测准确率。
第3层:内容抽取与结构还原。按格式分支处理:TXT做文本清洗、Word做样式与表格抽取、PDF做双轨解析(文字版式和OCR)、网页做正文提取。抽取结果统一转成带元数据的Markdown或JSON结构。
第4层:切分与后处理。清洗好的文本按照业务语义做切分,给chunk打上来源标签(文件路径、页码、表格编号、图件坐标等),输出给向量化环节。
这四层链路里,最容易偷懒的是第4层,很多人直接把文本按固定字符数切了就完事,这在探矿场景里特别吃亏。地质报告里的一个重要矿化体描述可能跨越好几页,固定字符数切分等于把完整语义切碎。
3. 四种格式的清洗实操:TXT、Word、PDF和网页各走各的路
3.1 TXT的清洗:编码检测比正则替换重要得多
探矿领域接触的TXT文件,我一个一个说:
仪器导出文件。最常见的是化探样品分析数据、物探测量原始记录。这类文件的定界符不一定是制表符,可能是空格、逗号、竖线,甚至混合使用。清洗策略是:先按行读取,统计第一行的字符组成,判断是固定宽度还是定界符模式,再做字段映射。
老式档案文本。有些老资料是从印章版材料手工录入的,段落之间会有全角空格、连续换行、页眉重复,还有OCR造成的同音字错误。这类文本处理重点是:去重段落头部的统一格式、统一换行符、修正明显的OCR错误(比如把"钾"识别成"甲"、"岩心"识别成"言心")。
编码检测是TXT清洗里决定成败的一步。Python里老牌的chardet库效果还行,但速度慢且大文件容易误判;我后来换成了charset-normalizer,准确率和速度都更好。实测下来有个经验:不要盲目信任检测结果,先抽样检测,再结合文件内容特征交叉验证。比如一个文件名带"物探"的TXT,检测出来是ISO-8859-1,那大概率是误判——物探数据文件不太可能用西文编码存中文内容。此时要回退到GB18030尝试解码。还有一个小技巧:检测到编码不一致时,优先用GB18030而不是GBK,因为GB18030是GBK的超集,覆盖面更广。
3.2 Word的清洗:技术难点在样式还原和表格抽取
探矿业务的Word文档,大多是技术人员自己写的报告,格式非常「个性化」。字体混用、标题层级乱套、表格里套表格、嵌入对象(地图、剖面图)满天飞。对于RAG来说,Word清洗的核心不是追求视觉上和原文档一致,而是让文本内容和结构关系能被准确抽取。
我用的是python-docx做基础抽取,但有几个坑一定要避开:
坑一:正文内容和文本框内容分离。python-docx默认只读取body里的段落,而地质报告里的图签、图注经常放在文本框(TextBox)里。需要额外遍历document.part.package里的XML,把w:txbxContent节点里的文字也提取出来。
坑二:表格结构容易丢。探矿报告里的"地层简表"、"岩石化学分析结果表"简直是重灾区——单元格合并、跨页断行、列宽设置变形,抽取出来经常是乱的。我的处理方式是:对每一个表格对象,先读列宽信息,判断表格方向(横向表还是纵向表),再逐行逐单元格唯一标识后拼接。遇到合并单元格,需要先通过gridSpan和vMerge判断跨度,否则数据会错位。这一步没法完全自动化,我最终保留了人工抽查的兜底。
坑三:宏污染。从外部收来的Word文档经常带宏,python-docx读取时不会执行宏所以没影响,但如果直接用VBA或Docx2Txt工具批量转换,宏可能会触发安全警告拖慢进程。安全的做法是先把文档转成docx格式(如果还是老版本.doc),再用LibreOffice无头模式转成纯文本或中间格式。实测LibreOffice的docx转换效果比很多第三方库稳定,对表格和样式的保留率更高。
如果你处理的Word文档量特别大,建议直接用Pandoc做一次中间转换,数据流这样走:docx → pandoc → markdown(保留表格线)→ 清洗脚本 → 结构化的纯文本。Pandoc对Word表格的支持比直接读XML省事得多,但遇到病态文档(比如嵌套表格超过三层)也会崩,需要脚本兜底。
3.3 PDF的清洗:双轨解析是唯一出路
PDF是探矿文档里最令人头疼的格式。地质报告往往同时存在两种形态:文字版PDF(从Word直接转换而来)和扫描版PDF(从纸质档案扫描)。两者处理方式完全不同,必须分开。
文字版PDF:核心是坐标信息保留和阅读顺序还原。我强烈建议用PyMuPDF(fitz)而不是pdfplumber做RAG清洗的前置解析。PyMuPDF的优势是速度快、能拿到每个文本块的坐标框。拿到坐标后,先把页面按物理布局切分成区块(标题、正文、页脚、页眉),再按坐标从上到下、从左到右排序。这一步做不对,段落顺序是乱的——很多地质报告是双栏排版,默认按流式读取会把左栏前半部和右栏前半部拼到一起。
具体操作上,我的脚本逻辑是:遍历page.get_text("dict"),拿到每个block的bbox和text,按block的y0坐标排序,同一行的x0坐标再排序。遇到跨栏文本块,通过"列宽百分比"判断是否属于同一流。地学报告经常有"标题跨双栏、正文分两栏"的情况,需要额外判断当前block是否横跨页面中线的左右两侧——如果横跨则看作通栏段落,否则按左右分栏处理。这个逻辑让我处理几百份报告的准确率从70%提到了92%左右。
扫描版PDF:没有文字层,直接进OCR。这个在后面的章节单独展开说,因为探矿行业的OCR有特殊难点。
另有一个容易忽略的点:PDF里的图件(地质图、剖面图、柱状图)是以矢量图形存在的,PyMuPDF能提取出嵌入的图片,但图片本身没有语义。如果检索需求涉及图件里的内容(比如"查一下矿区南部有哪些断层"),单纯文本层是不够的。我目前的做法是把图件先抽取出来单独归档,图名、图例和相邻段落文本作为元数据关联,检索时需要语义匹配图件元数据来间接定位。这个方案不完美,但能应付大多数查询场景。
3.4 网页的清洗:正文提取要专门做
探矿业务的网页信息来源很杂:矿政管理部门的公告栏、地质资料馆的在线目录、行业协会的新闻页、学术数据库的摘要页。RAG如果要覆盖这些信源,清洗的重点是把正文和页面噪声分离。
我测试过几款主流正文提取工具,对探矿类网页的效果差异很大:
| 工具 | 效果 | 适用场景 |
|---|---|---|
| Trafilatura | 稳定、抽取速度快 | 政府公告类、新闻类页面 |
| Readability | 对长文效果好 | 学术文本、PDF化网页 |
| BeautifulSoup + 手写规则 | 高度可控 | 定制化站点抓取 |
Trafilatura是目前我用得最多的,它对页面的页脚版权信息、导航栏、广告区识别得比较干净,而且支持多种元数据抽取(标题、日期、作者、URL),这些元数据在RAG里可以作为有用的过滤条件。但Trafilatura对表格的处理比较弱,探矿公告里经常有"矿业权审批结果表"这类带表格的内容,提取出来会被压扁成一坨文本,检索精度受影响。我的处理方式是:先检查页面里是否有table标签,如果有,先单独把表格提出来做结构化,再对剩余正文做文本提取,最后把表格Markdown和正文拼接成一个文档块。
网页清洗还有一个探矿场景特有的需求:坐标信息的正则抽取。矿政公告里经常包含"矿区坐标范围"(类似"东经115°30′00″~115°45′00″")这样的关键数据。这类信息用通用清洗流程很容易被当作普通文本处理掉。我的做法是在清洗脚本里专门加了地理坐标正则,命中后自动转为十进制度数并附加到文档元数据里,这样后续按"工区范围"过滤检索结果时就能直接用了。
4. 图片型PDF和扫描件的OCR攻坚——探矿业务没法绕过这道坎
4.1 什么时候必须上OCR,以及识别前的关键预处理
判定一份PDF是否需要OCR,不能只看有没有文字层,还要抽查文字层的质量。有些扫描版PDF虽然做过OCR,但质量差,识别结果全是"口口口"或错字连篇,这种比没有文字层还危险——因为下游检索会把这些乱码当成有效内容,拉低整体精度。我的判断标准是:随机抽3页,按页提取文字,如果乱码字符比例超过5%,就整份文件重新走OCR。
真正的扫描件OCR,在探矿领域有几个通用方案解决不了的痛点:
痛点1:图表混排。地质图件上既有线划又有注记,OCR引擎常常把线条识别成文字笔画。我处理这类页面的策略是:先用OpenCV做连通域分析,检测大面积的线状连通域并填充为背景色,再送OCR。实测对地质剖面图的注记识别率有明显提升。
痛点2:手写体。野外记录本上的手写采样记录,目前没有完美的自动方案。折中的办法是:识别印刷体标题和表格框架,手写部分转成图片单独存储,同时生成"图片存在"的文本标记。这样可以保证RAG不会因为缺少这部分内容而误答,同时又不会因为OCR错误把手写内容识别得面目全非。
痛点3:分栏与阅读顺序。很多地质报告是双栏排版,OCR引擎默认按单栏流式输出。PaddleOCR提供了版面分析功能(PP-Structure),能输出每个文本块的位置坐标和版面顺序,这个对恢复双栏阅读顺序很有帮助。建议多花点时间在版面分析上,而不是直接调识别接口。
4.2 OCR流水线的具体搭建
我当前的OCR流程是:
- PDF按页转图片:用PyMuPDF将页面渲染成300 DPI的PNG。精度太低识别率上不去,太高文件体积暴涨。300 DPI对大部分印刷体中文够了,对密集表格可以按需调至400 DPI。
- 图像预处理:灰度化、降噪、二值化。这里有个技巧:灰度化后不要直接二值化,先用自适应阈值(如Otsu)看看效果,很多地质报告的纸张底色发黄,固定阈值会把背景噪声一股脑识别成文字。
- 版面分析:用PaddleOCR的det和rec模型分开调用,det用于文本检测,rec用于文字识别,中间加一个cls方向分类器处理倒置文字。注意:PaddleOCR默认输出是逐行文字,没有排版信息,一定要开启
use_angle_cls处理倾斜。 - 结果结构化:把识别出的每行文字和坐标位置对齐,再按上一节提到的坐标排序逻辑重建阅读顺序。
- 术语纠错:加载自定义地质术语词典做后处理。这一步是精度倍增器,后面单开一小节讲。
4.3 OCR错误修正:地质专业词汇表必须融入后处理
OCR识别的错字分布不是随机的,而是集中在笔画复杂的字、生僻字和专有名词上。地质资料里"砂岩"可能被识别成"砂石"、"泥岩"被识别成"泥言"、"花岗岩"被识别成"花冈岩"。这些错误如果只是个别字偏差,语义检索或许还能扛住;但"辉绿岩"识别成"灰绿岩"后,向量表征会偏离,检索精度下滑得非常明显。
我的修正方案分两层:
第一层:词典替换。整理一份常见地质术语的词频表,包含"矽卡岩""大理岩""玄武岩"等数百个高频词。OCR后处理时,对识别结果做模糊匹配,优先用编辑距离在1-2范围内的词典词替换。这层能修掉大约60%的术语错误。
第二层:特殊规则修复。探矿文本里有不少化学式、地层代号和同位素符号,OCR识别经常把下划线、上标丢掉。我写了正则,把"K2O"这一类的化学式、地层符号(如"J2"代表中侏罗统)抽出来,统一转成标准形式,比如将"J_2"明确标记为J2。注意不要强制性规范化所有符号,否则会把"J2(下段)"和"J2(上段)"混到一起,语义就丢了。
提示:OCR后处理词典要按工区/矿区定制。不同矿区的矿物名称差异很大,金矿和铁矿的岩性描述完全不是一回事。我在处理一个多金属矿区时,把当地最常见的30个地层和岩性术语单独抽出来做成短列表,修正效果比通用大词典好得多。
5. 清洗完还不行,我得让检索效果"被测出来"
清洗到这一层,很多人的任务就结束了。但高精度检索才是最终目标。清洗做得到不到位,得用检索指标来验证。我搭建的验证流程是这样的:
5.1 构建探矿语境的检索测试集
通用RAG评估用的公共数据集(比如Natural Questions)不适合探矿场景。我按照矿区实际情况,构造了三类测试问题:
- 实体型问题:"矿区内三叠系地层的主要岩性有哪些?"
- 关系型问题:"XX断裂带与矿化体的空间位置关系是什么?"
- 数值型问题:"某矿区伴生金品位最高达到多少?"
每类问题准备30条,每题预先从报告、表格或网页里标注出正确答案的位置(哪个文档、哪一页甚至哪一段)。这样在清洗后跑一遍检索,就能算出Top-K召回率和命中位置的中位数——这个中位数非常直观:清洗前可能命中在第30个chunk,清洗后提到第3个chunk,提升效果一目了然。
5.2 清洗质量不高时,重排序也救不了你
现在有不少RAG框架自带了重排序(Rerank)模块,可以先用向量召回候选,再用Cross-Encoder精排。有一个常见的误解是"清洗不行靠Rerank兜底"。我实测过:Rerank确实能把排名靠前的错误结果压下去,但如果清洗阶段把关键信息切碎或污染了,召回阶段根本找不回那个chunk,Rerank再强也无济于事。所以清洗验证和Rerank调优要分开做:先验证召回率,再加Rerank看最终准确率,哪一环都别跳过去。
5.3 中文分词的坑和命名实体保护
最后一个影响检索精度的问题是分词。探矿文本里大量专业术语在通用分词器里会被拆碎。"矽卡岩型铜矿"可能被拆成"矽""卡""岩型""铜矿",那就完全没法建立语义关联。解决方式有两个:
一个是给分词器加载用户自定义词典。Jieba这类分词器支持添加自定义词,把探矿术语词典加进去后,分词结果明显改善。另一个是评估向量模型对领域术语的敏感度。embedding模型如果是通用于新闻、百科的,对"蚀变带""激电异常""视电阻率"这些词的空间表征不够准确。条件允许时,收集几万条探矿报告文本做继续预训练或微调,效果提升非常明显。我个人的体会是:先别急着换大模型,把分词语料和领域embedding做扎实,提升的幅度比你想象的大得多。
6. 走过的弯路:三个容易翻车的细节
6.1 编码误判比不判还糟
之前处理一批物探仪器导出的TXT时,检测出来是UTF-16,我直接转换了。下游确实不再乱码,但坐标数据全乱套了——因为原始数据是GBK编码,其中一部分中文注释被误判成UTF-16后,二进制层面发生了位移。后来我加了一个硬规则:凡是检测结果与实际内容明显矛盾(比如文件名含中文但检测为西文编码)时,自动尝试所有候选编码,并对每种结果做中文字符占比评分,选最高的那个。这个规则上线后,误判率降到了3%以内。
6.2 表格切碎后的上下文丢失
探矿报告里大表很多,动态切分chunk时表头被隔离到上一个chunk,表体到了下一个chunk。检索"某地层的地球化学特征"时,向量匹配到的是表体,但表头(列名)在另一个chunk里,导致系统不知道这些数值对应的是"Au(金)含量"还是"Ag(银)含量"。我最后的解决方法是:切分时把表格整体作为一个chunk,不做内部切分。如果表格超过chunk最大长度,就按行拆成两块,但每块前面都重复一遍表头。这个操作虽然费一点存储,但检索精度确实有提升。
6.3 清洗过度:把格式信息全删了反而不利于检索
展示一个我自己踩过的坑:刚开始写清洗脚本时,我追求"纯净文本",把段落缩进、层级的序号、表格线、页眉页脚全部删除。结果发现一些查询(比如"第三章讲了什么")召回时找不到层级信息。后来调整思路:清洗的目标不是把文本变干净,而是把信息变成可检索的结构。章节标题和层级编号要保留,因为这些是语义锚点;页眉页脚这种与正文无关的重复信息要删,因为它们会干扰embedding的表征。区分这两者的关键是:删除的内容是否承载着"定位信息"——能帮检索定位到"这是哪个章节、属于哪张表"的内容,都值得保留。
一些最后想说的话
这套清洗链路从搭建到调优我走了将近三个月,其中大概有一半时间都花在数据探查和踩坑修复上。探矿业务的资料特殊性决定了它没法直接用通用RAG流程套完就跑。你如果也要处理类似场景,我建议从优先级最高的那10份文档开始,把清洗、切分、检索、评估这个闭环先跑通,再逐步扩大清洗范围——不要一上来就追求全量清洗。清洗是一个持续迭代的过程,不是一次性预处理任务。
还有一点想提醒你:市面上有不少号称"全自动文档解析"的RAG工具,它们对通用办公文档效果不错,但对探矿这种专业性强、历史资料杂、扫描件占比高的场景,还是要自己掌握清洗的控制权。工具可以帮忙做初筛,但最终的清洗规则、术语表、切分策略,必须基于对业务文档的深入理解来定制。这也是"从乱码到高精度检索"这条路上,任何框架都替代不了的部分。