探矿RAG数据清洗实战:TXT、Word、PDF、网页四类数据源处理与分块策略 1. 探矿业务里的数据泥潭为什么通用 RAG 方案一上就废做过矿业数字化的人都有一个共同体会这个行业的数据脏得非常有“个性”。地质报告是扫描件钻孔柱状图是 CAD 导出的 PDF化验数据是老师傅用 Excel 攒了十几年的 TXT矿权公示信息散落在各种网页里还有一堆 Word 格式的内部评审意见。你把这些东西一股脑丢给一个开箱即用的 RAG 框架检索出来的结果基本没法看——要么是乱码要么是表格错位要么是公式变成一串问号。我去年接手的一个探矿知识库项目前期用通用方案跑了两周召回率惨不忍睹。后来花了大概一个月时间专门做数据清洗和分块策略才把检索精度拉到可用水平。这篇文章就把这套清洗流程完整拆开讲涉及 TXT、Word、PDF、网页四种来源的处理思路以及探矿业务特有的坐标、品位、地层代号这些结构化信息的保留方法。先说清楚这套方案适合谁如果你手上有大量地质报告、化验数据、矿权资料需要做成可检索的知识库不管是用现成的 RAG 框架还是自建向量库这篇文章里的清洗逻辑都能直接参考。不需要你精通深度学习但得懂基本的 Python 和正则表达式。核心思路其实就一句话先按数据来源分而治之再按业务语义重新组装。通用方案失败的根本原因是它假设所有文档都是“干净的连续文本”而探矿数据恰恰相反——它的价值藏在表格、图件标注、坐标数字和地层代号里这些恰恰是通用解析器最容易丢掉的部分。2. 四类数据源的清洗策略拆解2.1 TXT 化验数据看似最简单坑最深TXT 在探矿业务里通常有两种形态一种是化验室导出的原始数据用制表符或逗号分隔另一种是老师傅手工整理的台账格式全靠自觉。前者好办后者才是噩梦。我遇到过一个典型情况某矿区从 2008 年到 2020 年的铜品位数据前五年用空格分隔中间三年用制表符最后几年又变成了逗号。更麻烦的是有些行中间夹杂着“以下为补测数据”这样的中文说明还有的行因为当年录入时手抖多了一个小数点。处理这类数据的核心原则是先做结构探测再做字段映射最后做数值校验。不要一上来就写死分隔符。import re import pandas as pd def detect_delimiter(line): 探测单行最可能的分隔符 candidates [\t, ,, ;, |, r\s{2,}] for d in candidates: parts re.split(d, line.strip()) if len(parts) 3: return d return None def clean_assay_txt(filepath): with open(filepath, r, encodingutf-8, errorsignore) as f: lines [l for l in f.readlines() if l.strip()] # 过滤说明性文字行 data_lines [l for l in lines if not re.search(r[以下补测说明备注], l)] delimiter detect_delimiter(data_lines[0]) records [] for line in data_lines: parts re.split(delimiter, line.strip()) records.append(parts) df pd.DataFrame(records) return df这里有个经验编码问题一定要用 errorsignore 兜底。地质行业的老文件很多是 GBK 甚至 GB2312 编码直接按 UTF-8 读会直接抛异常中断整个流程。忽略错误字符虽然会丢一点信息但比整个文件读不进来强得多。数值校验环节我通常会加一个合理性检查铜品位超过 30% 的基本可以判定为录入错误自然界极少有这种富矿这时候要么标记出来人工复核要么按前后行的均值修正。这一步看起来多余但实测能拦掉大概 3% 到 5% 的脏数据对后续检索的可信度提升很明显。2.2 Word 文档表格和公式是重灾区Word 格式的地质报告难点集中在三块嵌套表格、MathType 公式、以及跨页的合并单元格。python-docx 这个库能处理大部分情况但遇到合并单元格会直接返回 None需要自己写逻辑补全。表格处理的关键是保留行列的语义关系。举个例子一个钻孔的测斜数据表表头是“孔深、倾角、方位角”如果解析后变成三列孤立的数字检索时就完全丢失了“这是哪个钻孔的哪个深度”这个上下文。我的做法是把表头信息拼接到每一行的文本里from docx import Document def extract_word_tables(doc_path): doc Document(doc_path) chunks [] for table in doc.tables: # 提取表头 headers [cell.text.strip() for cell in table.rows[0].cells] for row in table.rows[1:]: cells [cell.text.strip() for cell in row.cells] # 跳过全空行 if not any(cells): continue # 将表头与数据拼接成语义完整的句子 row_text .join( f{h}为{c} for h, c in zip(headers, cells) if c ) chunks.append(row_text) return chunks这样处理之后原本一行“120.5 | 75 | 180”会变成“孔深为120.5倾角为75方位角为180”检索“倾角75度的测点”时就能命中。公式处理是另一个大坑。MathType 插入的公式在 python-docx 里读出来是空的因为它是 OLE 对象。可行的方案有两种一是用 Word 宏批量把公式转成 LaTeX 再导出二是用 LibreOffice 的无头模式转换。我实测下来如果公式量不大几百个以内用宏转换更可控量大的话建议走 LibreOffice 命令行虽然慢但稳定。注意Word 宏转换公式前一定要先备份原文件。我踩过一次坑宏跑到一半卡死原文档的公式对象全部损坏只能从备份恢复。2.3 PDF 文档扫描件和矢量图要分开对待PDF 在探矿资料里占比最大也最复杂。必须先把 PDF 分成两类文本型 PDF和扫描型 PDF。前者可以直接抽文字后者必须先做 OCR。判断方法很简单用 pdfplumber 试着抽第一页的文字如果字符数少于 50基本可以判定是扫描件。import pdfplumber def is_scanned_pdf(pdf_path): with pdfplumber.open(pdf_path) as pdf: first_page pdf.pages[0] text first_page.extract_text() or return len(text.strip()) 50文本型 PDF 的表格提取pdfplumber 的 extract_tables 已经够用但要注意它的默认策略对无边框表格识别很差。地质报告里的化验表经常没有完整边框这时候需要调整 table_settings把 vertical_strategy 和 horizontal_strategy 改成 text 模式靠文字对齐来推断表格结构。扫描型 PDF 走 OCR 路线我推荐 PaddleOCR对中文和数字的识别率比 Tesseract 高不少尤其是表格里的数字。但 OCR 之后一定要做后处理把“O”和“0”、“l”和“1”这类常见混淆字符按上下文修正。地质数据里数字密度高这个修正步骤能显著提升后续检索的准确率。还有一个容易被忽略的点PDF 里的图件标注。地质图上的地层代号、断层编号、产状符号这些信息在纯文本抽取时全部丢失。如果业务上需要检索这些内容得单独走图件解析流程把标注文字和坐标一起抽出来存成结构化数据。这块工作量不小建议按需处理不要一开始就全量做。2.4 网页数据动态渲染和反爬的平衡矿权公示、地质资料馆的公开信息很多只在网页上。静态页面用 requests 加 BeautifulSoup 就能搞定但现在的政务类网站大量使用动态渲染必须上 Playwright 或 Selenium。我的选择是 Playwright原因是它的等待机制更智能不用写一堆 sleep。抓取时重点处理三件事正文提取、表格保留、附件链接记录。正文提取不要用简单的 div 选择器网站改版就失效。用 readability 类的算法比如 python-readability自动识别正文区域更稳。表格直接转成和 Word 一样的“表头数据”拼接格式。附件链接单独存一个字段因为很多矿权信息的核心数据在附件 PDF 里正文只是索引。from playwright.sync_api import sync_playwright from readability import Document def scrape_page(url): with sync_playwright() as p: browser p.chromium.launch() page browser.new_page() page.goto(url, wait_untilnetworkidle) html page.content() browser.close() doc Document(html) main_content doc.summary() # 后续用 BeautifulSoup 清理标签、提取表格 return main_content抓取频率要控制好政务网站经不起高频请求。我一般设置每请求间隔 3 到 5 秒并且严格遵守网站的 robots.txt。这不是技术问题是基本的职业操守。3. 从清洗到入库分块策略与元数据设计3.1 探矿业务的分块逻辑通用 RAG 方案默认按固定字符数分块比如每 500 字一块。这在探矿场景下是灾难——一个钻孔的描述可能跨了三个块检索时只能召回片段上下文全丢了。我的分块策略是按业务实体分块一个钻孔一个块一个化验样品一个块一个矿权一个块。块内保留完整的属性信息块与块之间通过元数据关联。具体实现上先用规则识别实体边界。钻孔描述通常以“ZK”加编号开头化验数据以样品编号开头矿权信息以许可证号开头。识别到边界就切块块大小不固定从几百字到几千字都有可能。这样做的好处是检索粒度匹配业务粒度。用户问“ZK1201 的见矿深度是多少”直接命中那个钻孔的完整块不会出现只召回半句话的情况。3.2 元数据字段设计元数据是探矿 RAG 的灵魂。我设计的字段包括数据来源类型TXT/Word/PDF/网页、矿区名称、钻孔编号、样品编号、坐标范围、数据年份、置信度等级。置信度等级这个字段特别有用。原始化验数据标为“高”OCR 识别的标为“中”人工整理台账标为“低”。检索时可以按置信度过滤避免把不可靠的数据混进结果。坐标范围用 GeoHash 存储检索“某区域内的所有矿点”时可以直接做空间过滤比全文检索快得多。3.3 向量化前的文本规范化入库前还有一步不能省术语统一。探矿行业里同一个东西有多种叫法“品位”和“含量”、“倾角”和“倾伏角”、“断层”和“断裂”如果不统一检索时召回率会大打折扣。我的做法是维护一个同义词表在向量化之前做替换。这个表不用一开始就建全跑一段时间检索日志把用户实际用的词和文档里的词对不上的情况收集起来逐步补充。数字格式也要统一。有的文档写“1.5%”有的写“1.5个百分点”有的写“15000ppm”。统一转成标准格式再入库检索“品位大于1%的样品”时才能准确命中。4. 实操中踩过的坑与排查手册4.1 常见问题速查表问题现象可能原因排查方法解决方案检索结果全是乱码编码识别错误用 chardet 检测文件编码按检测结果指定编码读取加 errorsignore表格数据错位合并单元格未处理打印解析后的行列结构补全合并单元格的空白值公式变成空字符串MathType OLE 对象检查 docx 里的 XML 结构用宏或 LibreOffice 预转换扫描件 OCR 数字错误字体或分辨率问题抽样对比原图提高 DPI 到 300做字符混淆修正网页抓取为空动态渲染未等待检查 networkidle 是否触发增加显式等待或改用 API检索召回不相关分块粒度过细查看召回块的上下文改为按业务实体分块4.2 三个独家避坑技巧第一个PDF 解析一定要做页面级容错。我遇到过一份 200 页的地质报告第 87 页损坏导致整个文件解析失败。后来改成逐页 try-except坏页跳过并记录其余 199 页正常处理。这个改动让整体成功率从 60% 提升到 98%。第二个OCR 结果要保留原图坐标。PaddleOCR 返回的每个文本框都有坐标信息不要只取文字。保留坐标后可以把 OCR 结果按位置重新组装成表格比纯文本顺序准确得多。这个技巧在处理化验单时特别管用。第三个建立清洗日志和回滚机制。每次清洗都记录输入文件、输出结果、处理参数。发现某批数据检索效果差时能快速定位是哪个环节出的问题。我吃过一次亏清洗脚本改了一行正则导致三千多条数据被误删没有日志只能全部重跑。4.3 性能优化的实际数据清洗流程的性能瓶颈通常在 OCR 和 PDF 解析。我的实测数据是纯文本 TXT 每秒处理约 5000 行Word 每秒约 20 页文本型 PDF 每秒约 10 页扫描型 PDF 走 OCR 每秒只有 0.5 到 1 页。优化手段主要是并行化。OCR 用多进程按 CPU 核心数开进程池8 核机器能跑到每秒 4 到 6 页。PDF 解析用多线程因为主要是 IO 等待。这样一套组合下来一个十万页级别的资料库全量清洗大概需要两到三天可以接受。内存控制也要注意。不要一次性把所有文档加载到内存用生成器逐文件处理。我见过有人写清洗脚本把 50GB 的 PDF 全部读进内存机器直接卡死。5. 检索效果验证与持续迭代5.1 怎么判断清洗做得好不好清洗质量最终要落到检索指标上。我用的评估方法是人工构造 100 个典型查询看 Top5 召回里有多少是相关的。查询要覆盖不同数据源和不同业务场景比如“某矿区 2015 年的平均铜品位”、“ZK1201 的终孔深度”、“某矿权的有效期限”。基线是通用方案召回率大概 40% 左右。经过上面这套清洗流程后召回率能到 85% 以上。剩下的 15% 主要是图件标注和手写体识别的问题属于已知的难点。5.2 迭代方向清洗不是一次性的工作。新数据进来要按同样的流程处理同时把检索日志里暴露的问题反馈到清洗规则里。我现在的做法是每月做一次检索质量复盘把低分查询挑出来分析是分块问题、术语问题还是解析问题然后针对性优化。还有一个值得投入的方向是结构化知识库与向量库的混合检索。品位、坐标、深度这类数值型查询走结构化数据库比向量检索准得多。把两者结合先做结构化过滤缩小范围再做向量检索排序效果比纯向量方案好不少。这块我还在摸索等跑出稳定数据再单独写一篇。这套流程跑下来最大的体会是探矿数据的 RAG清洗占七分模型占三分。与其花时间调 embedding 模型不如把清洗做扎实。数据干净了用最基础的向量模型也能跑出不错的效果。