1. 探矿业务文档处理的真实困境
1.1 为什么探矿场景下的RAG落地格外棘手
做过地质勘探、矿产评估这类业务的朋友应该都有体会,我们每天打交道的资料跟互联网上那种规整的文本完全是两码事。一个中型探矿项目从立项到结题,积累下来的文档动辄几百上千份,格式横跨TXT、Word、PDF、扫描件、网页存档,内容里夹杂着大量专业符号、坐标数据、化验表格、钻孔柱状图说明,还有各种手写批注的翻拍件。我最早接触这块是在一个铜多金属矿的储量核实项目上,当时想把历史资料做成可检索的知识库,结果第一版跑出来的效果惨不忍睹——检索“ZK1203孔见矿深度”,返回的却是一堆无关的化验报告片段,甚至把“1203”这个数字拆得七零八落。
这就是探矿业务做RAG(检索增强生成)最典型的痛点:原始文档的噪声密度极高,而业务对检索精度的要求又极其苛刻。普通场景下RAG召回个大概意思就行,但探矿不一样,一个品位数据、一个标高数值、一个地层代号,错一位数可能就是完全不同的结论。所以清洗这一步,在探矿RAG里不是“锦上添花”,而是决定整个知识库能不能用的生死线。
1.2 四类文档各自的“脏”法完全不同
我后来把踩过的坑梳理了一遍,发现TXT、Word、PDF、网页这四类来源,脏的方式各有各的特点,用一套清洗逻辑去套必然翻车。
TXT看起来最干净,实际上最阴险。探矿业务里的TXT很多是从老式地质软件导出的,编码混乱是家常便饭,GBK、GB2312、UTF-8甚至带BOM的混在一起,直接读进来就是满屏乱码。更麻烦的是这类文件里经常用空格或制表符做列对齐,比如钻孔编录表,一旦按行切分再按空格split,遇到某个字段为空就会整体错位。
Word的问题在于“看不见的结构”。表面上是段落和表格,底层XML里藏着一堆修订记录、批注、域代码、公式对象。我遇到过一份储量报告,正文里“品位”两个字在修订模式下被改过三次,直接提取文本会把删除线和插入内容全混在一起,检索出来的句子逻辑都是矛盾的。
PDF是重灾区中的重灾区。探矿资料里大量PDF是扫描件,本质是图片,不做OCR根本提不出字。就算是电子版PDF,双栏排版、跨页表格、页眉页脚、图注混排也会让文本抽取顺序彻底乱掉。我印象最深的一份详查报告,双栏排版下抽取出来的文字是“左栏第一行+右栏第一行+左栏第二行”这种交错顺序,读起来像天书。
网页相对好一点,但探矿行业的网页很多是老式政府或地勘单位网站,表格用table嵌套table,还有大量JavaScript动态渲染的内容,直接requests抓下来是空壳。
1.3 这篇内容适合谁看
如果你正在做地质、矿业、勘探相关的知识库或智能问答系统,被文档清洗卡住了,那这篇就是写给你的。我会把四类文档的清洗思路、具体参数、踩坑经验完整拆开讲,包括编码探测、OCR预处理、表格还原、分块策略这些实操细节。即使你不是探矿行业,只要你的文档同样“脏且专业”,这套方法论也能直接迁移。下面我按“整体设计—分类清洗—实操流程—问题排查”的顺序展开,尽量把每一步的“为什么”讲透。
2. 整体清洗架构的设计思路
2.1 先分类再清洗,而不是一把梭
我见过不少团队上来就写一个通用loader,所有文件丢进去统一转文本,结果就是TXT的乱码、Word的修订、PDF的错序全混在一起,后面根本没法针对性修复。正确的做法是先按格式分流,每类走独立的清洗管道,最后在统一的分块层汇合。
这么设计的原因很直接:不同格式的“病灶”不同,修复手段也完全不同。编码问题只在TXT和部分网页里出现,修订记录只在Word里出现,OCR只在扫描PDF里出现。如果混在一起处理,你要么写一堆if-else把代码搞得面目全非,要么就得用过度通用的方案牺牲精度。分流之后,每条管道只关心自己那类问题,逻辑清晰,调试也方便。
具体分流我一般这么分:TXT和网页归为“纯文本类”,重点解决编码和结构;Word归为“半结构化类”,重点解决修订和表格;PDF单独一类,先判断是电子版还是扫描版,再决定走文本抽取还是OCR。
2.2 清洗的目标不是“干净”,而是“保真”
这里有个认知误区要纠正。很多人以为清洗就是把文本洗得越干净越好,把特殊符号、数字、单位全删掉。但在探矿业务里,保真比干净重要得多。一个“Cu 0.85%”如果被洗成“Cu”,品位信息就丢了;一个坐标“E 102°34′56″”如果被洗成“E 1023456”,度分秒结构就没了。
所以我的清洗原则是:只去除明确的噪声(页眉页脚、乱码字符、重复空行),保留所有可能承载业务语义的符号、数字、单位、代号。判断标准很简单——如果这个字符删掉后,地质工程师看了会觉得信息缺失,那就不能删。这个原则贯穿后面所有环节。
2.3 分块策略要跟着业务语义走
清洗完的文本怎么切块,直接决定检索精度。通用做法是按固定字数切,比如512字一块,但探矿文档这么切必然出问题。一份钻孔柱状图说明可能就200字,但它是完整语义单元;一份化验报告表格可能3000字,但按512切会把一个样品的数据切到两块里。
我的做法是按文档结构切,而不是按字数切。具体来说:识别出标题层级、表格边界、段落边界,优先在结构边界处切分,只有当单个结构单元超过阈值(我一般设800-1000字)时才在内部按句子切。这样切出来的块,每一块都是一个相对完整的业务语义单元,检索时命中率明显更高。
提示:分块阈值不要拍脑袋定,建议拿20-30份典型文档做实验,分别用300、500、800、1000字切,看检索命中率的变化曲线,选拐点位置。
3. 四类文档的清洗实操要点
3.1 TXT文档:编码探测与列对齐修复
TXT清洗第一步永远是编码探测。我不用chardet那种纯统计的方法,因为探矿TXT里专业符号多,统计容易误判。我的做法是优先尝试UTF-8,失败则用GB18030兜底,因为GB18030兼容GBK和GB2312,覆盖了绝大多数老式地质软件的输出。
def read_txt_safe(path): for enc in ['utf-8-sig', 'utf-8', 'gb18030']: try: with open(path, 'r', encoding=enc) as f: return f.read(), enc except UnicodeDecodeError: continue # 最后兜底,忽略错误字符 with open(path, 'r', encoding='gb18030', errors='ignore') as f: return f.read(), 'gb18030-ignore'编码解决后是列对齐问题。探矿TXT里大量用空格对齐的表格,比如:
孔号 深度 岩性 品位 ZK1203 120.5 黄铜矿 0.85 ZK1204 98.2 黄铁矿 0.32直接按空格split,遇到“黄铜矿”这种三字岩性和“黄铁矿”三字岩性还好,但如果某行岩性为空,就会错位。我的处理方式是先按连续两个以上空格切分,再对结果做长度校验,如果某行列数跟表头不一致,就标记为异常行人工复核,而不是强行对齐。
3.2 Word文档:修订清理与表格还原
Word清洗的核心是先把修订和批注处理掉,再提正文。python-docx默认读的是最终状态,但修订记录如果没接受,读出来的文本可能包含被删除的内容。稳妥做法是用docx库遍历w:ins和w:del节点,只保留插入内容,丢弃删除内容。
from docx import Document from docx.oxml.ns import qn def extract_word_clean(path): doc = Document(path) texts = [] for para in doc.paragraphs: # 跳过修订删除的内容 if para._element.findall('.//' + qn('w:del')): continue texts.append(para.text) return '\n'.join(texts)表格还原是另一个重点。探矿Word里的表格经常有合并单元格,直接按行列读会得到一堆None。我的做法是先读表格的XML结构,识别合并单元格的跨度,再重建二维数组。对于跨页表格,还要判断续表关系,把同一张表的多个片段合并。
注意:Word里的公式对象(OMML)不要试图转成文本,直接保留为占位符如
[FORMULA],因为公式转文本几乎必然失真,后续检索时公式本身也不是主要检索目标。
3.3 PDF文档:电子版与扫描版的分流处理
PDF第一步是判断类型。我的判断逻辑是:用PyMuPDF抽取前3页文本,如果平均每页有效字符数少于50,就判定为扫描版,走OCR;否则走文本抽取。
电子版PDF的文本抽取,关键是处理阅读顺序。PyMuPDF的get_text("blocks")能返回文本块及其坐标,我一般按坐标排序:先按y坐标从上到下,同一行内按x坐标从左到右。对于双栏排版,还要先判断页面是单栏还是双栏——如果文本块的x坐标明显分成两簇,就按栏分别排序再拼接。
import fitz def extract_pdf_text(path): doc = fitz.open(path) full_text = [] for page in doc: blocks = page.get_text("blocks") # 按y坐标排序,同一行按x排序 blocks.sort(key=lambda b: (round(b[1]), b[0])) for b in blocks: full_text.append(b[4]) return '\n'.join(full_text)扫描版PDF走OCR,我一般用PaddleOCR,因为对中文和表格的识别效果比Tesseract好不少。OCR前要做图像预处理:灰度化、二值化、去噪、纠偏。探矿扫描件经常有倾斜,纠偏这一步不能省,否则OCR准确率会掉一大截。
3.4 网页文档:动态渲染与表格提取
探矿行业很多资料在政府或地勘单位网站上,这些网站大量使用老式技术,表格嵌套严重。我的抓取流程是:先用requests试抓,如果正文为空或明显不完整,再上Playwright做动态渲染。
表格提取是网页清洗的难点。我一般用BeautifulSoup定位table,然后逐行逐单元格提取,遇到rowspan和colspan要展开成规整的二维数组。对于嵌套表格,只提取最内层的叶子表格,外层表格作为容器忽略。
from bs4 import BeautifulSoup def extract_web_tables(html): soup = BeautifulSoup(html, 'lxml') tables = [] for table in soup.find_all('table'): # 跳过嵌套表格的外层 if table.find('table'): continue rows = [] for tr in table.find_all('tr'): cells = [td.get_text(strip=True) for td in tr.find_all(['td', 'th'])] rows.append(cells) tables.append(rows) return tables4. 完整实操流程与参数配置
4.1 从原始文件到可检索块的完整管道
把前面各环节串起来,一条完整的清洗管道是这样的:
- 文件分流:按扩展名和MIME类型分到TXT、Word、PDF、网页四条管道。
- 格式解析:每条管道用对应的解析器提取原始文本和结构信息。
- 噪声去除:统一去除页眉页脚、重复空行、乱码字符、修订标记。
- 结构还原:表格转二维数组,标题识别层级,段落合并。
- 语义分块:按结构边界切分,超长单元内部按句子切。
- 元数据附加:给每块打上来源文件、页码、章节、文档类型标签。
- 质量校验:抽样检查块内容,统计异常块比例。
4.2 关键参数的计算与选择
分块阈值我前面说设800-1000字,这个数怎么来的?我做过一组实验,拿50份探矿报告,分别用不同阈值切块,然后用同一组查询测召回率。结果300字时召回率高但噪声大,1000字时噪声小但漏召回多,800字左右是召回率和精确率的平衡点。当然这个数跟你的文档特点有关,建议自己测。
OCR的DPI设置也有讲究。太低(150以下)小字识别不清,太高(400以上)处理慢且噪点放大。我一般设300 DPI,这是扫描文档OCR的经典值,兼顾清晰度和速度。
表格还原时的合并单元格判断,我用的是坐标重叠法:如果两个单元格的边界框有重叠,就判定为合并。这个方法比单纯看rowspan/colspan更鲁棒,因为有些PDF转出来的表格没有明确的合并标记,只能靠坐标推断。
4.3 实操现场记录:一次真实的清洗调试
我拿一个实际项目片段记录一下调试过程。当时有一份《某矿区详查报告》PDF,电子版,双栏排版。第一版抽取出来文本顺序全乱,检索“矿体倾角”返回的是不相干的段落。
排查发现是阅读顺序问题。我改成先按x坐标聚类判断栏数,再分栏排序。改完后文本顺序正常了,但表格还是乱的——跨页表格被拆成两个独立表格。我又加了跨页表格合并逻辑:如果上一页最后一个表格和下一页第一个表格的列数相同、表头相似,就合并。
最后检索“矿体倾角35°”能准确命中目标段落,召回率从最初的40%提升到92%。这个过程花了大概两天,但值得,因为清洗质量直接决定了后面所有环节的上限。
5. 常见问题与排查技巧实录
5.1 乱码问题的系统排查
乱码是最高频的问题,但成因有好几种,得分开治。我整理了一个速查表:
| 现象 | 可能原因 | 排查方法 | 解决手段 |
|---|---|---|---|
| 中文全变问号 | 编码不兼容 | 试GB18030读取 | 换编码重读 |
| 部分字符乱码 | 混合编码 | 分段检测编码 | 分段解码后拼接 |
| 出现\ufeff | UTF-8 BOM | 检查文件头 | 用utf-8-sig |
| 数字字母正常中文乱 | GBK被当UTF-8 | 看字节模式 | 强制GB18030 |
我的经验是,探矿TXT里GB18030能解决90%以上的乱码,剩下10%多半是文件本身损坏或用了罕见编码,这种直接标记异常人工处理,不要硬解。
5.2 表格错位的定位与修复
表格错位表现为行列对不上、数据串行。排查时我一般先打印前10行原始文本,肉眼看对齐方式,再决定切分策略。如果是空格对齐,用连续空格切;如果是制表符,用\t切;如果是固定宽度,按字符位置切。
修复时有个技巧:先提取表头,确定列数和列名,再逐行校验。如果某行列数跟表头不一致,就把这行单独拎出来,用模糊匹配猜它属于哪列,猜不准就标记人工复核。千万别强行对齐,强行对齐产生的错误数据比缺失更危险。
5.3 检索精度上不去的排查思路
如果清洗做完了但检索精度还是不行,按这个顺序排查:
- 分块是否合理:打印几个块的完整内容,看是否语义完整。
- 元数据是否齐全:检索时能否按来源、章节过滤。
- 嵌入模型是否适配:探矿术语多,通用嵌入模型可能效果差,考虑微调或换领域模型。
- 查询是否被正确解析:用户问“ZK1203见矿深度”,查询里是否保留了“ZK1203”这个关键实体。
我遇到过最隐蔽的一个问题,是分块时把“ZK1203”和后面的数字切到了两块里,导致检索时实体和数值分离。后来改成实体和其属性值必须同块,问题就解决了。
提示:探矿业务里,孔号、样品号、坐标、品位这些实体,一定要保证和它们的属性值在同一块内,这是硬约束。
6. 我个人的几条实操心得
清洗这件事,工具和代码都是次要的,对业务的理解才是核心。我刚开始做的时候,总想着用通用方案一把梭,结果反复返工。后来我逼着自己去读了几十份探矿报告,搞清楚了钻孔编录、储量估算、化验分析这些文档的结构规律,清洗逻辑自然就清晰了。
另一个体会是,清洗质量要用检索效果来验证,而不是用“看起来干净”来判断。我现在的习惯是每改一版清洗逻辑,就拿一组标准查询测召回率和精确率,用数据说话。有时候文本看起来更乱了,但检索效果反而更好,因为保留了业务语义。
最后说个具体的:探矿文档里的单位符号(%、‰、m、°)和数字之间的空格,千万别删。我试过删掉空格统一格式,结果“Cu 0.85%”变成“Cu0.85%”,嵌入模型反而把它当成一个整体token,检索“Cu品位”时命中率下降。保留原始空格,让模型自己去学,效果更稳。
这套清洗管道我后来在三个探矿项目上复用,基本框架没大改,只是针对不同矿种的文档特点微调了参数。如果你也在做类似的事,建议先把TXT和Word这两类相对简单的跑通,再啃PDF和网页这两块硬骨头,循序渐进比一上来就全量处理要稳得多。