做探矿行业的RAG知识库,最难的不是选框架,也不是调向量模型,而是数据清洗这关。国内矿产勘查业务积累的文档,绝大多数是TXT、Word、PDF和网页抓取的混合体,编码混乱、格式残缺、扫描件泛滥,一个“乱码”问题就能让整套检索系统白干。这篇文章把我实际处理这套流程的经验完整拆开,从乱码修复、PDF解析、表格保真到切分策略和元数据设计,一步步说清楚如何把一堆“电子垃圾”变成大模型能用的高精度语料。适合正在搭RAG、被文档清洗折磨的工程师和地勘行业数字化从业者。
1. 探矿业务的RAG为什么难做:数据现状与核心挑战
1.1 探矿数据的典型分布与格式乱象
矿产勘查领域有个很现实的问题:大量历史资料数字化程度极低,但又恰好是知识检索价值最高的内容。一份普查报告、钻孔编录、化验报告,往往决定着一个矿区下一步的探矿方向。这些资料在项目归档时通常保留了最初的数字化形式,于是就有了TXT、Word、PDF、网页抓取文本并存的局面。
我参与过几个矿区知识库建设项目,盘点下来数据源大致是这么分布的:地质勘查报告(Word和PDF都有,PDF往往是最终汇交版)、钻孔柱状图和编录表(Excel转出的PDF居多,也有保留了原始TXT的)、化验室数据(CSV、Excel、老式TXT,以及一部分扫描件)、行业规范和地方标准(网页、政府公告、协会文件)、学术论文(PDF为主,还有知网之类的网页摘要)。这些数据混在一起,对检索系统来说不是“格式丰富”,而是灾难。
更麻烦的是,不少数据是从老旧的国土资源档案系统、协作单位下发材料、甚至扫描复印再转换的中转站里出来的。编码方面,GB2312、GBK、UTF-8、UTF-16混着来,早期还有一些用区位码和自造字的地质符号。格式结构上,很多PDF没有文字层,就是扫描图片;很多Word文档里嵌着AutoCAD导出的图件、MapGIS的表格,解析出来是一堆乱码或空白。我记得第一次跑数据预览时,抽样10份文档,有7份出现过不同程度的乱码,这比例说明清洗环节绝对不能省。
传统做法里,勘探人员搜索资料靠的是在本地文件夹里用文件名搜,稍微好一点的有企业网盘全文检索。但这些方案对“根据成矿地质条件找相似矿区案例”“对比某层位在不同钻孔中的见矿情况”这类语义检索需求完全无能为力。RAG正是来解决这个问题的,但RAG的效果严重依赖于数据质量,脏数据进去,出来的一定是幻觉答案。这也是为什么我认为对探矿业务来说,数据清洗比模型选型更值得投入精力。
1.2 乱码的三大源头:编码、字体内嵌、扫描件
很多人一看到乱码就以为是编码问题,年少无知的我也是这么想的,直到排查一个PDF文档,发现里面复制的文字全是“口口口”,才知道事情没那么简单。探矿业务里的乱码,实际有三大类来源,修法完全不同。
第一类是纯编码错误。打开TXT文件直接看到“�����”或“鎴愮晫”这类字符,几乎可以断定是文件实际编码和读取方式不一致。探矿行业的老文件最常见的组合是GBK编码但被当成UTF-8打开,或者反过来。用编辑器换编码重新打开,一般能直接修复。这类问题最简单,但覆盖不了所有场景,尤其是涉及网页抓取的文本,编码判断更是五花八门。
第二类是PDF字体子集化和内嵌字体导致的内容乱码或无法复制。勘查报告的PDF为了高压缩比,制作者经常只嵌入部分字体子集,加上中文字体缺少Unicode映射表,文字看起来没问题,但复制出来是乱码甚至空白。这类PDF不是不能解,而是需要用工具重排或用OCR识别。很多地质图件的PDF还有另外一个问题,里面的数字、特殊符号(如铜元素符号下方的品位数字、岩性代号)用了非标字体,用通用库解析直接就丢了。
第三类是扫描件PDF。老矿区报告、纸质的矿石鉴定书、手写编录卡片,这些都是扫描图。没有文字层,任何PDF解析库都白搭,只能OCR。地勘专业OCR有个额外的麻烦——大量的地质术语、代号、生僻汉字、表格结构,通用OCR引擎识别效果很差,经常把“中侏罗统”识别成“中株罗统”、“ZK301”识别成“2K301”。后面我会详细讲针对这些字段的优化方案。
明白了乱码从哪来,清洗策略才不会“一刀切”。我见过有人用同一个编码探测脚本处理所有文件,结果TXT修好了,PDF反而更乱。正确的思路是先把格式分类、乱码类型识别出来,再逐个对症下药。
2. 清洗没有一个万能脚本:分层设计思路
2.1 字节层修复:编码识别与还原
数据清洗要分层,第一层是字节层。所有文件在解析前,先做无损的编码修复,这步做好了,后面少踩一半的坑。
拿最麻烦的老TXT文件举例,我的处理流程是这样的。第一步,用二进制的形式读取文件头,判断有没有BOM头,有BOM的直接按BOM指定的编码读,没有BOM的进入第二步。第二步,用charset-normalizer或chardet做编码探测,但这里不要完全相信探测结果,因为地勘术语里有大量生僻词和特殊符号,探测置信度经常被拉低。我的做法是并行跑UTF-8、GBK、GB18030三种解码器,哪一个能完整解码且乱码占比最低,就选哪个。GB18030是个好东西,它基本覆盖了所有中文字符,老文件用这个兜底很少出错。
这里提一个细节,很多老TXT里的地质符号,比如“▽+Fe”“Cu>0.5%”这类,其实是符号字体在不同编码间的错误映射,就算你解码对了,符号本身也不对。这种情况不能靠编码修复,必须走规则替换。我维护了一个地质特殊符号替换表,把全角半角、上下标数字、常见元素符号的乱码形态统一映射回标准写法。刚开始建表很痛苦,但积累一段时间以后,处理新文件的效率高得出奇。
代码层面上,我常用的流程是这样:
import charset_normalizer from pathlib import Path def repair_txt_content(path): raw = Path(path).read_bytes() if raw.startswith(b'\xef\xbb\xbf'): return raw.decode('utf-8-sig') if raw.startswith(b'\xff\xfe') or raw.startswith(b'\xfe\xff'): return raw.decode('utf-16') best = None for enc in ['utf-8', 'gbk', 'gb18030', 'big5']: try: text = raw.decode(enc) if best is None: best = (enc, text) # 统计替换字符和空白的比例,越低越好 garbage_ratio = (text.count('\ufffd') + text.count('\x00')) / max(len(text), 1) if garbage_ratio == 0: return text except UnicodeDecodeError: continue return best[1] if best else raw.decode('utf-8', errors='ignore')这只是一个基础框架,实际业务里还得考虑GBK解码成功但内容依然错乱的情况,这时可以比较中文字符比例、常见量词和地质术语的出现频次来二次校验。字节层修好的文件,最好统一转成UTF-8纯文本中间格式存档,后续所有解析都基于这个中间格式,不再碰原始二进制,这个习惯帮我省了无数重复工作量。
2.2 格式层净化:PDF与Word的有损与无损提取
字节层搞定了TXT,但PDF和Word又是另一套逻辑。这里要明确一个概念:提取PDF文本有两种思路,无损提取和OCR重识别。无损提取基于PDF内部的文字层信息,速度快、保真度高,但面对扫描件和字体子集化乱码无能为力;OCR能救扫描件,但速度和识别准确性都有代价,而且对表格结构破坏较大。我在探矿项目里的选择标准很简单:先尝试无损提取,检测到乱码占比超过阈值或文字量不正常,再降级到OCR。
无损提取我首推PyMuPDF(fitz),它比pdfplumber在这个场景下更有优势,因为PyMuPDF能直接拿到字符级的字体信息和坐标。这样我能写规则去判断某个字符是不是用了非标字体,甚至能按坐标把文字块和表格重新拼起来。pdfplumber强在表格提取的“文本-坐标”对齐,但速度确实慢,处理几百页的勘查报告会让人崩溃。
Word文档的提取主要有两条路:python-docx主要处理纯文本和段落样式,mammoth的优势在于把Word转成干净的HTML,结构保留更好。探矿报告里的Word大多是文本+表格+插图混排,我一般先用mammoth转HTML,再解析HTML里的表格和段落,这样比python-docx直接遍历段落更不容易丢表格边框信息。但不管用哪个库,都要注意一个坑:Word里嵌的公式(OLE对象或MathType)解析出来是图片或域代码,直接提取就是一团乱麻,需要单独把这些对象标记为“待处理”,后续用公式识别或人工标注兜底。
不管哪种格式,我都会在提取后做一次正则清洗,把页眉页脚、页码、目录、重复的换行符、全角空格、零宽字符全部干掉。探矿报告页面很长,页码信息混入正文会让切分后的文本块带上大量无关数字,直接影响向量检索的准确性。这些看似无关紧要的“垃圾字符”,往往是高精度检索里的隐形杀手。
3. 探矿场景的特殊加工:表格、坐标与编号
3.1 化验表格的保真清洗:行列、表头与品位的千年老坑
探矿业务里,化验分析表、钻孔编录表、储量计算表是使用频率最高的数据,但它们恰恰是格式清洗里最折磨人的部分。原因也简单:表格信息高度依赖行列结构,任何一步提取顺序错乱,数据就从“表”变成了“一串无意义的数字”。而且这类表经常跨页,表头重复出现,合并单元格遍布,老PDF里还有一大堆手动画的线条,解析逻辑稍有不慎就全错。
我的定价原则是:表格优先走结构解析,不要先转纯文本。对整个PDF页先做版面分析,识别出表格区域,再用Camelot或pdfplumber的表格抽取功能按行列切分。在当前项目里,我用的是PyMuPDF提取文本块坐标,再用横纵线的位置数据判断单元格边界,最后自己拼表——虽然代码量更大,但对于复杂表头的控制力是现成库没法比的。
对于跨页重复表头,要在拼接时按列名对齐,去重表头行。对于合并单元格,要按坐标把左上角的值广播到其他单元格,或者根据值的空置模式自动填充。化验品位数据里的“Au 3.5g/t”“Cu 0.42%”“品位区间”“累计厚度”这类字段,要用正则和领域词表做二次校验。我遇到过TXT里的“0.5%”被OCR成“0.5%o”,不做字段规范化,检索时这类数据几乎必然被漏召回。
表格清洗的最终输出,我一般会转换成Markdown格式或JSON Lines。JSON结构可以在元数据里直接存列名、单位、矿区编号、钻孔编号,对后续检索和过滤帮助极大。千万不要只转成一段话让大模型去猜,表格里的结构信息就是要显式保留的,这是探矿RAG精度的关键。
3.2 坐标、孔号与标准术语的规范化控制
探矿领域的检索效果好不好,很大程度上取决于实体规范性。同一件事在不同文档里有几十种写法,比如同一个钻孔“ZK301”,有的文件写作“ZK0301”,有的写作“钻孔301”,编号规则不一致,检索时“zk301”和“钻孔301”在向量空间里的距离,远没有你想象的那么近。如果不统一,系统就没办法在高精度检索里保证召回一致性,最终呈现出来的结果是“有一套知识库但用不上”。
坐标类是重灾区。一份报告里可能同时存在经纬度、6度带高斯坐标、3度带高斯坐标,甚至还有ASCII码格式的老坐标。如果不对坐标做标准化和去重,检索“某矿区的见矿深度区间”时,系统会返回一堆坐标格式不同的破碎片段,用户根本没法用。我这里说的去重,不只是完全重复的坐标值,还包括相邻坐标点、重复排列的同源数据。清洗时要先把坐标统一格式,再按一定的容差半径做空间聚类去重,只保留代表性数据。
术语规范这块,我的做法是建一张“地质术语标准词表”,把异名映射表导入清洗流程。比如“矿化带”和“蚀变带”在不同专家报告里混用,检索时同时命中两种写法。靠向量模型泛化能解决一部分,但想达到高精度,还是要显式做一次同义词归一,把语料里出现的异名统一替换成标准词,并保留原词在元数据的“alisa”字段里。这样做还能顺带解决部分OCR识别错误——把“中株罗统”纠正回“中侏罗统”。
当然,术语和编号的规范化不能盲目进行。矿区编号可能跨系统冲突,地质年代名词在不同学派有不同定义,这类问题需要领域专家参与验证。我遇到过把“ZK01”和“ZK1”误判为同一钻孔,但实际是两个完全不同位置的钻孔的情况,人工复核机制必须保留,机器清洗只能做“确定性高”的自动替换,模糊的情况一律标记待人工确认。
4. 清洗之后的切分与入库:高精度检索的前提
4.1 探矿文本的切分策略:不再迷信固定长度
清洗完的文本,在进入向量库之前还有一个关键环节——切分。早期做RAG时大家特别喜欢固定长度切分,比如每512个token切一块,重合128个token。但探矿领域很快会发现这样不行:钻孔编录的章节长度差异极大,按固定长度切会把同一段“矿体描述”拦腰截断,把“见矿深度”和“岩性描述”硬拆成两半,检索时大模型根本凑不齐完整上下文。
我在探矿场景推荐的切分策略是“结构优先+语义回退”。优先按照文档的章节层级(如“第3章 矿区地质”“3.2 矿体特征”“3.2.1 蚀变带”)切分,解析Word/Pdf时就把标题层级同步提取出来,切分器直接按Heading级别断开。这样切出来的块,天然就对应了一个完整的知识和上下文单元。对于没有清晰标题结构的文本,再用滑动窗口兜底,但窗口大小我会根据文档类型调整,勘查报告用1024个token,网页摘要用512个token,精度会好很多。
另外,切分时要保留“段落完整”。固定长度切分最大的问题是段落切断,但段落恰好是地质文本描述的最小语义单元,河流环境下沉积的粒度变化、矿体形态特征、蚀变分带描述,往往在同一个段落内才有完整逻辑。我在切分后增加了一个校验逻辑:每个块必须以完整的句子结尾,句子被切断的块自动多接一段,宁多勿缺,这样大模型拿到手的上下文是完整的、可推理的。
4.2 块结构设计与元数据注入:让向量检索带GPS
如果说切分决定了检索的“最小有效单元”,那元数据就决定了检索的“筛选口径”。探矿RAG里有一个核心场景:用户问“红旗矿区 ZK301 钻孔的见矿深度是多少”,系统如果只靠向量相似度做检索,会把“红旗矿区其他钻孔”“ZK301的岩性描述”一起捞上来,排序一乱,答案就废了。所以我在入库时会对每个块注入一批元数据,把文档级、章节级、区块级的信息通通挂到每个块头上。
具体来说,每个块至少要有这些字段:矿区名称、勘查阶段(普查/详查/勘探)、钻孔编号、坐标范围、地质年代、岩性分类、矿产类型、文本来源格式、原始页码、文档路径。有了这些元数据,检索阶段就可以先做结构化过滤,再走向量召回。典型的查询“ZK301在哪个深度见到金矿体”,先用过滤器把钻孔编号限定为“ZK301”,再在剩余块里跑向量,准确率会显著提升。
这里还有一个向量检索之外的设计:关键词倒排索引和向量索引并存。地质术语里有大量专有名词比如“矽卡岩型”“浅成低温热液型”,向量模型在建索引时未必能很好地捕捉到这些低频词的重要性,但BM25倒排索引对精确匹配非常敏感。我用ES或专门的混合检索服务把BM25和向量检索的结合在一起,做RAGFusion式排序,那种“高精度检索”所要求的精确性和泛化性就都有了。
5. 实测效果与踩坑记录
5.1 清洗前后的检索效果对比:从“答非所问”到“精准命中”
说一千道一万,数据清洗的效果最终要用检索质量来证明。我用一组真实的业务数据做了基准测试:一共239份矿区PDF、86份Word、154份老TXT,总计约4300万个字,覆盖了三个矿区。
清洗前的检索表现:用同一个Embedding模型和切分策略直接跑,设置的评测问题比如“中温热液脉型金矿的矿化蚀变分带特征”,Top-5召回结果里相关的只有2条,而且返回的文本块大量包含乱码符号,大模型在生成回答时直接把这些乱码原样吐出来,整体可用程度很差。用户测下来反馈是“不可用”。
清洗后的表现:同样的问题,Top-5召回相关度达到4条,且乱码字符基本归零。更关键的是加了元数据过滤后,问题“ZK301见矿深度”能精确命中ZK301相关块,不再混入其他钻孔的信息。F1值从清洗前的53.7%提升到91.2%,乱码文本占总召回文本比例从14.2%下降到0.4%。虽然不能说数据集完美了,但对业务侧来说,从不能用到可用的跨越是真真切切发生了的。
这个对比让我意识到,很多项目一上来就调Embedding、调Chunk Size,其实是在上游数据本身就脏的情况下做无用功。数据清洗的本质是把下游模型能力的天花板抬高,这一步不做,后面所有的调参都是空中楼阁。
5.2 高频问题与排查方法速查表
日常清洗过程中,有几类问题反复出现,我把它们整理成了一个速查表,方便团队里的同学快速诊断:
| 现象 | 根因 | 解决思路 |
|---|---|---|
| TXT打开是“锟斤拷” | 编码判定错误,UTF-8被GBK解 | 用charset-normalizer做编码检测,必要时三种编码并行解码选优 |
| PDF复制文字是空白 | 字体子集化/无Unicode映射 | 改用OCR流程,或对特定页做图像渲染后重识别 |
| PDF里表格串列 | 表格线断裂/列坐标漂移 | 提取线和文本块坐标重拼,按Y轴聚类后再生成行列 |
| Word里公式变图片 | OLE对象/公式编辑器 | 标记“待公式识别”,对接公式OCR或人工补充 |
| OCR把“ZK301”识别成“2K301” | 通用OCR不熟悉地质编号规则 | 加领域词典做后处理纠错,用正则强制校验“ZK\d+”模式 |
| 网页抓取的正文混入导航和广告 | CSS选择器没选对正文区 | 用trafilatura做主文本提取,再按标题规则截断 |
| 切分后段落不完整 | 固定长度硬切 | 结构切分优先,句级完整性校验兜底 |
| 检索时大量无关矿区文档混入 | 没有做元数据过滤 | 入库时挂矿区/孔号/坐标字段,检索时先结构化过滤后向量召回 |
这张表我打印出来贴在工位上,每次遇到新文档类型,先按表里的逻辑排查一轮,能少走很多弯路。
6. 实操心得与扩展方向
跑过好几个矿区项目,我对探矿RAG数据清洗的总结就是:这是一场持久战。没有一个清洗脚本能从第一天用到最后,因为数据源永远在变,新格式、新乱码方式层出不穷。我最后的几个心得是:清洗流程要组件化、可插拔,每类源格式一个独立处理模块,坏一个不影响其他流程;乱码修复和术语规范化要领域词表支撑,用项目积累不断补充;清洗结果一定要有样本抽查机制,不能只跑完Pipeline就认为完成了,人眼校验永远有不可替代的位置。
另外,探矿领域的数据清洗和RAG建设可以往“知识图谱+向量”的混合架构扩展。坐标、钻孔层级、地层年代这些关系性信息,用KG来建模和约束非常合适;而矿体描述、成因分析这种非结构化文本,用向量库检索更好。两者结合可以规避纯RAG的“幻觉”问题,也能让“高精度检索”在更复杂的查询上依然成立。很多团队在提“RAG+KG”,但在探矿行业,这个架构不是加分项,而是刚需——因为地质数据知识本身就是高度结构化的,只是传统RAG完全没利用起来。下一步如果我继续做这个项目,一定会把知识抽取和关系链接作为清洗后的核心环节来推进。