
1. 探矿资料为什么必须做RAG清洗地质勘探行业有一个很尴尬的现实数据量极大但真正能被检索和复用的比例极低。一个中型勘探项目跑下来积累的资料包括野外记录本扫描件、钻孔柱状图PDF、化验报告Word文档、老专家手写的TXT台账、以及从不同渠道下载的网页资料。这些文件散落在各个硬盘和共享目录里格式五花八门命名毫无规律。你想查一个“某钻孔在特定深度的岩性描述”只能靠人肉翻文件夹运气好十分钟找到运气不好半天都翻不出来。RAG检索增强生成看起来是解决这个问题的理想方案——把资料灌进知识库用自然语言提问系统自动检索相关段落并生成回答。但真正动手做过的人都知道RAG的效果上限不取决于大模型有多强而取决于你喂进去的数据有多干净。垃圾进垃圾出这句话在RAG场景下体现得淋漓尽致。探矿业务的特殊性在于资料格式跨度极大从纯文本TXT到复杂排版的PDF从结构化表格到自由书写的Word报告还有大量从网页复制粘贴的碎片信息。每种格式的清洗策略完全不同用一套通用流程去处理结果就是检索时要么召回一堆无关内容要么关键信息被切得支离破碎。我见过太多团队花大价钱搭了RAG框架最后因为数据清洗没做好检索精度惨不忍睹项目不了了之。这篇文章面向的是正在或准备在探矿业务中落地RAG的技术人员和地质信息化从业者。我会把TXT、Word、PDF、网页四种格式的清洗方法拆开讲透包括每种格式的坑在哪里、怎么处理、为什么这样处理。所有内容基于实际项目经验不是理论推演。2. 四种格式的清洗难度分级与核心挑战在动手写清洗代码之前先要搞清楚每种格式到底难在哪里。我把探矿业务中常见的四种格式按清洗难度做了分级这个分级直接决定了你该投入多少精力。格式清洗难度核心挑战典型问题TXT低编码混乱、无结构GBK与UTF-8混用、字段分隔符不统一Word中格式嵌套、公式图表表格跨页、公式对象无法直接提取PDF高版面还原、扫描件双栏排版错乱、OCR噪声、表格线干扰网页中高噪声过滤、动态内容导航栏广告混入、JavaScript渲染缺失TXT看起来最简单但探矿行业的TXT往往来自不同年代、不同软件导出编码问题能让你抓狂。我遇到过一批2005年左右的地质台账同一批文件里GBK、GB2312、UTF-8三种编码混着来直接读取全是乱码。Word的问题在于“看起来是文本实际是对象”——公式是OLE对象、图表是嵌入图片、表格有合并单元格直接转文本会丢失大量结构信息。PDF是公认最难啃的骨头尤其是扫描版的老资料OCR识别出来的内容需要大量后处理。网页资料的问题则是噪声太多导航栏、广告、评论区全混在正文里。提示不要试图用一种工具或一套正则规则处理所有格式。每种格式的清洗管线应该独立设计最后再统一输出为RAG可消费的标准化格式。3. TXT文件从编码地狱到结构化字段3.1 编码检测与批量转码的实操方案TXT清洗的第一步永远是编码检测。Python的chardet库可以自动检测编码但它的准确率在短文本上并不理想。我的做法是先用chardet做初步判断然后对置信度低于0.8的文件用iconv尝试多种编码转换看哪种能输出可读的中文。import chardet def detect_encoding(file_path): with open(file_path, rb) as f: raw f.read(10000) # 读前10KB做检测 result chardet.detect(raw) encoding result[encoding] confidence result[confidence] # 置信度低时尝试常见中文编码 if confidence 0.8: for enc in [utf-8, gbk, gb2312, gb18030]: try: raw.decode(enc) return enc, 1.0 except UnicodeDecodeError: continue return encoding, confidence批量转码时有一个关键决策统一转成UTF-8还是保留原编码我的建议是统一转UTF-8因为后续的RAG框架、向量数据库、大模型接口基本都默认UTF-8。转码过程中要记录原始编码万一出现乱码可以回溯。实际操作中还有一个坑有些TXT文件是“混合编码”前半部分GBK后半部分UTF-8。这种情况chardet会给出一个折中结果导致两边都乱。处理方法是按行读取逐行检测编码分别解码后再拼接。虽然慢但能保证正确率。3.2 探矿台账的字段切分与语义保留编码问题解决后下一个问题是结构。探矿TXT台账通常长这样ZK001 0-5m 第四系覆盖层 黄色黏土 松散 ZK001 5-12m 强风化花岗岩 灰白色 岩芯采取率75% ZK001 12-25m 中风化花岗岩 青灰色 岩芯采取率90% 见石英脉这种数据如果直接整段塞进RAG检索“ZK001的岩芯采取率”时模型可能只召回其中一行丢失上下文。更好的做法是按钻孔编号和深度区间做结构化切分把每行转成JSON对象{ hole_id: ZK001, depth_from: 0, depth_to: 5, layer: 第四系覆盖层, description: 黄色黏土 松散, extra: {} }这样切分的好处是检索时可以精确匹配钻孔编号和深度范围生成回答时也能保留完整的层位信息。切分规则用正则就能搞定关键是字段分隔符要统一。我见过有的台账用空格、有的用制表符、有的用竖线清洗时需要先做分隔符归一化。注意结构化切分不是越细越好。如果切得太碎检索时可能只召回“岩芯采取率75%”这一句丢失了“ZK001 5-12m 强风化花岗岩”这个关键上下文。我的经验是以“一个完整的地质描述单元”为最小切分粒度通常是一行或一个自然段。4. Word文档表格、公式与嵌入对象的处理策略4.1 表格跨页与合并单元格的还原Word文档在探矿业务中主要用于化验报告、项目总结、钻孔设计书。这些文档里最要命的是表格——化验报告的结果表往往跨页表头在第一页数据延续到第三页钻孔设计书里的地层表有大量合并单元格。用python-docx读取表格时跨页表格会被识别为多个独立的Table对象合并单元格则会出现空值。处理方法是先按表格的列数做分组列数相同的连续Table视为同一个逻辑表格然后合并。合并单元格的空值需要用“向上填充”策略处理——如果某单元格为空取同列上一行的值。from docx import Document def extract_tables(doc_path): doc Document(doc_path) tables [] for table in doc.tables: rows [] for row in table.rows: cells [cell.text.strip() for cell in row.cells] rows.append(cells) tables.append(rows) # 合并列数相同的连续表格 merged [] for t in tables: if merged and len(merged[-1][0]) len(t[0]): merged[-1].extend(t[1:]) # 跳过重复表头 else: merged.append(t) return merged这个逻辑看起来简单但实际跑起来会发现有些表格的列数因为合并单元格而“看起来不同”需要先做列数归一化。我的做法是取所有行的最大列数作为标准不足的补空。4.2 公式与图表的文本化转换探矿报告里的公式主要是品位计算公式、储量估算公式。Word里的公式是OLE对象python-docx无法直接提取文本。有两种处理路径第一种是用docx2python或mammoth这类库它们能把公式转成LaTeX或MathML。第二种是先把Word转成PDF再用PDF的公式识别方案处理。我倾向于第一种因为转换链路短信息损失小。图表的处理更麻烦。探矿报告里的图表通常是“品位等值线图”“钻孔柱状图”这些图本身包含大量信息但RAG目前对图片的处理能力有限。我的策略是图片单独存储在文本中保留图注和引用位置。比如原文是“见图3-2 品位等值线图”清洗后保留这段文字同时把图3-2的图片文件路径记录在元数据里。检索时如果命中这段文字可以提示用户查看对应图片。提示如果RAG知识库需要存储图片建议把图片转成base64编码后存入支持多模态的向量库同时在文本chunk里保留图片的描述性文字图注、标题、关键结论。纯文本RAG无法直接检索图片内容但可以通过图注文字间接关联。5. PDF解析版面分析与扫描件OCR的取舍5.1 双栏排版与表格线的干扰排除PDF是探矿资料里最复杂的格式。老资料是扫描件新资料是排版软件导出的电子版。电子版PDF的解析难点在于版面分析——地质报告通常是双栏排版直接按文本流读取会把左右两栏的内容交错在一起读出来完全不通顺。处理双栏PDF我推荐用pdfplumber做版面分析。它可以把页面按x坐标切分成左右两个区域分别提取文本。关键参数是layoutTrue和自定义的bbox裁剪区域。import pdfplumber def extract_two_column(pdf_path): with pdfplumber.open(pdf_path) as pdf: full_text [] for page in pdf.pages: width page.width left page.crop((0, 0, width/2, page.height)) right page.crop((width/2, 0, width, page.height)) left_text left.extract_text() or right_text right.extract_text() or full_text.append(left_text \n right_text) return \n.join(full_text)表格线是另一个干扰源。PDF里的表格是用线条画的extract_text()会把表格内容按阅读顺序拼成一行丢失行列关系。这时候需要用extract_tables()单独提取表格再和正文合并。5.2 扫描件OCR后的噪声清洗扫描件PDF必须走OCR。我常用的是PaddleOCR对中文地质资料的识别率不错。但OCR出来的文本有大量噪声页眉页脚被识别成正文、表格线被识别成“—”“|”、手写批注识别错误。清洗OCR结果需要一套组合拳页眉页脚过滤统计每页开头和结尾的重复文本出现频率超过80%的视为页眉页脚删除。短行合并OCR经常把一行字切成多行需要根据标点符号和语义合并。乱码字符过滤连续出现3个以上非中文非英文非数字的字符大概率是识别错误标记后人工复核。数字校验地质数据里的数字很关键OCR容易把“0”识别成“O”、“1”识别成“l”需要用正则做模式校验。import re def clean_ocr_text(text): # 过滤连续乱码 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。、\s]{3,}, , text) # 修正常见数字误识别 text re.sub(r(?\d)[Oo](?\d), 0, text) text re.sub(r(?\d)[lI](?\d), 1, text) # 合并被切断的句子 text re.sub(r([^。\n])\n([^A-Z0-9]), r\1\2, text) return text注意OCR清洗没有“一键完美”的方案。我的做法是清洗后随机抽10%的页面人工检查如果错误率超过5%就调整OCR参数重新跑而不是硬着头皮往下走。6. 网页资料正文提取与动态内容补全6.1 导航栏、广告与评论区的剥离从网页抓取的探矿资料最大的问题是噪声。一篇地质论文的网页版正文可能只有3000字但导航栏、侧边栏、广告、评论区加起来有10000字。直接全文抓取塞进RAG检索精度会被严重拉低。正文提取我推荐用trafilatura它对新闻和学术网页的正文识别准确率很高。如果trafilatura效果不理想可以用readability-lxml作为备选。两个库的核心逻辑都是找页面上文本密度最高、链接密度最低的DOM节点那个节点大概率是正文。import trafilatura def extract_web_content(url): downloaded trafilatura.fetch_url(url) text trafilatura.extract( downloaded, include_commentsFalse, include_tablesTrue, no_fallbackFalse ) return textinclude_commentsFalse会剥离评论区include_tablesTrue保留表格。这两个参数对探矿资料很重要——化验数据表要保留但网友评论要扔掉。6.2 JavaScript渲染页面的抓取方案有些地质数据库网站是JavaScript动态渲染的直接请求HTML拿不到内容。这时候需要用Playwright或Selenium模拟浏览器行为。我倾向于Playwright因为它对异步加载的处理更自然。from playwright.sync_api import sync_playwright def fetch_dynamic_page(url): with sync_playwright() as p: browser p.chromium.launch() page browser.new_page() page.goto(url, wait_untilnetworkidle) content page.content() browser.close() return contentwait_untilnetworkidle表示等网络请求完全停止后再抓取适合数据异步加载的页面。但要注意有些页面有无限滚动或定时刷新networkidle可能永远等不到这时候需要设置超时或改用domcontentloaded加手动等待。7. 清洗后的统一输出与RAG入库衔接四种格式清洗完最终要输出成RAG能消费的格式。我的标准输出是JSONL每行一个chunk包含以下字段{ chunk_id: zk001_5_12, source_file: 钻孔台账_2005.txt, source_type: txt, content: ZK001 5-12m 强风化花岗岩 灰白色 岩芯采取率75%, metadata: { hole_id: ZK001, depth_from: 5, depth_to: 12, layer: 强风化花岗岩 } }content是用于向量化的文本metadata是用于过滤和精确匹配的结构化字段。RAG检索时可以先按hole_id或depth范围做元数据过滤再在过滤结果里做向量相似度检索精度会大幅提升。切分粒度上我的经验值是每个chunk 200-500字。太短丢失上下文太长向量化后语义被稀释。对于结构化数据如台账按行切分对于叙述性文本如报告按段落切分段落超过500字的再按句子边界切分。提示清洗后的数据一定要做一轮抽样验证。随机抽50个chunk人工判断“如果用户问XX问题这个chunk能不能被召回并支撑回答”。如果召回率低于70%说明切分或清洗有问题需要回头调整。8. 我踩过的坑与精度提升心得第一个坑是过度清洗。早期我做PDF清洗时把所有的换行符都删了结果表格数据和正文混在一起检索时完全分不清哪是表头哪是数据。后来我学会了保留结构标记比如用[TABLE]和[/TABLE]包裹表格内容用[PAGE_BREAK]标记分页。第二个坑是忽略元数据。一开始我只存文本内容不存来源和结构信息。结果检索到一段“岩芯采取率75%”但不知道是哪个钻孔、哪个深度。后来强制要求每个chunk必须带来源文件和关键元数据检索精度直接上了一个台阶。第三个坑是OCR语言模型选错。探矿资料里有大量专业术语和地名通用OCR模型识别率很低。换成针对地质领域微调过的OCR模型后专业术语的识别准确率从60%提升到了90%以上。如果找不到领域模型至少要在OCR后加一个专业词典做纠错。第四个坑是网页抓取频率过高被封。批量抓取地质数据库时没控制请求间隔IP被临时封禁。后来加了随机延迟1-3秒和User-Agent轮换问题解决。这个坑不涉及技术难度但很影响进度。精度提升方面最有效的措施是混合检索向量检索加关键词检索BM25两路结果做加权融合。纯向量检索对专业术语的匹配不够精确加上BM25后像“ZK001”“花岗岩”“岩芯采取率”这类关键词的召回率明显提升。权重我一般设向量0.7、BM25 0.3具体根据数据特点调。另一个有效做法是查询改写。用户问“ZK001的岩芯采取率是多少”直接检索可能召回多个钻孔的数据。我加了一个轻量的查询解析步骤用正则提取钻孔编号和指标名称先做元数据过滤再做向量检索。这个改动让检索准确率从65%左右提升到了85%以上。最后分享一个实用技巧建立清洗质量监控看板。每次清洗完一批数据自动统计chunk数量、平均长度、元数据完整率、乱码率等指标和历史数据对比。如果某个指标异常波动说明清洗流程可能出了问题。这个看板不复杂用Python脚本加一个简单的HTML报告就能搞定但能帮你提前发现很多隐患。