ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

探矿RAG知识库:从乱码到高精度检索的文档清洗实战指南

探矿RAG知识库:从乱码到高精度检索的文档清洗实战指南 在探矿业务里做RAG知识库最让人头疼的从来不是模型选型而是入库前的数据清洗。我记得第一次把二十多年的地质资料统一送进向量库时检索回来的内容大段大段都是“锟斤拷”“”甚至把一个钻孔的坐标跟你问到的人物简介混在一起。当时我还以为是Embedding模型不行后来排查到源头才发现一堆TXT、Word、PDF和网页文档带着各自的编码、排版和结构缺陷就这么不处理地进了管道再好的向量模型也救不回来。这篇文章就围绕这个场景把从乱码到高精度检索的清洗路径完整讲一遍。如果你正在搭矿业、地质、勘探类的RAG知识库或者只是被一堆老格式文档折磨过这篇内容应该能帮上忙。1. 为什么探矿资料难倒了一堆RAG项目地质队、矿企这些年攒下来的资料基本就是一部格式演进的活历史。我手头上处理过的项目里既有上世纪九十年代用针式打印机打出来、再扫描成黑白PDF的纸质报告也有从老式工控机里导出的纯TXT日志还有近年用Word/WPS排版的成果报告以及散落在政府网站、行业论坛里的网页资料。这些资料一旦要喂给RAG知识库第一个迎面而来的问题就是乱码。乱码不是单纯的“看不懂”。在RAG语境下乱码意味着分块错乱、向量化失真、检索召回率暴跌。你问知识库“某矿区钻孔ZK301的品位数据是多少”它答非所问甚至直接说“资料中未找到”十有八九不是模型的问题而是入库之前的清洗没做到位。很多团队把精力花在调Embedding模型、调Chunk Size上却忽略了上游的清洗环节等上线后发现检索效果差回头排查才意识到原始文档根本没法用。这篇文章不聊泛泛的RAG原理就聚焦一件事探矿业务中文档的清洗与结构化。1.1 探矿资料到底特殊在哪说到清洗很多通用教程教的是“用Pandas清洗CSV、去重、处理空缺值”。但在探矿业务里情况完全不同。这个领域的资料有几个绕不开的特点第一来源极度混杂。钻孔编录、化探采样、物探测量、岩矿鉴定、储量估算各环节产出的文件格式和模板都不一样。有的单位习惯把所有数据都塞进Word表格有的老师傅坚持用纯文本记录有的人只交PDF。更麻烦的是同样的内容在不同格式里含义可能不同比如“品位”在化验报告里是百分比数值在资源量估算表里可能变成克/吨清洗时如果统一转文本很容易丢失单位与量纲的语境。第二表格是命根子。探矿业务中最值钱的信息往往不在大段文字里而在密密麻麻的表格中。钻孔坐标、见矿深度、样品化验结果、岩芯编录描述绝大多数是表格化数据。通用的RAG清洗管道如果按段落切分会把一行行的数据拦腰切断检索时永远凑不齐完整的一条记录。第三专业符号多。地层时代如Pt2、J1、化学元素Fe、Au、Cu、矿物代号Qz、Py、坐标系统北京54、西安80、CGCS2000……这些符号在普通清洗规则里常常被当成噪声删掉。我之前遇到一个项目清洗脚本把“Au 1.25 g/t”里的“g/t”当乱码剔了结果那批数据入库后所有品位相关的提问都检索不到教训很深。第四历史资料质量参差不齐。有些老扫描件连OCR都识别不出地层柱状图里的细线划分有些电子文档当年在多种操作系统、编码之间倒腾过文件头全是乱字节。所以探矿业务的RAG清洗本质上不是“清洁工”干的活而是需要既懂文档解析、又懂一点地质专业背景的“精洗”工作。1.2 清洗目标不是把文档变干净而是让检索变准先明确一件事清洗的最终产物不是一份“看起来正常”的纯文本而是一套分块友好、语义保留、可检索的结构化文档。说得再直白一点清洗的目标是让后续的chunking、embedding、retrieval这三段都能正常工作。举个例子。同一份PDF报告如果清洗后是连续几百KB的无格式大文本RAG系统要么因为超过上下文窗口截断要么按固定长度切分时把表格、图件说明切开检索召回自然差。但如果清洗后保留了标题层级、识别了表格边界、补全了断行和编码错误同样的内容切分出来的chunk就干净很多向量检索的命中率会有肉眼可见的提升。我在实际项目中把清洗目标拆成四条编码正确、结构完整、噪声消除、语义保留。编码正确是底线结构完整保证切分合理噪声消除避免向量污染语义保留保证专业信息不丢。这四条不全是技术问题每个都要结合业务语言来取舍。2. TXT、Word、PDF、网页四种格式的清洗拆解2.1 TXT编码是第一道鬼门关TXT看起来最简单其实在探矿业务里最容易翻车。原因很简单老文件太多编码太杂。上世纪九十年代到本世纪初的资料最常见的编码是GBK/GB2312但有些从Unix工作站、DOS系统、甚至进口仪器导出的是Latin-1、UTF-16甚至自定义编码。你用UTF-8的默认方式去读屏幕上立刻全是“锟斤拷”“烫烫烫”。我的做法是三层检测第一层用chardet做自动编码检测这能解决80%的常规情况但要提醒的是chardet对短文本、数字占比高的文件经常误判。比如钻探班报表这种纯数字文件chardet可能检测成ISO-8859-1实际上人家是GBK。所以不能全信检测结果要配合人工抽样验证。第二层搞一个“探矿术语特征检测”。我会内置一个地质专名表包括“钻孔”“品位”“矿体”“围岩”“蚀变”“见矿”“终孔”这些高频词用不同编码去解码文件的前几百个字节看哪个编码能解出最多的术语命中。这个方法朴素但极其好用尤其是对那种几十行、几百行的短文件。第三层实在检测不出来直接看二进制。用xxd或者vbindent查看文件头几个字节UTF-8有0xEF 0xBB 0xBFUTF-16有FF FEGBK没有明显的BOM。加上术语检测一般能覆盖95%以上的情况。TXT清洗还有两个环节容易被忽略一个是“断行合并”。很多老TXT是从打印输出重定向来的一行物理行被硬折成两行如果直接按行切分完整句子被拆散RAG检索时会频繁截断。我会用一个简单规则如果某一行结尾不是句号、分号、冒号、右括号标点且下一行开头是小写字母、数字或物理回车就判定为折行做合并处理。另一个是坐标与数据行的保留。探矿TXT里经常出现“ZK301 4356789.12 37654321.45 123.45”这样的测线坐标行清洗时不能因为它们“不像自然语言”而丢弃反而要专门标记为“数据行”后续切分时整行保留。实际操作中我还会用一小段Python脚本来辅助判断编码类似下面这个思路import chardet raw open(zk301.txt, rb).read() detected chardet.detect(raw[:2000]) print(chardet result:, detected) # 地质术语特征检测 feature_words [钻孔, 品位, 矿体, 围岩, 蚀变, 见矿, 终孔] for enc in [gbk, utf-8, utf-16, latin-1]: try: text raw.decode(enc) hit sum(1 for w in feature_words if w in text) print(enc, hit:, hit) except UnicodeDecodeError: print(enc, failed)有点时候编码检测会给出多个候选这时候就用“哪种编码解出来的专业术语多”作为最终标准。这套组合拳用下来我还没遇到过完全解不开的TXT文件。2.2 Word结构抽取比文字提取更重要Word文档在探矿业务里属于“看起来友好、实则很坑”的格式。python-docx能读取段落、表格但它默认只吐纯文本你拿到的是一串没有层级、没有表格边界的字符串。真正的问题在于从Word到RAG chunk之间至少要经历两次信息重组——先是文档结构识别再是内容降噪。我踩过的一个典型坑是“表格标题识别”。很多探矿Word报告里表格上方一行“表3-1 岩芯矿化蚀变一览表”是小四号加粗正文也是小四号python-docx读出来没有任何样式差异单纯的文本提取会把这个表格标题混在前后段落里。后续切分时表格标题和表格内容被分配到不同chunk检索“蚀变一览表”相关内容时总是召回不全。我的处理方式是在解析Word时先把文档对象树遍历一遍区分Paragraph和Table对象用Table在文档中的顺序号和上下文给它重建一个“虚拟标题”同时利用docx内置的Heading样式先把各级标题单独拎出来作为chunk切分的锚点。另一个问题是表格本身。Word表格从“视觉上的行列表”变成“RAG需要的文本序列”方向不同方案完全不同。按行转文本是最常见的做法但遇到合并单元格、跨页表格、竖排表头按行转会乱。我现在的方案是先判断表格类型——如果行首是编号、列名是专业字段名如“孔号”“采样编号”“Au(×10⁻⁶)”就按“每行一条记录”转成JSON或键值对文本如果表格用于排版比如页眉处的留白、图件图例直接丢弃不给chunking添乱。还有一类Word文档带公式。探矿业务中涉及资源量估算、加权平均品位计算时会有公式。Word原生公式OMML格式直接提取通常是一段XML不可读。我的妥协方案是有公式的Word先交给pandoc转成Markdown公式会变成LaTeX表达式然后保留LaTeX原文作为文本存储不做公式求值。因为RAG要的是“能检索到公式对应的概念和上下文”而不是做符号推理。转换过程中注意检查pandoc是否丢失了表格线、批注、修订记录。批注和修订记录在清洗时建议全部剥离这些内容既不是原文又容易污染向量。如果你手头的Word版本比较杂还有一个细节老版本的.doc文件python-docx读不了需要先用LibreOffice转成.docx再解析。这一步也要纳入管道否则文件流转到解析环节才发现打不开又得回头补转换浪费时间。2.3 PDF两类文本两种打法OCR必须按需启用PDF是探矿资料的重灾区。我把PDF分成两类文本型PDF数字生成的、可选中文字和扫描型PDF纯粹的图片。不同来源、不同年代的资料往往类型混杂一个目录下几十份PDF可能两类各占一半。文本型PDF的解析我推荐先用PyMuPDFfitz做快速文本抽取因为速度极快对规则排版的文本保真度尚可。但如果发现抽取出来的文本顺序错乱、列与列混在一起就用pdfplumber它对坐标敏感能按页面上的物理位置提取文本处理双栏或多栏布局时更稳。这两款工具各有短板PyMuPDF对复杂表格的文本顺序处理不够聪明pdfplumber速度慢对超大文件不友好。实践中我会先跑一轮PyMuPDF如果检测到文字中“数字和下一列数字之间缺失制表符/空格”或者段落顺序明显跳跃再降级到pdfplumber重跑。扫描型PDF没有捷径必须OCR。探矿扫描件最老的一批是黑白二值图分辨率只有200dpi折痕、污渍、手写注释到处都是。通用方案里Tesseract虽然免费但中文识别率在老扫描件上惨不忍睹。我实测下来PaddleOCR的PP-OCRv4对中文字符、数字混排的表格类文本更友好而且能输出每个词的坐标框这对后续重建表格结构极其重要。OCR的预处理也不可忽视二值化、去噪、倾斜矫正三个步骤能显著提升识别率。我用OpenCV做倾斜检测检测到页面文本行角度超过1度就做旋正这一条对历史扫描件尤其关键。表格类PDF的清洗最忌“见行拆行”。很多探矿表格是带竖线的OCR识别时会输出大量“|”字符如果清洗规则把它们当面处理表格记录就被打散了。我的做法是先检测页面上的表格线用坐标聚类找到表格区域再按列切分提取如果文本型PDF没有明显表格线就依据列的x坐标阈值合并同一行文字。这个步骤做完一行原始表格文本对应的是一条完整记录。还有个容易踩坑的地方是页眉页脚。探矿报告的页眉常带“第×页”“××地质队”字样页脚有页码、日期这些信息几乎不具备检索价值却会被切进每个chunk里导致不同chunk的向量相似度虚高污染检索结果。清洗时必须根据版面位置或者字体特征做页眉页脚剔除。另外提一句很多人问我是不是先用免费的PDF转Word软件把PDF转成Word再清洗会更好。我的经验是不建议。多一次中间格式转换就多一次信息丢失和排版错乱的风险而且免费转换工具对表格、公式、多栏布局的处理普遍不稳定。绕了一大圈最后还是得回到PDF原生解析或OCR这条路。除非是文档量极少、且以简单文本为主否则别走这条捷径。2.4 网页结构化解析保存可比保存前更重要探矿业务中需要采集的网页资料主要来自政府地矿主管部门的公开地勘公告、行业协会的期刊文章、以及一些专业论坛的技术讨论。网页清洗的关键不是“把HTML变成文本”而是“从HTML里挑出正文”。直接用正则去HTML标签的做法早就过时了我建议用trafilatura或者readability-lxml这类正文抽取库。trafilatura对学术类、报告类网页的正文抽取效果尤其好能自动剔除导航、侧栏、页脚、广告、同类推荐。实测下来它在中文地矿网站上的效果也不错偶尔需要搭配BeautifulSoup做定制处理。网页清洗还有两个细节要留意一个是编码与乱码。国内不少老网站页面是GB2312编码但HTTP头可能没标明爬虫下下来的HTML如果按UTF-8解码又是一堆乱码。所以在下载阶段就用requests的apparent_encoding做检测同时用BeautifulSoup的from_encoding参数强制指定。另一个是页面内嵌表格。很多地勘公告里的表格是直接写在HTML里的正文抽取器可能把表格当噪声丢掉或者在抽取时把一行行的数据连成一大段。我现在的做法是先用trafilatura抽取正文段落然后单独用BeautifulSoup提取页面里的table标签并转成Markdown表格最后把两部分拼接。表格在网页里的意义跟Word、PDF里一样重要不能一刀切。还有一个常见的错误操作很多人习惯先把网页“打印成PDF”再按PDF流程清洗。这条路我试过效果很差。网页打印出来的PDF表格线经常丢失、链接失效、文本被强制分页反而给后续解析添了一堆新问题。直接解析HTML才能保留原始结构。保存策略也值得说一句。不要只存抽取后的纯文本建议同时存一份原始HTML和抽取后的JSONJSON里保留URL、标题、正文、表格、发布日期字段。这样后续如果发现抽取质量不行还能回溯原始页面重新解析而不用重新爬取。3. 从清洗到入库一个可复用的RAG数据处理管道3.1 管道整体设计前面的格式拆解是“单点技术”但真实项目里你不能拿一段脚本跑完所有格式得有一条串起来的流水线。我用到的基本结构是这样的第一阶段采集与登记。所有原始文件进入统一目录按“来源-年份-类型-编号”重命名同时写一个manifest.json记录原始路径、文件类型、大小、SHA256值。登记表的价值在后面做问题回溯时会体现出来比方说某个chunk内容可疑能直接定位到原始文件和当初的解析参数。第二阶段格式识别与分流。根据文件扩展名和文件头magic bytes判断格式PDF、Word、TXT、HTML分别进入对应的解析器。注意扩展名可能失效比如一个扩展名为.doc的文件实际是PDF或者文件头乱掉所以我的规则是“先读文件头再信扩展名”。第三阶段编码修正与乱码检测。对所有文本类内容统一做编码检测与归一化到UTF-8。同时跑一个乱码检测器重点检测“”字符、替换符比例、非法字节序列。如果乱码比例超过阈值流入人工复核或修复规则。第四阶段结构解析与转换。TXT做断行合并Word/PDF/网页分别按前面说的逻辑抽取结构和表格统一输出为“段落文本 表格JSON”两部分再合成Markdown或JSON行格式。我个人偏好输出为标准JSON格式每份文档一个对象里面有metadata、sections、tables三个字段。第五阶段内容清洗与去重。跑pandas做全局去重比较相同段落文本的MD5同时在业务规则层面根据标题、日期、矿区名称识别重复文档。探矿资料重名率高同一份储量报告可能在三个不同的归档目录里都存在不去重的话RAG检索结果会很嘈杂。第六阶段质量校验与报告。每份文档清洗完成后生成一份“清洗报告”记录原始字符数、清洗后的字符数、表格提取数量、乱码修正位置、人工干预标记。我习惯用阈值控制上线比如乱码比例低于0.1%、文档结构识别率高于90%才允许入库否则进入“待复核”队列。3.2 为什么选择“段落表格”双结构而不是纯文本这是我被问得最多的设计选择。很多人不理解为什么清洗管道要单独输出表格JSON而不是把所有内容转成一长串纯文本。原因很简单RAG的chunking策略没办法同时兼顾长文本语义和表格行记录完整性。长文本段落适合按语义切分每块200-500个token自然段落本身是完整的语义单元表格则完全不一样一张钻孔数据库可能几百行如果你把整张表塞进一个chunk上下文窗口和检索相似度都会出问题。而如果按固定长度把表格切碎一行数据的各个字段会被分到不同的chunk里。所以清洗阶段就做区分段落文本按语义分块表格转成“一条记录一个短chunk”或“一个分组的记录一个chunk”。这样向量化时表格记录的每一行都形成完整独立的向量用户问“ZK301的见矿深度”检索系统能直接命中那一行的内容。还有一点经验是不要把清洗后的数据直接拿来入库建议中间加一层“统一占位符替换”。具体来说我会把清洗文本里的矿产品位单位g/t、%、ppm、坐标值、钻孔编号等专业令牌做轻度归一化比如把“克/吨”“g/t”“grams per ton”统一转成“g/t”。这一步能明显提升Embedding模型对相似语义的召回率因为不同报告里同一个量的表述方式太多了。这里也顺带回应一个很多人纠结的问题到底用RAG知识库、KG知识库还是结构知识库。我的判断很简单探矿资料里既有大量非结构化的报告文本又有结构化的表格数据单一的知识库形式都不够。RAG负责文本语义检索表格数据抽出来做结构化存储两者通过文档ID关联才能在问答时做到“既知道上下文又能精确定位到那一行数据”。你要是一开始就把所有东西压成纯文本等于把结构化数据的优势白白扔掉了。3.3 一个可复用的清洗参数配置参考这里给一份我项目中常用的参数参考不同资料情况请按需调整参数推荐值说明文本型PDF解析库PyMuPDF优先pdfplumber兜底遇到多栏或表格乱序时切换OCR引擎PaddleOCR PP-OCRv4中文混排场景识别率高OCR预处理灰度化自适应二值化倾斜矫正老扫描件必做编码检测chardet 地质术语特征检测双重校验防短文本误判TXT断行合并行尾非句末且下行非数字/大写开头则合并需人工抽查表格切分按行转JSON保留表头字段名字段名缺失时自动用“列1”占位页眉页脚剔除按版面坐标或字体特征识别遇到重复文本块优先删去重MD5 文档级相似度双重去重表格型文本MD5可能不同质量门槛乱码率≤0.1%表格提取成功率≥90%未达标进人工复核这些参数不是拍脑袋定的是我在不同矿种、不同年代资料上试出来的经验区间。比如倾斜矫正阈值设1度是因为多数老扫描件不过几度的小倾斜OCR模型自己能容忍但超过1度后识别率下降明显。4. 实战中踩过的坑与排查方法4.1 乱码“假修复”检测出GBK后直接改全局编码结果更乱有个项目里一批TXT钻孔编录文件。chardet检测结果是GB2312概率很高我就直接按GBK解码、转成UTF-8。结果入库后抽查发现不少文件的中英文混杂处出现“?? ”和多余替换符。后来细查才发现这批TXT虽然是GB2312编码但里面混着一些日文片假名、繁体汉字、甚至个别乱字节全局GBK解码处理不了这些字符。正确做法是errorsreplace策略与无损备份并行解码时允许未知字节替换为UFFFD但保留一份原始字节文件。如果后续发现乱码比例高就针对特定字节序列做局部修复而不是整个文件重新解码。另一个教训是不要只检测一次编码文件在不同段落中可能经历过拼接编码并不统一。4.2 OCR把1认成了l品位数据直接失真这是个教训很深的案例。一批扫描版化探分析表PaddleOCR跑完抽查时看起来没问题但后来做数值校验时发现某一列的Au品位值原本应该是“1.25”识别成了“l.25”小写L替代了数字1。这种错误在自然语言检索里无伤大雅但在探矿业务数值检索里是致命的——如果RAG被问到“品位大于1g/t的样品有哪些”这类错误数据能直接带偏结果。解决方案有两层一层是清洗阶段的规则引擎对数值型字段做格式强校验比如“品位列必须匹配数字单位”的模式匹配失败的推送人工核对另一层是入库前与原始表格做交叉比对。条件允许的话对有坐标或样品编号的表格用编号去和已有的Excel版台账对齐如果编号匹配但数值对不上多半是OCR错误。4.3 表格切分后一条记录碎成三块这也是常见问题。一张钻孔柱状图生成的数据表有合并单元格某行文字跨了两列。按列切分时我直接按物理x坐标切结果同一行文字因为坐标分裂被切进了两个chunk。后来改成“先按行聚类再按列切分”的顺序先依据文字的y坐标把所有文字聚成行行内再按x坐标分成列。这样即使合并单元格跨列也能保证一行记录整体留在同一个chunk里。4.4 网页正文抽取器丢掉重要表格地勘公告里的评审结果表格用trafilatura抽正文时表格被当成结构化数据过滤掉了。我当时差点直接上线后来因为想到“表3-2 资源量估算结果”这种表格是核心数据才特意做了表格单独抽取。之后再接任何网页源我都会先检查“抽取后的文本里是否包含表格内容”如果发现表格数量为零多半是抽取器把表格吞了要切换策略。4.5 高质量检索验证方法用问题回溯清洗效果清洗工作做得对不对最终要用检索效果验证。我的验证办法不复杂准备20-30个带标准答案的探矿业务问题比如“某矿区矿体平均厚度是多少”“某钻孔的见矿深度有多深”在清洗前后各跑一轮RAG检索统计Top-5命中率。如果命中率从清洗前的30%提升到清洗后的80%说明清洗是有效的如果提升不明显就看命不中的问题集中分布在哪个环节——是文档没解析出来还是解析后chunk切得不对还是向量检索本身的问题。这套“问题集驱动”的验证方法比单纯盯着清洗报告里的“字数对比”靠谱得多。5. 工具选型与管道维护的个人经验5.1 通用框架与自建管道怎么权衡现在做RAG清洗可选项其实不少有Apache Tika、unstructured这类“万能提取”框架也有微软的MarkItDown、国内的TextIn等商业化服务。我的使用原则很简单通用型框架适合快速铺路子但探矿业务这种专业场景最好还是自己搭一条基于开源组件的管道原因有三点。一是可调试性。第三方服务是个黑盒解析结果不对你只能干瞪眼连参数都调不了。二是数据不出域。探矿资料很多涉及商业机密和矿权信息不适合全部上传到外部服务自己搭本地管道更稳妥。三是定制空间。探矿领域的脏数据模式太特殊了通用框架并不会专门为“老扫描件里的表格线破损”设计修复方案只有自己写规则才能覆盖这些长尾问题。当然纯自建不等于每个模块都要自己造轮子。PyMuPDF、pdfplumber、PaddleOCR、trafilatura、pandas都是开源组件串起来并不难。难点在于“规则层”也就是针对探矿术语、坐标格式、表格行记录做的那些清洗规则这部分是真正需要你结合业务去沉淀的。5.2 清洗管道一定要做成可重跑的另一个建议是清洗管道一定要做成可重跑的。千万别把清洗做成一次性脚本跑完就删。因为清洗规则会随资料集的扩大持续迭代你今天发现新的乱码模式改了规则得能对全部历史数据重新跑一遍才能让整个知识库保持一致性。所以每个环节都做成幂等的源文件只读产出物有版本号这样随时能重建知识库。还有一个实操细节清洗报告别舍不得存。每个批次清洗完成把统计结果归档成一个CSV或JSON积累多了你就能总结出规律比如“哪一年的扫描件质量最差”“哪种来源的网页表格最容易被吞”。这些数据对后续优化清洗规则和判断新资料的清洗难度都非常有价值。我个人的经验是数据清洗在RAG项目里不是可有可无的前置步骤而是决定项目成败的基本盘。今天这篇文章写的很多方法比如PDF双引擎、OCR的数值校验、表格行聚类切分本质上都不是什么高深的技术但每一项都是在探矿业务这个特定场景里反复打磨出来的。如果你正在做矿业、地质、勘探类的知识库项目希望这份经验能帮你少走点弯路。
返回列表