探矿RAG数据清洗实战:TXT、Word、PDF与网页的保真处理 1. 探矿数据为什么必须做RAG清洗地质探矿行业的数据形态可能是所有传统行业里最杂的之一。一个中型勘探项目跑下来你手里会同时握着野外记录本扫描的PDF、老专家手写的Word报告、仪器导出的TXT测井曲线、地质图件的说明文档、从公开渠道采集的网页资料甚至还有上世纪存档的纸质资料翻拍件。这些东西不整理RAG系统喂进去就是一堆噪声检索出来的结果比不检索还离谱。我去年接手过一个铜矿勘探知识库的搭建前期没做清洗直接灌库问“ZK-1203钻孔的见矿深度”系统返回的是另一份报告里“ZK-1203设计孔深”的数值差了整整两百米。这种错误在探矿场景里是致命的——设计孔深和实际见矿深度完全是两回事。问题出在哪PDF里的表格被粗暴地按行切分表头和数据行错位加上TXT文件里的编码乱码整个检索链路从源头就烂了。所以这篇内容想聊的就是探矿业务里TXT、Word、PDF和网页这四类数据源各自该怎么清洗才能喂给RAG。核心不是讲RAG框架怎么搭而是讲清洗这一层——这是决定检索精度的地基。适合正在做地质、矿业、勘探类知识库的同行也适合任何需要处理多格式文档的RAG项目参考。提示清洗不是“把文件转成文本”就完事。探矿数据的清洗目标有三个——保留数值精度、保留空间关系、保留专业术语的完整性。缺一个检索就会出问题。2. TXT测井与化验数据的编码陷阱和结构化处理2.1 乱码的根源GBK与UTF-8的混用探矿TXT文件最大的坑是编码。仪器导出的TXT尤其是国产测井仪和化验设备默认编码经常是GBK而你的处理脚本默认按UTF-8读结果就是“Cu品位”变成“Cu鍝佷綅”。更麻烦的是同一个项目里不同批次的文件编码可能不一致——早期用GBK后期换了设备变成UTF-8混在一起处理时你甚至不知道哪个文件是哪种编码。我的做法是写一个编码探测函数用chardet库先检测检测置信度低于0.8的再人工确认。实测下来探矿TXT文件里GBK占比约六成UTF-8约三成剩下的是UTF-8 with BOM或者GB2312。下面这段代码可以直接用import chardet def detect_encoding(file_path): with open(file_path, rb) as f: raw f.read(10000) result chardet.detect(raw) encoding result[encoding] confidence result[confidence] if confidence 0.8: # 低置信度时尝试常见编码 for enc in [gbk, utf-8, gb2312]: try: raw.decode(enc) return enc except UnicodeDecodeError: continue return encoding注意不要用errorsignore来“解决”乱码。忽略掉的字符里可能正好是“Au”“Ag”这样的元素符号或者“m”“ft”这样的单位丢了就是精度事故。2.2 化验数据表格的列对齐问题化验室导出的TXT通常是固定宽度格式列与列之间用空格对齐。但空格数量不固定因为数值长度不同。比如“Cu 0.85 0.12”和“Cu 12.30 1.05”前者元素符号后两个空格后者一个空格。如果你用split()按空白切分遇到元素符号和数值之间没有空格的情况就会错位。更稳的方案是用正则表达式按“元素符号数值数值”的模式提取import re pattern re.compile(r([A-Z][a-z]?)\s([\d.])\s([\d.])) # 匹配 Cu 0.85 0.12 或 Au 1.23 0.45但探矿数据里还有“Cu%”“Au_g/t”这种带单位的表头以及“0.01”这种检出限表示法。我的经验是先把表头行单独提取出来做字段映射数据行再用正则匹配匹配不上的行单独存到一个“待人工复核”文件里。一个万行级别的化验TXT通常只有几十行需要人工看效率可以接受。2.3 测井曲线的深度对齐测井TXT的每一行通常是“深度 值1 值2 值3...”但不同仪器的深度间隔不一样有的是0.1米有的是0.125米。如果你要把多个测井文件合并成一个知识库深度对齐是必须做的。我的做法是统一重采样到0.1米间隔用线性插值填充。这一步不做后面检索“某深度段的伽马值”时不同文件返回的深度基准不一致结果没法比。还有一个细节测井TXT里经常有“-9999”或“-999.25”这样的无效值标记。这些值必须在清洗阶段就替换成空值或NaN否则RAG检索时会把这些无效值当成真实数据返回。我见过一个案例系统返回“该深度段密度值为-9999 g/cm³”这种结果直接让整个知识库的可信度归零。3. Word报告里的公式、表格与批注怎么保真3.1 公式转换OMML到LaTeX的必经之路地质报告里的公式虽然不如数学论文多但品位计算公式、储量估算公式、坐标转换公式都是核心内容。Word里的公式是OMML格式直接转文本会变成一堆乱码或者丢失。必须走OMML→LaTeX的转换路径。pandoc可以处理大部分OMML公式命令是pandoc input.docx -t markdown --extract-media./media -o output.md但实测下来pandoc对复杂公式比如带矩阵、分段函数、上下标的储量计算公式的转换准确率大概在七成左右。剩下的三成需要人工修正。我的做法是转换后用一个LaTeX语法检查脚本扫一遍把报错的公式单独提取出来人工处理。提示如果报告里用了MathType而不是Word自带公式编辑器pandoc可能完全无法识别。这种情况需要先用MathType的“转换→转换为Office公式”功能批量转换再走pandoc。3.2 表格跨页与合并单元格的处理Word报告里的表格经常跨页而且有合并单元格。直接转Markdown表格跨页的部分会变成两个独立表格合并单元格会丢失层级关系。比如一个“钻孔编号 | 见矿深度 | 品位”的表格如果“钻孔编号”列有合并单元格转换后第二页的表格可能就没有钻孔编号了数据行变成“孤儿行”。我的处理策略是先用python-docx读取表格结构把合并单元格的信息提取出来展开成扁平结构再转Markdown。具体来说对于每个合并单元格记录它的起始行、结束行、起始列、结束列然后在展开时把合并单元格的值填充到所有被合并的位置。这样转换后的表格虽然冗余但每一行都是自包含的RAG检索时不会丢失上下文。from docx import Document doc Document(report.docx) for table in doc.tables: for row in table.rows: for cell in row.cells: # cell._tc 可以获取底层XML判断合并属性 tc cell._tc grid_span tc.xpath(.//w:gridSpan) v_merge tc.xpath(.//w:vMerge) # 根据合并属性做展开处理3.3 批注和修订记录的取舍地质报告在评审过程中会产生大量批注和修订记录。这些内容对RAG来说是把双刃剑批注里可能有专家对某个数据的质疑或修正意见这是高价值信息但修订记录里的删除内容如果被保留会导致同一句话出现两个版本。我的原则是批注保留并标注来源修订记录只保留最终版本。具体操作是用python-docx读取批注comments把批注内容作为独立段落插入到对应位置并加上“【批注】”前缀。修订记录则通过docx的accept_all_revisions逻辑只保留最终文本。这样处理之后检索“ZK-1203见矿深度”时如果专家批注说“该深度需复核”这条批注会作为附加信息返回提醒使用者注意数据可靠性。这在探矿场景里非常有用。4. PDF图件与扫描件的文本提取策略4.1 原生PDF与扫描PDF的分流判断PDF分两种原生PDF文字可选和扫描PDF图片。探矿资料里两者都有而且经常混在同一个文件里——前几页是原生文字后面附图是扫描件。不分流处理要么扫描件提取不出文字要么原生文字被当成图片做OCR浪费算力还降低精度。判断方法很简单用pdfplumber尝试提取第一页的文字如果提取出的字符数少于50基本可以判定为扫描件。但更稳的做法是逐页判断因为混合型PDF很常见。import pdfplumber def classify_pdf_page(page): text page.extract_text() if text and len(text.strip()) 50: return native return scanned原生PDF用pdfplumber直接提取扫描PDF走OCR。OCR工具选PaddleOCR对中文地质报告里的宋体、黑体识别率明显高于Tesseract尤其是“勘探线”“钻孔”“标高”这些专业词汇。4.2 地质图件的图例与标注提取地质图件是PDF里最难处理的部分。一张地质平面图上有图例、标注、坐标网格、剖面线这些信息对RAG检索极有价值但常规文本提取完全拿不到。我的做法是分两步第一步用OCR提取图件上的所有文字包括图例和标注第二步用图像处理把文字的位置信息保留下来因为“图例”和“标注”的语义不同——图例是解释性的标注是定位性的。具体来说用PaddleOCR的det和rec模型拿到文字框的坐标然后根据坐标位置判断位于图幅边缘的文字大概率是图例位于图幅内部的文字大概率是标注。这个判断不是百分百准确但实测下来能到八成以上。剩下的两成通过关键词过滤修正——比如包含“组”“段”“系”的文字通常是地层图例包含“ZK”“TC”“BT”的通常是工程点标注。4.3 表格跨页与旋转文本的修复PDF里的表格跨页问题比Word更严重因为PDF没有“表格”概念只有文字和线条。pdfplumber的extract_table()对简单表格有效但遇到跨页表格或者带旋转文字的表格就歇菜。跨页表格的修复思路是先提取每一页的表格然后根据表头是否重复来判断是否跨页。如果第二页的表格第一行和第一页的表头相似度超过80%就合并。旋转文本比如纵向排列的钻孔编号需要先用pdfplumber的chars属性拿到每个字符的旋转角度对旋转角度超过45度的文字单独做旋转校正后再OCR。注意PDF清洗最容易被忽略的是页眉页脚。地质报告每页都有“项目名称页码”如果不剔除RAG检索时每段文本都会带上这些噪声严重影响检索精度。用pdfplumber的crop功能把页面上下各裁掉10%再提取。5. 网页采集内容的去噪与知识对齐5.1 从网页正文中剥离导航与广告探矿业务需要的网页资料主要是公开的地质调查报告、矿权公示、行业标准。这些页面通常有大量导航栏、侧边栏、广告、评论区。直接抓取整个HTML喂给RAG检索结果里会混入“上一篇”“下一篇”“相关推荐”这类完全无关的内容。正文提取用trafilatura比BeautifulSoup更省心它内置了正文识别算法对新闻类和文档类页面的准确率很高。但地质类网页有个特点很多报告是以表格形式呈现的trafilatura可能会把表格当成非正文剔除。这种情况需要先用BeautifulSoup把table标签单独提取出来再对剩余部分用trafilatura提取正文。import trafilatura from bs4 import BeautifulSoup html fetch_page(url) soup BeautifulSoup(html, html.parser) tables soup.find_all(table) # 先提取表格 for table in tables: # 处理表格内容 pass # 再提取正文 text trafilatura.extract(html)5.2 网页表格与PDF表格的字段对齐网页上的矿权公示表格和PDF报告里的表格字段名经常不一致。比如网页叫“矿权名称”PDF叫“项目名称”网页叫“面积(km²)”PDF叫“区块面积”。如果不做字段对齐RAG检索“某矿权面积”时只能命中其中一种表述。我的做法是维护一个探矿领域的字段同义词表在清洗阶段就把不同来源的字段名映射到统一的标准字段。这个表不需要很大覆盖常见的几十个字段就够了。比如标准字段同义词项目名称矿权名称、勘查区名称、区块名称面积区块面积、勘查面积、矿权面积矿种主矿种、勘查矿种、目标矿种品位平均品位、Cu品位、Au品位这个表是动态维护的每遇到一个新的表述就加进去。半年下来字段对齐的准确率能从六成提到九成以上。5.3 网页时间戳与版本信息的保留地质资料的时效性很重要。一个2015年的矿权公示和2023年的数据可能完全不同。网页采集时如果不保留时间戳RAG检索“某矿权面积”时可能返回过时数据。我的做法是在清洗阶段就把网页的发布时间、更新时间提取出来作为元数据附加到文本块上。如果网页没有明确的时间戳就从URL或页面内容里推断——比如URL里有“2023”就标注为2023年。检索时按时间倒序排列优先返回最新数据。6. 清洗后的分块策略与检索精度验证6.1 探矿文本的分块粒度按语义还是按长度通用RAG的分块策略通常是按固定长度比如512个token切分但探矿文本不能这么干。一个钻孔的描述可能跨了三个段落按长度切分会把“ZK-1203”和它的“见矿深度”切到两个块里检索时只命中一个信息就不完整。我的策略是按语义单元分块一个钻孔的描述是一个块一个化验表格是一个块一个图件的图例是一个块。具体实现上用段落之间的空行和标题作为分块边界如果单个语义单元超过1000个token再按句子边界二次切分。这样做的好处是每个块都是自包含的检索时不会出现“半截信息”。代价是块的长度不均匀有的块只有几十个token有的块有上千个。但对于探矿场景精度比均匀性重要得多。6.2 元数据附加让检索知道“这是什么”清洗后的每个文本块都必须附加元数据否则RAG不知道这段文字是化验数据还是地质描述。我附加的元数据包括来源类型TXT测井、Word报告、PDF图件、网页采集数据类型化验数据、测井曲线、地质描述、矿权信息空间信息钻孔编号、勘探线号、坐标范围时间信息数据采集年份、报告提交年份置信度OCR识别置信度、编码检测置信度这些元数据在检索时可以用于过滤和排序。比如用户问“2020年以后的铜品位数据”系统可以先用时间元数据过滤再在过滤结果里做语义检索精度和速度都会提升。6.3 检索精度验证用已知答案反查清洗和分块做完之后必须做检索精度验证。我的方法是构造一批“已知答案”的问题比如“ZK-1203的见矿深度是多少”然后看系统返回的结果是否包含正确答案。验证集不需要很大50到100个问题就够。关键是这些问题要覆盖不同的数据源和数据类型。如果某个数据源的命中率明显偏低就回去检查那个数据源的清洗逻辑。我自己的经验是TXT和Word的清洗效果通常最好命中率能到九成以上PDF图件和网页采集的命中率在七到八成主要问题是OCR错误和正文提取不完整。这个命中率对于探矿知识库来说已经可用但还有优化空间。提示验证时不要只看“是否命中”还要看“返回了多少无关内容”。如果一个问题返回了10个块只有1个是相关的那检索精度其实很低。我的标准是前3个块里必须包含正确答案否则就算失败。7. 我在探矿RAG清洗中踩过的几个坑第一个坑是过度清洗。早期我为了让文本“干净”把所有非中文字符都过滤掉了结果把“Cu”“Au”“g/t”“km²”这些关键信息也滤掉了。后来改成只过滤控制字符和连续空白保留所有可见字符。第二个坑是忽略文件级元数据。清洗时只关注文本内容忘了保留文件名、文件路径、修改时间。后来发现文件名里往往包含关键信息比如“ZK1203_化验结果_2023.txt”这些信息在检索时非常有用。第三个坑是OCR语言模型选错。PaddleOCR默认是中英文混合模型但地质报告里经常有俄文、蒙文的地名。后来加了多语言模型识别率才上来。第四个坑是分块时切断表格。一个化验表格如果被切成两半后半部分的数值就失去了表头变成无意义的数字。后来改成表格整体作为一个块不切分。这些坑的共同点是清洗的目标不是“干净”而是“保真”。探矿数据的价值在于精度和完整性任何以“干净”为名的信息丢失都是不可接受的。