首页 / 资讯中心 / 文章详情

矿山地质资料RAG清洗实战:从乱码到高精度检索

矿山地质资料RAG清洗实战:从乱码到高精度检索 ★ FEATURED ARTICLE
进了矿山地质资料那批老文档之后我第一反应是头痛。几百份 TXT、Word、PDF 混在一起还有从网页上抓下来的公告和矿权信息字符一会儿是乱码一会儿是半截表格标题里写“从乱码到高精度检索”这句话基本就是我接手 RAG 清洗工作的真实写照。这里面的 RAG指的就是检索增强生成那套技术路线但很多人一上来就追着模型和向量库调参忽略了最前面的清洗环节。探矿业务的特点是资料杂、专业词多、格式老如果不先把 TXT、Word、PDF 和网页四类来源的乱码和结构噪声处理干净后面做高精度检索就是空中楼阁。这篇文章不会写成教科书式的流程文档。我只讲我在实际项目中拆解乱码、整理文本、构建检索链路的完整思路每一步都对应可复现的操作方案。适合正在做 RAG 落地、尤其是地质矿产类文档处理的技术人员参考也适合刚接触数据清洗、想搞懂为什么解析之后文本还是乱的初级工程师。1. 探矿资料入RAG前乱码与错字如何拖垮检索精度先说一个反直觉的结论多数 RAG 项目准确率上不去问题不出在模型出在文档进入向量库之前的那一步。Embedding 模型见过大量正常文本但它不会知道你这份文档里藏着多少个解码错误字符也不会自动帮你把错位的表格行重新拼回去。乱码一旦进入向量化环节就会被当作正常语义进行编码检索时自然就南辕北辙。1.1 乱码不是小概率问题而是数据现状探矿业务的文本来源极其特殊。很多老矿山资料是当年在 DOS 环境下录入的保存为 GBK 编码的 TXT后来从档案系统导出的文件也可能是 GB2312 或 GB18030。还有一部分 Word 文档经过了多次转存某些段落用了旧版公式编辑器页面里还嵌着扫描图。PDF 的情况更复杂有的有文本层有的干脆就是扫描件整页变成了图片。再加上从政府公示、行业网站抓取的网页动态渲染、编码声明与实际内容不符的情况比比皆是。这些资料混在一起如果不做针对性清洗你看到的现象就是检索“XX矿区ZK0301钻孔见矿深度”返回结果里出现大量“”字符或者把第二页页眉当成正文切片。更隐蔽的问题是中文标点被转成英文标点后没影响肉眼阅读但会让模型语义向量出现偏移专业术语“g/t”与“克/吨”之间明明等价检索系统却认为它们是两个完全不同的概念。1.2 乱码对四种检索能力的伤害程度不同我一般把探矿 RAG 的检索分四类来评估全文本语义检索、关键词精确匹配、表格数据查询、坐标和数值范围查询。乱码对这四种模式的影响并不一样。语义检索最怕“字符层面残缺”。一句“Cu平均品位1.24%”如果变成“Cu平均品1.24%”Embedding 模型会把“品”和“1.24”之间的距离拉远可能最终召回的段落是“该矿区地下水流向为北东”因为这句话里同样有“Cu”和“1.24”相关的地球化学背景词语义空间错配了。关键词精确匹配则被“全角半角”和“编码替代字符”直接击穿。“ZK0301”写成“”关键词就检索不到。坐标格式也是重灾区“40°30′30″”和“40.5083°”表达的是同一个位置但字符串完全不同。探矿检索场景里这些数字不是普通的数字是判断矿体走向、孔深、品位的关键索引一旦解析出错后面所有分析都建立在错数据上。表格数据查询主要依赖结构完整性。PDF 里的化验结果表如果提取出来变成一长串文本行和列全部错位表面上看数据都在实际上字段名和数值根本对不上。RAG 的检索粒度是“语义相近的句子”它无法根据“Cu”这个表头自动知道下一列是“Au”所以你必须提前把表格转化为有明确列分隔的文本甚至直接走结构化存储。数值范围查询更直接它要求数字必须保持数字形态。清洗过程中一旦用 errorsignore 这种参数把非法字符静默丢掉百分号、小数点、负号就可能一起消失。这造成的后果比乱码还严重因为至少乱码还能提醒你信息丢了静默丢弃是无声的。2. 四大来源逐个拆TXT编码、Word对象、PDF扫描、网页动态内容乱码的成因不同清洗办法就不能一套打天下。我按来源分开处理每一类都先判断它最容易坏在哪个环节再决定用什么工具链。2.1 TXT 老档案编码是原罪先做编码识别再统一老探矿档案里的 TXT 大多数是 GBK 编码但也存在少量 UTF-16 和 GB18030。最保险的做法不是让 Python 猜一次编码就结束而是把几种常见编码都尝试一遍再用汉字比例、连续可读长度、特殊符号密度做一轮质量打分。import chardet raw open(zk0301_old.txt, rb).read() guess chardet.detect(raw) print(guess) # 常见结果{encoding: GB2312, confidence: 0.99} try: text raw.decode(gbk) except UnicodeDecodeError: text raw.decode(utf-8, errorsreplace)这里有个关键细节不要用 errorsignore。一旦忽略非法字符整个数字串可能断成两截而你根本不知道断在哪里。我建议统一用 errorsreplace把无法解码的字符替换成特殊占位符比如“”然后在清洗阶段用正则把连续的占位符行挑出来交给人工或 OCR 流程二次确认。编码统一之后还要处理 TXT 里常见的“旧式表格”。这类文本一般用空格或制表符对齐列内容肉眼看着是整齐的表格但输入到模型里只是一堆空白字符。我的做法是把连续两个以上空白字符统一替换为制表符再用 pandas 按行拆分保留分隔符结构而不是让所有内容糊成一段话。2.2 Word 文档公式、图片、批注和宏都可能偷走内容很多人以为 Word 转 txt 是最简单的实际上 Word 在探矿业务里最容易丢内容。地质详查报告经常包含大量公式比如储量计算的加权平均公式、抗压强度表达式这些内容如果是以旧版公式对象或 MathType 公式形式存在的python-docx 默认是读不到文本的。你取到的段落可能是空的或者只有公式前后的说明文字。比较可靠的路线分两步。第一步用 LibreOffice 命令行把 docx 转成 txt 或纯文本期间关闭宏执行避免来自外部文档的宏在转换过程中触发意外操作。第二步对仍无法解析的公式对象做专项提取。公式在 docx 内部其实是一段 OMML 或嵌在 document.xml 里的对象引用你可以用 python-docx 遍历所有的 oMath 节点把它转成 UnicodeMath 或 LaTeX 文本再把 LaTeX 字符串作为该段的附加内容接回去。soffice --headless --convert-to txt:Text --outdir ./clean_txt ZK0301_详查报告.docx这里强调一下安全问题。Word 宏在纯文本提取阶段是可以躲开的但如果你用 Windows COM 的方式调用 Word 打开文档再另存为纯文本宏可能被执行。地质资料经常在多个单位之间互相传输你不知道这份 docx 里的宏到底写了什么。稳妥起见所有 Word 转换一律放在禁用宏的模式下或者直接放到 Linux 环境里跑 LibreOffice。表格是另一个坑。探矿报告中的钻孔岩芯采样表、水文观测表往往嵌在文档的文本框里python-docx 默认遍历不到文本框内部。你要做的是写一个递归函数把 document.element.body 下的 w:txbxContent 全部找出来再继续解析其中的段落和表格。我第一次做的时候没处理文本框结果一份报告少了三分之一的数据检索精度直接崩到没法看。2.3 PDF 扫描件与文本层混合体提取密度阈值决定走OCR还是直接取PDF 在探矿资料里严重两极分化。新版电子勘查报告有完整文本层老扫描图件则完全没有。前端时间我看到有人推荐用 PyMuPDF 一步搞定但拿到扫描件立刻翻车因为根本没有文本层。我的判断方式很简单对每一页先提取文本计算非空格、非换行字符的数量如果低于该页面积的某个阈值就判定为扫描页交给 OCR。Python 里我习惯用 PyMuPDF 快速抽文本层用 pdfplumber 处理表格边界。PyMuPDF 的 text 提取速度快适合大规模过滤pdfplumber 读取的表格结构更接近视觉上的行列适合处理化验结果表。import fitz doc fitz.open(2023_ZK0301_岩芯化验.pdf) for page_no in range(len(doc)): page doc[page_no] txt page.get_text(text) txt_stripped .join(txt.split()) if len(txt_stripped) 20: print(fpage {page_no1} 疑似扫描页)扫描页 OCR 并不复杂探矿资料里的正文多为印刷宋体字PaddleOCR 中文识别效果已经足够。难点在图表注释和小字号数字上地图、剖面图、柱状图上的钻孔编号和标高数字非常容易被认错。我通常先把页面图像放大到 300 DPI再转灰度、做一次去噪然后才交给 OCR 识别。识别之后的文本还带着坐标位置可以用坐标信息把同一表格框内的文字重新拼接避免表格内容被 OCR 输出成多列混排。最后还要处理 PDF 中“嵌入式网页打印版”。有些来源是网页转成的 PDF页眉页脚、目录、链接全部带进来提取之后满页面都是网址和导航文字。这种 PDF 清洗时要把页眉、页脚按页面位置过滤只保留中间正文区域同时对连续的页码行做正则删除。2.4 网页内容动态渲染和编码跳变是主要来源探矿 RAG 的语料库不可能只用本地文件矿权公示、行业新闻、招投标信息都要从网页抓取。网页清洗的核心问题是“抓到的东西和浏览器看到的不一样”。静态 HTML 明明没有数据新闻正文是通过 JavaScript 异步加载出来的如果你用 requests 直接拿 HTML得到的是一堆 script 标签和毫无意义的外壳。正确做法是分两层抓取。第一层先用 trafilatura 或 readability-lxml 抽取正文候选区把 script、style、nav、footer 全部剥掉。第二层如果正文区为空或异常短再用 Playwright 或 Selenium 无头浏览器加载一次等网络请求完成后再提取。探矿公示类页面的表格通常是服务端渲染用 requests 就能拿到新闻类和行情类页面则多半是前端渲染必须走无头浏览器。import trafilatura downloaded trafilatura.fetch_url(https://example-mining-page/notice) text trafilatura.extract(downloaded, include_commentsFalse, include_tablesTrue, favor_recallTrue)编码方面网页最坑的是“声明编码与实际编码不一致”。HTML 的 meta 标签写的是 UTF-8实际内容却是 GBK这种文件直接喂给解析器前几百字节正常后面的中文全变乱码。我在抓取时会把响应头里的 charset、HTML meta 声明的 charset、字节层面的实际编码结果三个信号放在一起判断以 chardet 给出的置信度为主如果 chardet 说 GB18030 而非页面声称的 UTF-8就强制用 GB18030 解码。网页里的图片注释要单独处理尤其是工程图纸扫描件转成的网页图片。这时候必须走 OCR 流程把图片里矿区边界线上的地名、坐标点识别成文本再把 OCR 结果作为该网页的额外文本字段挂在同一份文档 ID 下。这样检索时既能看到网页正文也能检索到图片内部的地名信息。3. 从清洗到入库我用pandas与SQL做的三层数据治理文本解析完成只是清洗的第一步后续还需要把清洗后的内容组织成适合入库的结构。我把这一步拆成三层去重、规范化、低质段落标记。三层做完文本才能干净利落地进入向量化和检索环节。3.1 去重不只看标题指纹碰撞才可靠探矿资料特别喜欢重复。同一份报告可能同时存在 Word 版和 PDF 版内容一字不差但文件头、页眉、版本编号略有不同。如果只按整个文件 MD5 去重这两份会同时进入知识库检索时重复段落占用大量召回窗口明明只有一个答案却频繁返回两段相似文本徒增误导。我的做法是用规范化哈希。先把整篇文本做一层标准化全角转半角、转换成统一换行符、去掉所有空白字符、过滤掉页眉页脚然后计算 SHA256。Word 和 PDF 经过不同解析器提取出来的文本在标点、空格上会有一点差异但经过规范化之后两者应该能撞上同一个指纹。对于部分逻辑复制导致的内容性重复比如 A 章节从旧报告中复制了整段但段落开头改了两个字哈希指纹会失败这时再用 SimHash 或 MinHash 计算局部相似度。在 pandas 里可以先把每篇文档的行拆成短词窗口计算每行的 simhash再两两比较。这样能揪出 90% 以上的整段复制文本。3.2 规范化和元数据注入矿区编号、坐标格式、单位统一清洗到这一步文本内容基本是正常人了但“正常”不等于“可检索”。探矿文本有很强的约定俗成特征比如品位单位“g/t”在不同报告里可能写成“克/吨”“克每吨”“g·t⁻¹”经纬度有度分秒和十进制度的差异甚至钻孔号“ZK0301”在表格里被写成“ZK-03-01”。这些表达差异会直接拉低关键词检索的精度。我在 pandas 里建了一张映射表用正则把常见的异体写法统一。单位统一放在文本清洗之后、切块之前因为切块之后再做替换容易破坏分块边界。import pandas as pd df pd.read_csv(parsed_docs.csv) df[content] df[content].str.replace(r[gG]\s*[·/tT]\s*, g/t, regexTrue) df[content] df[content].str.replace(r克/吨|克每吨|克吨, g/t, regexTrue) df[content] df[content].str.replace(r[(]?ZK[-]?0*([0-9])[)]?, rZK\1, regexTrue)这种替换必须基于对探矿业务的理解不能只看通用文本清洗规则。如果不知道“ZK0301”和“ZK-03-01”在矿区台账里可能指同一个孔号这条规则就不会被写出来。所以 RAG 清洗不是纯数据处理任务还要配合业务人员梳理一份“专业术语归一化对照表”至少覆盖品位单位、坐标格式、钻孔编号、地层代号和主要矿物名称。3.3 低质量段落标记让后续切块绕开噪声区不是所有文本都值得进入向量库。探矿文档里大量存在页眉页脚、表格序号行、纯页码、录入时遗留的空行和背景说明文字。与其在切块阶段依赖模板去猜不如在入库前就把低质量段落标记出来。我用一个简单的打分函数统计每段文本的汉字占比、数字占比、符号占比、是否只包含数字与空白、是否包含页眉特征词。得分低于阈值的文本段落打上 low_quality 标签放进单独字段。切块器读取到该字段时直接跳过或只在整篇缺少正文时才考虑使用这些段落。这个步骤对检索精度的影响非常明显。我测试过一个包含大量 OCR 误识别的扫描版报告把低质量段过滤后相同问句的检索命中率提升了 21%。原因很简单乱码和噪声段落占据向量空间的区域太广把真正的矿石特征词淹没在无关向量里了。4. 切块与检索精度清洗后的文本在RAG管线的最后100米很多人以为文本清洗完了就大功告成其实切块和检索之间的配合同样决定最终精度。探矿文本的切块不能用“固定 512 字一刀切”因为地质报告中章节语义长度差异巨大一句话可能就十几个字但信息密度极高一个章节表格可能连续上百行却只围绕同一钻孔。4.1 切块策略按文档结构分层再按长度微调我的优先策略是“标题优先切块”。先把清洗后的文档按标题层级拆成一棵目录树标题在探矿报告里就相当于勘探阶段、矿区范围、地质构造、矿体特征、储量计算这些语义边界。每一个二级标题下的内容作为一个候选块如果长度超过模型上限再按段落向前滑动滑动步长控制在文本窗口的三分之一左右。这样切出来的块有一个好处每一块的语义是完整的不会把一个钻孔柱状图从中间拦腰截断。钻孔柱状图的数据表经常在“ZK0301”和“ZK0302”之间有明显标题分隔标题优先切块能天然保留这种边界。表格在切块前需要特殊处理。我用 pdfplumber 或 pandas 把表格转成 tab 分隔的纯文本后会显式加入列头重复逻辑如果表格超过一定行数切块时每隔若干行重复一次表头。否则模型在检索“品位”时看到的可能只是中间行数据却不知道这一列对应的是哪个元素。4.2 元数据注入把矿区、孔号、坐标、文件类型塞进检索过滤条件高精度检索不能完全依赖语义相似度元数据过滤可以帮你把检索范围从“整个知识库”缩小到“某个矿区、某份报告、某个钻孔”。比如用户问“ZK0301 的见矿深度”如果元数据里已经拆出 doc_name台帐.xlsx、drill_idZK0301、file_typeexcel检索系统就能先过滤出相关候选集再让向量模型做精排。我把元数据字段设计成两套。一套是文件级元数据包括文档来源、文件格式、入库时间、矿区编号、版本号另一套是块级元数据包括所在二级标题、页码、表格是否存在、是否含坐标。块级元数据里的“是否含坐标”特别有用因为探矿问题的坐标相关性很强用户问“钻孔孔位坐标”时某些干巴巴的岩石描述文本虽然包含经纬度但根本不是答案来源。4.3 评估闭环用30个业务问句定期回测清洗质量清洗做得好不好最终要靠业务问题来验证。我每个项目都会建立一个包含 30 个左右真实问题的评估集覆盖矿区查询、钻孔参数、品位、坐标、储量、矿石类型这几个维度。每次清洗管线改动之后就把这些问题跑一遍对比检索命中的 top5 中有多少比例是正确段落。例如“XX矿区ZK0301钻孔见矿深度多少米”“辉绿岩地层厚度数据在哪份报告里”“Mo元素平均品位超过 0.05% 的样品集中在哪个孔”“某矿区的边界坐标是多少”。这些问题如果命中率在清洗前后没有显著变化说明清洗策略可能还没打中要害。我把前后对比记录成一张简单表格。问句类型清洗前top5命中率清洗后top5命中率主要改善原因钻孔参数58%82%Word文本框内容补全 去重品位与元素44%79%单位归一化 表格结构还原坐标范围36%71%OCR修复 坐标格式统一储量计算51%86%PDF文本层提取 低质段过滤这个评估集建议沉淀成独立脚本每次抓完新网页或转完新 PDF先跑一遍稳了再增量入库。否则今天刚跑的准确率是 80%明天灌入一批没有清洗的网页又跌回 60%你都不知道是哪一批数据惹的祸。5. 可以直接抄的坑位清单与配置踩坑记录比原理更有价值这一节把我在探矿业务 RAG 清洗过程里遇到的高频问题压缩成清单按环境、工具、数据三类分开。5.1 环境坑Windows、Linux、Mac 的转换差异要提前锁定探矿单位的技术环境非常杂有人用 Windows 服务器有人用 Mac 笔记本有人用 Linux 容器做定时任务。同一个 LibreOffice 转换命令在 Windows 上需要注意文档被 Word 进程占用的问题Mac 上则是字体路径和中文文件名的问题Linux 容器最常缺中文字体导致 PDF 提取出的空格异常多还是其次OCR 出来的汉字全是方块更致命。我建议把清洗这条链路的运行环境在项目一开始就固定下来。如果你要在 Mac 或 Windows 上做调试最后部署到 Linux至少要提前安装好中文字体比如 Noto Sans CJK、文泉驿正黑并且把字体缓存重建一次。不要等到跑完 5000 份 PDF 才想起来看 OCR 效果。5.2 数据坑页眉页脚、目录、修订标记都会伪装成正文有几类文本特别容易逃过清洗。第一是目录页Word 自动生成的目录在转纯文本后保留了大量点线和页码看起来像正文实际全是重复信息第二是修订和批注DOCX 解析时如果没排除 w:ins、w:del 节点会把修改前后的重复内容全部带进纯文本第三是 PDF 的链接注释文本部分 PDF 提取后会把超链接地址作为正文追加到段落里。针对目录我一般按“目录”标题位置定位把目录区直接截断针对修订和批注python-docx 读取时要用解析器区分插入和删除节点通常只保留删除前的原文或删除后的最终内容具体看业务文档基线针对 PDF 链接提取用 PyMuPDF 的 page.get_text() 时关闭 linksTrue 选项或者提取后正则过滤掉以 http 开头的孤儿文本。5.3 清洗前置检查单每批新文档上线前过一遍我最后保留的检查单只有十项但每项都能避免一次线上翻车。每批新文档入库前我都会跑一遍这个检查单。编码统一为 UTF-8原始编码信息记录在源的属性字段里。全角标点全部转为半角数字与单位之间的空格规则固定。用替换符替代 ignore 式错误处理乱码行标记不删除。PDF 按页提取文本密度评分扫描页走 OCR 流程。Word 转换时禁止宏执行文本框内容递归提取。表格统一转成 tab 分隔文本长表格明确保留列头。网页抓取先过滤 script、style、nav再抽正文。指纹去重与 SimHash 局部相似度双跑。坐标、品位、钻孔号等专业术语统一为对照表形式。跑 30 个核心问句评估集top5 命中率达标后再增量入库。这套检查单看起来繁琐但第一次跑通之后基本就是自动化脚本的事。真正花时间的反而是“业务对照表”的维护需要和总工办、地质组不断地核对单位、编码、术语版本。清洗不是一次性的活它更像是一套随语料库扩大的持续过程。我现在的习惯是每收到一批新资料先拿检查单过一遍再入库。只要这一步稳住了后面的切块、向量化、重排和 RAG 生成才能站得住脚。从乱码到高精度检索的距离往往不是模型的参数量而是你在清洗阶段有没有舍得花时间。
阅读完成 · 觉得有帮助?
咨询建站