探矿文档清洗实战:从多格式资料到RAG知识库的构建指南
1. 探矿业务文档处理的真实困境
搞地质勘查和矿产勘探的人都有一个共同的痛:资料太多、太杂、太乱。一个中型探矿项目,从预查、普查到详查阶段,积累的文档动辄上千份——钻孔柱状图、勘探线剖面图说明、化验分析报告、物化探数据表、地质填图记录、评审意见书,格式横跨TXT纯文本、Word文档、PDF扫描件、网页存档,甚至还有从老系统里导出的半结构化数据。这些资料如果只是堆在硬盘里,那就是一堆死数据;但如果能把它们清洗干净、结构化地喂给RAG系统,就能变成一个真正能用的地质知识库。
我所在团队从2023年下半年开始尝试用RAG技术做探矿知识管理,踩了无数坑。最开始想得很简单:把文档往向量数据库里一扔,做个embedding,检索就完事了。结果第一版上线后,检索出来的内容驴唇不对马嘴——问“某矿区花岗岩蚀变带特征”,返回的却是“花岗岩分类标准”的通用教材内容。问题出在清洗环节:TXT里的表格错位、Word里的公式变成乱码、PDF扫描件OCR后错字连篇、网页存档夹杂大量导航栏和广告文本。这些噪声直接污染了向量空间,导致检索精度惨不忍睹。
后来我们花了将近四个月时间,专门打磨文档清洗流水线,才把检索准确率从最初的不到40%提升到85%以上。这篇文章就是把这套清洗方法论完整拆解出来,包括TXT的结构化解析、Word的公式与表格处理、PDF的分层提取策略、网页的正文抽取,以及最终如何把这些清洗后的内容组织成适合RAG检索的知识库。无论你是做地质信息化、矿业数字化,还是其他需要处理多格式文档的领域,这套思路都可以直接参考。
2. 整体清洗架构与方案选型
2.1 为什么不能直接“一刀切”转TXT
很多人处理多格式文档的第一反应是:全部转成纯文本,统一处理。我一开始也是这么干的,用Python的textract库批量转换,结果发现这是最糟糕的方案。TXT转换会丢失所有结构信息——表格变成一堆空格分隔的数字,标题和正文混在一起,公式直接消失。对于探矿业务来说,化验分析表里的品位数据、钻孔编录中的分层深度,这些恰恰是最需要保留结构的信息。
正确的做法是分层处理:不同格式走不同的提取管道,但最终输出统一的结构化中间格式。我们选的是Markdown作为中间格式,原因是它既能保留标题层级、表格、列表等结构,又足够轻量,方便后续做分块和embedding。具体来说,TXT走规则解析管道,Word走python-docx加自定义后处理,PDF走PyMuPDF加OCR兜底,网页走trafilatura正文抽取。每条管道输出的Markdown再经过统一的清洗、分块、元数据标注,最后入向量库。
2.2 分块策略:按地质语义切,不按字数切
RAG检索的质量很大程度上取决于分块策略。最常见的做法是按固定字数切分,比如每500字一块。但在探矿文档里,这种做法会直接把一个钻孔的完整编录切得七零八落。我们采用的是语义分块:以地质单元为最小粒度,比如一个钻孔的某一层描述、一个化验批次的完整数据表、一段独立的构造分析段落。具体实现上,先用标题层级做一级切分,再在段落内用地质术语词典做二级切分。
这里有个关键参数:块大小控制在300到800字之间,重叠区设为50到100字。为什么是这个范围?因为地质描述通常比较紧凑,一个完整的岩性描述段落大约200到400字,加上上下文的蚀变信息和矿化信息,500字左右能覆盖一个完整的语义单元。重叠区是为了防止边界处的关键信息被切断,比如“上覆地层为……”这种跨段落的指代关系。
2.3 元数据标注:让检索能按业务维度过滤
光有文本内容还不够,探矿业务检索经常需要按矿区、按矿种、按文档类型、按年代来过滤。我们在清洗阶段就给每个块打上元数据标签:矿区名称、经纬度范围、矿种类型、文档来源格式、原始文件名、页码或段落位置、置信度评分。这些元数据存在向量库的payload里,检索时可以先用元数据做粗筛,再做向量相似度匹配,精度提升非常明显。
举个例子,用户问“西藏某铜矿的蚀变分带特征”,如果没有元数据过滤,系统可能会返回全国各地的铜矿蚀变资料。加上矿区名称和经纬度过滤后,检索范围直接缩小到目标矿区,准确率从50%多跳到了80%以上。
3. TXT文档的结构化解析
3.1 探矿TXT的典型形态
探矿业务里的TXT文件主要有几种来源:老式数据库导出的定长文本、仪器直读数据、人工录入的简版编录、以及从其他系统转换来的半结构化文本。这些文件的共同特点是:没有统一的格式标准,字段分隔符五花八门,有的用制表符,有的用多个空格,有的用竖线,还有的干脆就是固定列宽。
我处理过最棘手的一个TXT文件,是某老矿区1990年代的钻孔数据,每行128个字符,前20位是孔号,21到30位是深度,31到50位是岩性描述,后面是化验数据。这种定长格式用常规的split根本搞不定,必须按字符位置精确切片。
3.2 规则解析管道的实现
针对这类TXT,我写了一套基于配置的解析器。核心思路是:先自动检测文件的分隔模式,再根据检测结果选择对应的解析策略。自动检测的逻辑是统计每行中制表符、连续空格、竖线、逗号的出现频率,取频率最高且行间一致性最好的作为分隔符。如果所有分隔符的一致性都低于阈值,就判定为定长格式,转而用字符位置配置来解析。
解析完成后,输出结构化的字典列表,每个字典代表一条记录,键是字段名,值是字段内容。字段名从文件头或配套的说明文档中提取,如果找不到就自动生成field_1、field_2这样的占位名,后续再人工映射。这一步的关键是保留原始行号和字符位置,方便回溯核对。
3.3 表格数据的特殊处理
TXT里的表格是最难处理的。有些表格用加号、减号、竖线画边框,有些用等号分隔表头和表体,还有些干脆就是纯文本堆叠。我的做法是:先用正则识别表格边界(连续多行包含相同分隔符模式),再把表格区域单独提取出来,用类似CSV的解析逻辑处理。对于没有明确分隔符的表格,尝试用列对齐信息来推断列边界——统计每列字符的起始位置,如果多行的起始位置一致,就认为这是一列。
解析后的表格统一转成Markdown表格格式,保留表头和数据行的对应关系。这里有个细节:探矿数据表里经常有合并单元格,比如一个化验批次下多个样品共享同一个批号。Markdown表格不支持合并单元格,我的处理方式是把合并单元格的值向下填充到每一行,同时在元数据里标注“原始合并”标记,避免后续检索时误判。
注意:TXT解析最怕的是编码问题。国内探矿文档常见GBK、GB2312、UTF-8三种编码混用,甚至同一文件里不同段落编码不同。我的做法是先用chardet检测,检测置信度低于0.8的用GB18030兜底尝试,再不行就按字节流手动解码。千万别直接用UTF-8硬读,否则乱码会让你怀疑人生。
4. Word文档的深度清洗
4.1 公式与特殊符号的处理
Word文档在探矿业务里主要用于编写地质报告、设计书、评审意见。这些文档里经常嵌入MathType或AxMath公式,直接转文本会变成乱码或者丢失。我试过用python-docx直接读,公式部分返回的是空字符串或者XML片段,根本没法用。
后来采用的方案是:先用python-docx提取正文文本和表格,同时用docx2python库提取公式的OMML(Office Math Markup Language)源码。OMML是XML格式的,里面包含了公式的完整结构信息。我写了一个转换器,把常见的OMML元素映射成LaTeX:分数转\frac{}{},上下标转^和_,根号转\sqrt{},积分转\int。对于复杂的矩阵和分段函数,转换器覆盖不全的,就保留OMML源码,在Markdown里用注释块标注,检索时跳过这些块,但保留位置信息供人工查阅。
实测下来,常见的地质公式——比如品位计算公式、储量估算公式、坐标转换公式——转换成功率在90%以上。剩下的10%主要是手写公式的扫描件嵌入,这种只能走OCR加人工校对。
4.2 表格列宽与合并单元格的还原
Word表格在转Markdown时有个经典问题:列宽信息丢失,导致表格结构错乱。python-docx可以读取单元格的宽度属性,但很多文档的列宽是“自动调整”模式,实际宽度取决于渲染环境。我的做法是:优先读取显式设置的列宽,如果没有,就根据单元格内容的平均字符数来估算列宽比例,生成Markdown表格的分隔线。
合并单元格的处理更麻烦。Word里一个跨三列的单元格,在python-docx里会表现为三个独立的cell对象,但只有第一个有内容。我的还原逻辑是:遍历表格的XML结构,识别gridSpan和vMerge属性,重建合并关系。对于横向合并,把内容放在第一列,后续列留空;对于纵向合并,把内容放在第一行,后续行留空。同时在Markdown表格下方加一个注释,说明哪些单元格是合并的。
4.3 批注与修订记录的取舍
地质报告在评审过程中会产生大量批注和修订记录。这些内容对理解文档的演变过程很有价值,但直接混入正文会干扰检索。我的策略是:批注和修订单独提取,作为元数据附加到对应的段落块上,不混入正文文本。检索时,如果用户明确需要查看评审意见,可以通过元数据过滤调出;默认情况下只检索正文。
具体实现上,python-docx可以读取comments.xml和settings.xml里的修订信息。批注的锚点位置通过commentRangeStart和commentRangeEnd定位,把批注内容关联到对应的段落。修订记录则通过w:ins和w:del标签识别插入和删除的内容,插入内容保留,删除内容用删除线标注后放入元数据。
实操心得:Word文档清洗最耗时的不是技术实现,而是处理各种“非标准”操作。比如有人用文本框排版、用艺术字做标题、用图片代替表格。这些情况没有通用解法,只能靠预处理阶段先扫描文档结构,发现异常元素就标记出来,走人工复核通道。我一般会统计文档中文本框、图片、艺术字的数量,超过阈值就单独处理。
5. PDF文档的分层提取策略
5.1 文本型PDF与扫描型PDF的自动判别
PDF是探矿资料里最复杂的格式,没有之一。同样是PDF,可能是原生数字排版,也可能是扫描件,还可能是两者混合——正文是文字,附图是扫描图片。我的判别逻辑是:用PyMuPDF逐页提取文本,如果某页提取出的字符数超过阈值(比如50个字符),且字符分布均匀,就判定为文本页;如果字符数极少但图片面积占比很大,就判定为扫描页。混合文档按页分别处理。
文本页直接用PyMuPDF的get_text("dict")方法提取,保留字体大小、加粗、颜色等信息,用于推断标题层级。扫描页走OCR管道,我用的PaddleOCR,对中文地质文档的识别效果比Tesseract好不少,尤其是表格和手写体。
5.2 版面分析与阅读顺序还原
PDF的版面分析是清洗的关键难点。地质报告常见双栏排版、图表混排、页眉页脚干扰。如果直接按文本流提取,阅读顺序会乱掉——左栏和右栏的内容交错在一起,表格和正文混在一起。
我的做法是:先用PyMuPDF获取每个文本块的边界框坐标,再用版面分析算法判断分栏。具体来说,统计所有文本块的x坐标分布,如果存在明显的双峰分布,就判定为双栏,按x坐标把文本块分成左右两组,先读左栏再读右栏。对于跨栏的标题和表格,通过宽度判断——宽度超过页面宽度60%的块视为跨栏元素,单独处理。
页眉页脚通过位置过滤:y坐标在页面顶部5%或底部5%以内的文本块,且在多页重复出现的,判定为页眉页脚,直接丢弃。图表标题通过字体和位置识别:字号较小、位于图片下方或上方的文本块,判定为图题或表题,与对应的图片关联。
5.3 表格与图片的分离处理
PDF里的表格提取是个老大难。我试过camelot、tabula、pdfplumber,各有优劣。camelot对有线表格效果好,但对无线表格无能为力;pdfplumber能处理无线表格,但复杂合并单元格容易出错。最后的方案是组合使用:先用pdfplumber检测表格区域,如果检测到表格线,用camelot提取;如果没有表格线,用pdfplumber的文本对齐信息推断列边界。
图片的处理相对简单:提取出来单独存储,用OCR识别图片中的文字(比如扫描的化验报告),识别结果作为图片的alt文本附加到Markdown中。对于地质图件,还会用图像分类模型判断图件类型(柱状图、剖面图、平面图等),作为元数据标注。
注意:PDF清洗最容易被忽视的是字体嵌入问题。有些PDF用了非标准字体,提取出来的文本是乱码。遇到这种情况,我的做法是先检查字体的ToUnicode映射表,如果映射缺失,就回退到OCR识别。虽然OCR会引入少量错误,但总比乱码强。
6. 网页存档的正文抽取
6.1 探矿相关网页的类型分析
探矿业务需要的网页资料主要有几类:地质调查局的公开报告页面、矿业期刊的在线论文、行业标准发布页、以及一些地质论坛的讨论帖。这些页面的共同特点是:正文被大量导航栏、侧边栏、广告、评论区包围,直接抓取会引入大量噪声。
我试过用BeautifulSoup按标签提取,效果很差——不同网站的HTML结构千差万别,写规则写到崩溃。后来改用trafilatura,这是一个专门做正文抽取的库,基于文本密度和标签语义自动识别正文区域。实测下来,对新闻类和博客类页面的抽取准确率在85%以上,对论坛类页面稍差,但也能达到70%左右。
6.2 正文抽取与噪声过滤
trafilatura的核心逻辑是:计算每个HTML节点的文本密度(文本长度除以标签数量),密度高的节点更可能是正文。同时结合标签语义——<article>、<main>、<p>标签权重高,<nav>、<aside>、<footer>权重低。最终输出的是纯文本加基本的结构标记(标题、段落、列表)。
对于抽取结果,我还会做一轮后处理:过滤掉长度小于20个字符的段落(通常是残留的导航文字),过滤掉包含“版权所有”、“免责声明”等关键词的段落,过滤掉纯链接列表。对于地质论坛的帖子,额外提取发帖时间、作者、楼层号作为元数据。
6.3 动态网页的预处理
有些地质数据平台是动态加载的,直接抓HTML只能拿到空壳。这种情况需要先用无头浏览器渲染。我用的Playwright,等页面加载完成后获取渲染后的HTML,再交给trafilatura处理。这里有个坑:有些平台有反爬机制,频繁请求会被封。我的做法是控制请求频率,每次请求间隔3到5秒,同时设置合理的User-Agent和Referer头。
对于需要登录才能查看的内容,我一般不建议强行抓取,而是通过正规渠道获取数据授权。毕竟探矿资料很多涉及商业机密,合规性比技术更重要。
7. 统一清洗与知识库构建
7.1 清洗后的质量校验
四条管道输出的Markdown汇总后,还需要一轮统一的质量校验。我设计了几个检查项:文本长度是否在合理范围(太短可能是提取失败,太长可能是噪声混入)、表格行列数是否一致、公式LaTeX是否能正常渲染、元数据字段是否完整。校验不通过的块会被标记出来,进入人工复核队列。
校验通过后,还会做一轮去重。探矿资料里经常有同一份报告的不同版本,或者同一数据在不同文档中重复出现。我用SimHash做近似去重,汉明距离小于3的视为重复,保留元数据更完整的那一份。
7.2 向量化与索引构建
清洗后的Markdown块用嵌入模型转成向量。我对比过几个中文嵌入模型:text-embedding-ada-002对通用中文效果好,但对地质术语不够敏感;BGE-large-zh在地质领域微调后效果最好,尤其是对岩性名称、矿物名称、地层代号这些专业术语的区分度明显更高。
向量库用的Milvus,支持标量字段过滤和向量相似度联合查询。索引类型选的是HNSW,因为探矿知识库的查询量不大但要求低延迟,HNSW在召回率和速度之间平衡得比较好。参数上,M设为16,efConstruction设为200,efSearch设为64,实测召回率在95%以上。
7.3 检索效果验证与迭代
知识库上线后,我用一批真实的地质查询做了验证。查询集包括:“某矿区主要蚀变类型及分带特征”、“某钻孔见矿深度及品位”、“某区域地层序列及接触关系”等。评估指标是Top-5检索结果的相关性,由两位地质工程师独立打分。
第一轮验证发现,构造类查询的准确率明显低于岩性和矿化类查询。分析原因是构造描述往往跨段落、跨章节,分块时被切断了。后来调整了分块策略:对构造分析章节,用更大的块(800到1200字),并增加块间重叠到150字。调整后构造类查询准确率从62%提升到了81%。
8. 常见问题与排查技巧实录
8.1 乱码问题的系统排查
乱码是文档清洗中最常见的问题,表现形式多样:中文变问号、变方块、变奇怪符号。排查思路是分层定位:先确认文件编码,用file -i命令或chardet检测;再确认读取方式,Python里用open(file, encoding='xxx')显式指定编码;最后确认输出编码,写入文件时统一用UTF-8。
如果编码检测置信度低,可以尝试用iconv命令行工具做编码转换测试,看哪种编码转换后能正常显示。对于混合编码的文件,只能按段落或按行检测,分段解码。
8.2 表格错位的修复方法
表格错位通常表现为列对不齐、数据串行。修复方法是:先检查原始表格的分隔符是否一致,如果不一致,统一替换成制表符;再检查是否有单元格内容包含分隔符,如果有,用引号包裹或转义;最后检查列数是否一致,不一致的行标记出来人工核对。
对于Markdown表格,还要注意管道符|的转义。单元格内容里的|必须写成\|,否则会破坏表格结构。
8.3 公式转换失败的兜底方案
公式转换失败时,不要强行转换,而是保留原始OMML或MathML源码,在Markdown里用HTML注释包裹。检索时跳过这些注释块,但保留位置标记。同时把公式截图保存为图片,在Markdown里用图片链接引用,这样人工查阅时能看到公式原貌。
8.4 检索精度下降的排查清单
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 返回结果与查询无关 | 分块过大,噪声混入 | 检查块大小和内容 | 缩小块大小,增加清洗规则 |
| 返回结果不完整 | 分块过小,语义切断 | 检查块边界 | 增大块大小,增加重叠 |
| 专业术语检索不准 | 嵌入模型领域适配不足 | 测试领域术语相似度 | 微调嵌入模型或增加术语词典 |
| 按矿区过滤失效 | 元数据缺失或错误 | 检查元数据字段 | 补全元数据,增加校验规则 |
| 表格数据检索错乱 | 表格解析错误 | 检查Markdown表格结构 | 修复表格解析逻辑 |
实操心得:清洗流水线一定要有日志和回滚机制。每处理一个文档,记录原始文件路径、处理时间、各阶段输出、异常信息。一旦发现某批文档清洗质量下降,可以快速定位是哪个环节出了问题,回滚到上一版本重新处理。我一般会保留最近三个版本的清洗结果,方便对比。
9. 一些踩坑后的个人体会
这套清洗流水线从最初的想法到稳定运行,前后迭代了十几个版本。最大的体会是:不要追求一步到位,而是先跑通最小闭环,再逐步优化。最开始我们只处理TXT和文本型PDF,能检索就行;后来逐步加入Word、扫描PDF、网页,每加一种格式就专门打磨对应的清洗管道。
另一个体会是:地质领域的RAG和其他领域最大的不同在于术语的精确性。通用嵌入模型对“花岗闪长岩”和“花岗岩”的区分度不够,但对地质学家来说这是两种完全不同的岩石。后来我们在嵌入模型微调时加入了大量地质术语对,让模型学会区分这些细粒度概念,检索准确率才有了质的提升。
最后,文档清洗不是一劳永逸的事。新的文档格式、新的排版风格、新的噪声类型会不断出现,清洗规则需要持续维护。我的做法是建立一个反馈闭环:用户每次标记“检索结果不相关”,就把对应的查询和文档块记录下来,定期分析原因,补充清洗规则或调整分块策略。这个闭环跑起来后,知识库的检索精度才能持续提升,而不是上线即巅峰然后慢慢退化。