资讯动态

探矿业务多源异构文档RAG清洗实战:从TXT、Word、PDF到知识库

发布时间:2026/10/9 13:12:13 来源:尧图企业网站定制
1. 探矿业务文档处理的真实困境地质勘探行业有一个很尴尬的现状我们每天产生和消费的数据量极大但真正能被有效利用的却少得可怜。一个中型探矿项目从预查到详查阶段积累的原始资料动辄几十个GB——钻探编录、槽探记录、物化探数据、地质填图报告、评审意见、历史档案扫描件格式横跨TXT、Word、PDF、Excel、扫描图片甚至手写笔记的翻拍照片。这些资料散落在不同年代、不同项目组、不同人的硬盘里命名规则五花八门有的用“ZK3201_终孔报告_修.docx”有的直接是“新建文件夹(3)/最终版2.pdf”。我真正意识到问题的严重性是在去年接手一个老矿区深部找矿项目的时候。甲方要求我们基于近十五年的地质资料快速圈定三个靶区。按理说这种需求在RAG检索增强生成技术已经相当成熟的今天搭一个知识库应该不难。但实际动手才发现探矿领域的文档清洗和通用场景完全不是一回事。TXT文件里混着大量用空格和制表符拼出来的“表格”Word文档里嵌着OLE公式对象和跨页表格PDF更麻烦——有原生文本层的、有纯扫描件的、还有图文混排且图上有标注的。网页资料则来自不同地质队的公开报告页面HTML结构千奇百怪。这就是我写这篇东西的原因。市面上讲RAG清洗的文章不少但大多停留在“用LangChain切分一下、用Embedding存一下”的层面真正面对探矿业务这种多源异构、专业术语密集、格式极度不规范的场景那些通用方案基本跑不通。我踩过的坑包括但不限于TXT里的坐标数据被当成普通文本切碎、Word表格跨页后表头丢失导致品位数据错位、PDF扫描件OCR后“花岗闪长岩”被识别成“花岗闪长岩”看似一样但编码不同、网页抓取时把导航栏和页脚也塞进了知识库。这篇文章就是把这些经验系统化给同样在探矿或类似重工业领域做RAG知识库的同行一个可参考的路径。2. 多源文档清洗的整体设计思路2.1 为什么不能直接套用通用RAG清洗方案通用RAG清洗方案的核心假设是文档结构相对规范、语言以自然语言为主、表格和公式占比低。但探矿业务文档恰恰相反。一份典型的地质报告可能30%是叙述性文字40%是表格钻孔数据、样品分析结果、储量估算表20%是图件及其说明剩下10%是公式和符号。如果直接按固定长度切分一个钻孔的完整数据可能被切成三段检索时只能召回其中一段导致品位、厚度、深度这些关键参数对不上。更深层的问题是语义单元的定义。在通用场景里一个段落就是一个语义单元。但在探矿文档里一个语义单元可能是一张完整的表格、一个图版及其图注、或者一段包含多个参数的计算过程。我试过用递归字符切分结果把“Au品位0.5g/t厚度3.2m”和“Ag品位12g/t厚度1.8m”切到了两个chunk里检索“Au品位大于0.3的钻孔”时模型只能看到前半句完全无法回答。所以整体设计思路必须从“文档结构感知”出发先识别文档的物理结构和逻辑结构再根据业务语义定义切分边界。具体来说我采用的是“三层清洗”架构第一层做格式归一化把TXT、Word、PDF、网页统一转成带结构标记的中间格式第二层做语义分块按地质单元、表格、图版、公式等业务语义切分第三层做元数据注入给每个chunk打上钻孔号、勘探线号、矿种、品位区间等标签供检索时过滤。2.2 三层清洗架构的选型考量第一层格式归一化我选的是“保留结构标记的纯文本JSON”双输出。纯文本用于后续的EmbeddingJSON用于保留表格、公式、图注的结构信息。为什么不直接用HTML因为HTML标签太冗余而且不同来源的HTML结构差异太大后续处理反而麻烦。自定义的轻量级标记比如用[TABLE]...[/TABLE]包裹表格用[FORMULA]...[/FORMULA]包裹公式更可控。第二层语义分块核心是“先识别、再切分”。识别阶段用规则轻量模型结合的方式规则负责识别钻孔编号、勘探线号、坐标范围等强模式字段轻量模型我用的是一个微调过的BERT变体负责识别段落类型叙述、表格说明、图注、公式说明。切分阶段按“一个钻孔一个chunk、一张表格一个chunk、一个图版一个chunk”的原则执行同时设置最大长度限制超长的表格按行拆分但保留表头。第三层元数据注入我设计了一个“探矿元数据Schema”包含钻孔号、勘探线号、矿种、品位区间、深度区间、数据来源、置信度等字段。这些元数据一部分从文档内容中抽取一部分从文件名和目录结构中推断。比如文件名里有“ZK3201”就自动给这个文档的所有chunk打上钻孔号ZK3201的标签。检索时可以用这些标签做预过滤大幅提升召回精度。2.3 与知识图谱的协同设计热词里提到了“kg知识库”和“ontology rag”这确实是探矿领域RAG的一个重要方向。纯向量检索在处理“哪些钻孔位于F1断层上盘且Au品位大于1g/t”这类多条件查询时效果很差。我的做法是在RAG之外额外构建一个轻量级的知识图谱存储钻孔、断层、地层、矿体之间的空间关系和属性关系。RAG负责回答“F1断层的特征是什么”这类描述性问题知识图谱负责回答“F1断层上盘有哪些见矿钻孔”这类结构化查询。两者通过钻孔号这个主键关联检索时先走图谱过滤出候选钻孔集合再用RAG在候选集合内做语义检索。这个协同设计的关键在于实体对齐。同一个钻孔在不同文档里可能写成“ZK3201”“zk3201”“钻孔3201”“3201孔”需要在清洗阶段就做归一化。我的做法是维护一个别名表清洗时用正则匹配人工校验的方式把别名统一到标准名。这个工作很枯燥但做一次就能长期受益。3. TXT文档的清洗与结构化处理3.1 TXT在探矿业务中的特殊地位TXT格式在探矿行业的使用频率远超外行想象。很多老式测井设备、化探分析仪器、钻孔测斜仪的输出格式就是TXT而且往往是固定宽度或制表符分隔的“伪表格”。这些文件通常没有表头列的含义靠约定俗成或者配套的说明文件。比如一个典型的测井TXT可能长这样ZK3201 0.00 1.20 2.45 120.5 0.03 ZK3201 1.20 2.80 2.51 118.2 0.05 ZK3201 2.80 4.50 2.38 115.7 0.02没有表头但老地质队员一看就知道是“钻孔号 起始深度 终止深度 伽马值 电阻率 品位”。这种文件如果直接当普通文本处理Embedding后检索“ZK3201的伽马值异常段”模型根本找不到北。3.2 固定宽度与分隔符的自动识别我的处理流程是先检测文件是否包含制表符或连续多个空格如果有按分隔符拆分如果没有按固定宽度拆分。固定宽度的检测用“列方差法”——计算每一列字符的方差方差小的列很可能是固定宽度字段。这个方法的原理是固定宽度字段的每一列在垂直方向上字符类型相对一致比如深度列全是数字和小数点而叙述性文本的列方差会很大。识别出列边界后需要推断每列的含义。我的做法是维护一个“列语义推断规则库”比如“第一列匹配ZK\d模式则判定为钻孔号”“连续两列都是浮点数且第二列大于第一列则判定为深度区间”“最后一列浮点数且值域在0.01-100之间则判定为品位”。这个规则库需要根据实际数据不断迭代但一旦建好处理效率极高。注意固定宽度TXT里经常混有中文全角空格和英文半角空格肉眼看起来一样但编码不同。清洗时必须先统一替换否则列拆分必错。我一般用re.sub(r[\u3000\xa0], , text)做预处理。3.3 无表头数据的元数据补全对于无表头TXT清洗后的chunk必须补上列含义说明。我的做法是在chunk开头插入一行“列说明[钻孔号] [起始深度(m)] [终止深度(m)] [伽马值(cps)] [电阻率(Ω·m)] [Au品位(g/t)]”。这行说明不参与Embedding但会作为元数据存储检索时如果命中这个chunk生成答案时会把列说明一起送给模型。这样模型就能正确理解“2.45”是伽马值而不是品位。另外对于跨多个钻孔的TXT文件我会按钻孔号做二次切分确保每个chunk只包含一个钻孔的数据。这样做的好处是检索“ZK3201的见矿段”时不会召回ZK3202的数据造成混淆。4. Word文档的深度解析与表格处理4.1 Word文档的“暗坑”清单Word是地质报告的主力格式但也是清洗难度最大的格式之一。我整理了一份“暗坑”清单按出现频率排序坑点出现频率后果处理难度跨页表格表头丢失极高第二页数据无列名品位数据错位中OLE公式对象高公式变成乱码或空白高文本框内文字高常规解析读不到中页眉页脚混入正文中检索结果包含“第X页共Y页”低批注和修订痕迹中同一句话出现多个版本中图片内嵌表格中表格以图片形式存在需OCR高自动编号列表低编号与内容分离低跨页表格表头丢失是最致命的。一份钻孔数据表可能有几百行跨了五六页如果只保留第一页的表头后面几页的数据就失去了列语义。我的解决方案是用python-docx遍历表格时检测表格是否跨页通过比较表格前后段落的位置如果跨页手动把第一行的表头复制到每个分页的起始位置。这个操作在python-docx里没有现成API需要操作XML的tblHeader属性。4.2 表格数据的结构化提取Word表格提取的核心目标是“保留行列关系”。python-docx的table.rows和table.columns可以遍历单元格但合并单元格会导致行列索引错乱。我的做法是先构建一个二维数组用cell._tc的XML属性判断合并状态把合并单元格的值填充到所有被合并的位置。这样得到的二维数组就是规整的可以直接转成CSV或JSON。对于表格中的公式比如储量计算公式我会用正则匹配[A-Za-z]\s*\s*[\d\.\\-\*/\(\)]模式把公式单独提取出来用[FORMULA]标记包裹。这样后续检索“储量计算公式”时能精准命中而不是把公式当普通文本切碎。实操心得Word表格里经常有“其中”“合计”这类汇总行清洗时建议单独标记为[SUMMARY]检索时如果用户问的是明细数据可以过滤掉汇总行避免干扰。我试过不标记结果模型把合计值当成了单个样品值闹过笑话。4.3 公式与特殊符号的归一化地质报告里的公式主要是品位计算公式、储量估算公式、坐标转换公式。这些公式在Word里可能是OLE对象、MathType对象、或者纯文本。OLE和MathType对象用python-docx读出来是空的需要用olefile库单独提取或者用LibreOffice做一次格式转换。纯文本公式则相对好处理但要注意上下标和希腊字母的归一化。我的归一化规则是把α统一成alphaβ统一成betaΣ统一成sum上下标用_和^表示。这样做的原因是Embedding模型对希腊字母的语义理解不稳定归一化后检索“alpha角”和“α角”能命中同一批结果。这个规则看起来简单但实测下来对召回率提升很明显。5. PDF文档的解析策略与OCR处理5.1 原生PDF与扫描PDF的分流处理PDF在探矿资料里分两类原生PDF有文本层和扫描PDF纯图片。原生PDF用pdfplumber或PyMuPDF直接提取文本和表格扫描PDF必须走OCR。分流判断的方法是用PyMuPDF读取第一页如果page.get_text()返回的字符数少于50基本可以判定为扫描件。原生PDF的表格提取比Word更麻烦因为PDF没有“表格”这个概念只有绝对定位的文本块。pdfplumber的extract_table()方法基于线条和文本对齐来推断表格结构对规整表格效果不错但对没有边框线的表格地质报告里很常见就无能为力。我的补充方案是用pdfplumber的extract_words()获取所有单词的坐标然后用DBSCAN聚类做行分组再用列坐标的间隙做列分组。这个方法对无边框表格的识别率能到85%以上。5.2 OCR后的专业术语纠错扫描PDF的OCR是另一个大坑。通用OCR引擎对地质术语的识别率很低“花岗闪长岩”识别成“花岗闪长岩”看似一样但“闪”和“冈”在某些字体下容易混、“矽卡岩”识别成“砂卡岩”、“辉钼矿”识别成“辉钼矿”。更麻烦的是化学元素符号Au可能识别成Au正确或AU大小写错误Ag可能识别成Ag或A9。我的纠错方案是“词典规则”双管齐下。词典是从历年地质报告中抽取的专业术语表包含矿物名、岩石名、地层名、构造名等用编辑距离做模糊匹配。规则是针对化学元素符号和单位的比如“元素符号必须首字母大写、次字母小写”“品位单位必须是g/t或10^-6”。经过纠错后OCR文本的术语准确率能从70%提升到95%以上。注意OCR纠错不要过度有些“错误”其实是原文的笔误或特殊写法。我一般会保留原始OCR结果和纠错后结果两个版本检索时优先用纠错版但如果用户明确要查原文可以切换到原始版。5.3 图件与图注的关联处理地质PDF里大量图件剖面图、柱状图、等值线图及其图注。图注通常在图的下方格式为“图3-1 ZK3201钻孔柱状图”。清洗时我会用正则匹配“图\d-\d”模式把图注提取出来并尝试关联到最近的图片对象。关联后的chunk包含图注文本和图片的base64编码或图片路径检索时如果命中图注可以同时返回图片供用户查看。这个关联的难点在于PDF里图片和图注的物理位置不一定紧邻有时图注在上一页、图片在下一页。我的做法是用PyMuPDF获取图片的边界框和图注文本的边界框计算垂直距离取距离最小的图注作为关联对象。如果距离超过阈值比如200像素则标记为“图注待确认”人工校验。6. 网页资料的抓取与正文提取6.1 探矿相关网页的来源分析探矿业务的网页资料主要来自几个渠道地质队官网的公开报告、行业论坛的技术讨论、学术期刊的摘要页、以及一些老地质队员的个人博客。这些网页的结构差异极大有的用WordPress、有的用静态HTML、有的甚至是十几年前的表格布局。通用网页抓取插件比如Chrome的“网页抓取”扩展对这类网页的正文提取效果很差经常把导航栏、侧边栏、页脚都抓进来。我的做法是先用requestsBeautifulSoup获取HTML然后用“正文密度算法”提取正文。这个算法的核心思想是正文区域的文本密度文本长度/HTML标签数远高于导航和页脚区域。具体实现是遍历所有div和article标签计算每个标签的文本密度取密度最高的标签作为正文容器。对于表格布局的老网页这个算法会失效需要降级到“最大文本块”策略——找到包含最多连续文本的td或p标签。6.2 正文提取后的结构重建网页正文提取后还需要重建结构。地质报告网页通常有标题层级h1到h4、列表、表格。我会保留这些结构标记转成和Word清洗一致的中间格式。表格用[TABLE]标记标题用[H1]到[H4]标记列表用[LIST]标记。这样后续的语义分块可以统一处理不用为网页单独写一套逻辑。网页抓取还有一个伦理问题有些网站有反爬机制频繁请求会被封IP。我的做法是设置合理的请求间隔至少2秒并且只抓取公开的、允许索引的页面。对于需要登录才能查看的内容一律不抓。这个底线必须守住否则会给整个行业带来麻烦。6.3 网页元数据的自动抽取网页的元数据发布时间、作者、来源机构对检索很有价值。我会从meta标签、URL路径、页面内的“发布时间”文本中抽取这些信息。比如URL里有/2023/05/就推断发布时间是2023年5月。来源机构从域名和页面logo的alt文本中推断。这些元数据会作为chunk的标签存储检索时可以用“来源某地质队”做过滤。7. 语义分块与元数据注入的实操细节7.1 按业务语义定义切分边界语义分块的核心原则是“一个chunk一个完整语义单元”。在探矿业务里我定义的语义单元优先级是钻孔数据表 图版及图注 公式及说明 叙述段落。切分时按这个优先级从高到低处理高优先级的单元先切出来剩下的文本再按段落切分。具体实现上我用一个“分块状态机”来管理。状态机从文档开头扫描遇到[TABLE]标记就进入表格状态直到[/TABLE]结束整个表格作为一个chunk如果超长则按行拆分但保留表头。遇到[FIGURE]标记就进入图版状态把图注和图片路径一起打包。遇到[FORMULA]标记就单独成块。普通段落则按句号、分号、换行符切分但设置最小长度阈值比如200字避免切得太碎。7.2 元数据Schema的设计与填充我设计的探矿元数据Schema包含以下字段字段名类型来源示例drillhole_idstring文件名/内容抽取ZK3201exploration_linestring内容抽取32线mineral_typestring内容抽取Augrade_rangestring内容计算0.5-1.0g/tdepth_rangestring内容计算120-180mdata_sourcestring文件属性钻探编录confidencefloat规则计算0.95page_numberint解析器12这些字段的填充分自动和手动两步。自动填充用正则和规则比如从文件名匹配ZK\d得到钻孔号从表格内容计算品位区间。手动填充是在自动填充置信度低于阈值时由地质人员校验修正。这个Schema的好处是检索时可以做精确过滤比如“查32线上所有Au品位大于1的钻孔”先用exploration_line32线 AND mineral_typeAu AND grade_range1.0过滤再在候选集内做向量检索。7.3 分块后的质量校验分块完成后必须做质量校验否则脏数据会污染整个知识库。我的校验清单包括每个chunk是否有明确的钻孔号或来源标识、表格chunk是否包含表头、公式chunk是否完整、叙述chunk是否包含至少一个完整句子。校验不通过的chunk会被标记为“待人工审核”不进入检索库。这个校验步骤看起来繁琐但实测下来能拦截大约15%的脏数据。我试过跳过校验直接入库结果检索“ZK3201的Au品位”时返回了一个没有钻孔号的表格片段模型完全无法回答。从那以后我就把校验作为强制步骤。8. 常见问题与排查技巧实录8.1 清洗效果不达预期的排查路径清洗效果差通常表现为检索召回率低、答案包含乱码、表格数据错位。我的排查路径是“从后往前查”先看检索结果如果结果本身是乱码问题在清洗阶段如果结果正常但答案不对问题在分块或元数据阶段如果结果和答案都正常但召回率低问题在Embedding或检索策略阶段。具体排查时我会随机抽取20个查询人工标注期望结果然后计算召回率和准确率。如果召回率低于80%就逐个查询分析失败原因。常见的失败原因包括chunk切分过碎导致语义不完整、元数据缺失导致过滤失效、专业术语未归一化导致Embedding偏差。8.2 专业术语归一化的避坑经验专业术语归一化最大的坑是“过度归一化”。我一开始把“花岗岩”和“花岗闪长岩”都归一成“花岗岩类”结果检索“花岗闪长岩的含矿性”时召回了一堆花岗岩的资料完全不相关。后来我改成“分级归一化”大类归一花岗岩类小类保留花岗岩、花岗闪长岩、二长花岗岩检索时可以用大类做粗筛、小类做精筛。另一个坑是“同义词表维护”。地质术语的同义词极多比如“矽卡岩”和“夕卡岩”、“辉钼矿”和“硫化钼矿”。我的做法是维护一个同义词表但只做“单向映射”——把变体映射到标准名标准名本身不映射。这样避免循环映射导致的混乱。8.3 表格跨页与合并单元格的修复技巧表格跨页的修复前面提过这里补充一个合并单元格的修复技巧。Word和PDF里的合并单元格在解析后往往表现为“左上角有值、其他位置为空”。我的修复方法是遍历二维数组如果某个单元格为空且其上方或左方的单元格有值且跨越了当前行/列则把值填充过来。判断“跨越”的依据是合并标记Word的vMerge和hMerge属性PDF的线条缺失。这个修复逻辑用Python实现大概50行代码但能解决90%的合并单元格问题。剩下的10%是复杂嵌套合并比如表格里套表格这种只能人工处理但出现频率很低。8.4 常见问题速查表问题现象可能原因排查方法解决方案检索结果含乱码编码未统一检查原始文件编码统一转UTF-8表格数据错位合并单元格未处理打印二维数组填充合并单元格公式丢失OLE对象未提取检查XML用olefile提取召回率低术语未归一化检查同义词表补充同义词映射答案包含页眉页脚页眉页脚未过滤检查chunk内容正则过滤钻孔号混淆别名未对齐检查别名表补充别名映射OCR错误率高扫描质量差检查图片分辨率预处理增强对比度分块过碎切分阈值太小检查chunk长度分布调大最小长度实操心得清洗流程一定要做“可回溯”设计。每个chunk都要保留原始文件路径和页码这样出问题时能快速定位到原文。我试过没保留路径结果一个错误chunk查了半天不知道从哪来的最后只能全量重跑。9. 从清洗到检索的端到端验证9.1 验证集的设计与标注清洗效果最终要落到检索质量上。我设计了一个包含200个查询的验证集覆盖钻孔查询、品位查询、构造查询、储量查询、图件查询五类。每个查询人工标注了期望的chunk ID和答案要点。这个验证集不是一次性的每次清洗流程有改动都会重新跑一遍确保没有回归。验证集的查询设计要贴近真实使用场景。比如“ZK3201在120-180m深度段的Au平均品位是多少”这种查询需要同时用到钻孔号过滤、深度区间过滤和品位计算。如果清洗时深度数据错位或品位数据丢失这个查询就会失败。9.2 检索策略的调优检索策略我采用的是“元数据过滤向量检索重排序”三段式。先用元数据做粗筛比如钻孔号、矿种再用向量检索召回Top 50最后用一个轻量级Cross-Encoder做重排序取Top 5送给生成模型。这个策略比纯向量检索的准确率提升了约30%。重排序模型我选的是一个小型的BERT变体在验证集上微调过。微调数据就是验证集的查询-文档对正样本是标注的期望chunk负样本是随机采样的其他chunk。微调后的重排序模型能很好地识别“品位数据”和“厚度数据”的区别避免把厚度当成品位返回。9.3 端到端效果评估端到端评估的指标包括召回率期望chunk是否在Top 5中、准确率Top 5中相关chunk的比例、答案正确率生成答案是否包含期望要点。经过三轮迭代我的清洗检索流程在验证集上的召回率达到92%准确率达到85%答案正确率达到88%。这个水平对于探矿业务来说已经可用但还有提升空间。主要的失败案例集中在两类一是图件查询因为图片本身无法Embedding只能靠图注文本检索召回率偏低二是跨文档查询比如“对比ZK3201和ZK3202的见矿情况”需要同时召回两个钻孔的数据并做对比当前的单文档检索策略支持不够。这两类问题我还在探索解决方案图件查询考虑用多模态Embedding跨文档查询考虑用查询分解多路召回。10. 一些踩坑后的个人体会清洗这件事工具选型只占三成七成在“对业务的理解”。我一开始用通用方案跑效果很差后来花了两周时间跟着地质队员下矿区看他们怎么读报告、怎么查数据、怎么圈靶区才真正理解了什么叫做“一个完整的语义单元”。比如地质队员看钻孔数据从来不是看单行而是看整个钻孔的品位变化曲线所以chunk必须包含一个钻孔的完整数据段而不是按行切。另一个体会是“不要追求一步到位”。清洗流程一定是迭代出来的第一版能跑通就行然后根据检索效果逐步优化。我第一版清洗只做了格式归一化和简单分块召回率只有60%但至少能跑起来。然后每周根据失败案例补充规则、调整分块策略三个月后才达到现在的水平。如果一开始就追求完美可能永远发不了第一版。最后说一个具体技巧清洗日志一定要详细。每个文件的处理过程、每个chunk的切分依据、每个元数据的来源都要记日志。我用的是一张SQLite表记录file_path, chunk_id, chunk_type, metadata_json, process_log。出问题时直接查表比翻代码快得多。这个习惯帮我省了至少几十个小时的排查时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑