☰
探矿文档RAG清洗实战:从乱码到高精度检索的分块策略
2026/10/6 15:17:51 网站建设 项目流程

从乱码到高精度检索:探矿业务中 TXT、Word、PDF 与网页的 RAG 清洗之道

做探矿业务的老哥们应该都有过这种体验:项目的文字资料比矿体还难啃。几十年前的手写报告扫描件、同事从现场发来的Word勘探记录、从合作单位拷过来的PDF储量估算表、还有零散的TXT坐标数据文件,全都堆在一个共享盘里。平时没人看,等真要写报告、做立项论证、或者搞知识库检索的时候,才发现这些资料根本没法用——不是打开乱码,就是检索系统返回一堆牛头不对马嘴的结果。

所以我一直强调一个观点:RAG项目里,真正决定上限的不是模型,不是向量库,而是清洗环节。尤其是探矿这种文档结构松散、格式混乱、专业术语密集的领域,清洗没做到位,后面的检索和生成全是空中楼阁。这篇文章就把我在探矿业务中处理TXT、Word、PDF和网页这四类来源文件时的完整思路和实操细节做一次复盘,从乱码的根源一路讲到高精度检索的分块策略,全是踩坑踩出来的经验。

1. 探矿文档为什么难啃:乱码只是最表层的麻烦

很多人一听说"清洗"就想到编码、乱码、去空格,实际上探矿领域的文档问题远不止这些。我先从最常见也最容易被忽视的几个层面说起。

1.1 编码乱码:TXT文件里被折叠的矿化信息

探矿资料里TXT格式大量存在,坐标数据、测井记录、化学分析原始数据,很多都是老仪器直接导出的,编码方式千奇百怪。最常见的是GBK和UTF-8之间的错位——你用UTF-8的解析器打开一个GBK编码的文件,满屏"锟斤拷";反过来用GBK打开UTF-8文件,又会出现"涓€"开头的中文乱码。

但真正难的还不是这种肉眼可见的乱码,而是那些能正常打开、实际内容已经被破坏的文件。举个例子,我遇到过一批野外荧光分析仪导出的TXT文件,文件头声明是UTF-8,但中文字段名实际上是GBK编码,Python默认读取后字段名变成了一堆非法字符,程序直接报错。这种情况下不能光靠chardet自动检测,得结合文件来源和仪器型号做硬性映射。

1.2 排版与语义错位:PDF解析后表格全散架

探矿报告里最有价值的信息往往是各类表格——钻孔编录表、样品分析结果表、矿权范围坐标表。可PDF一旦经过扫描归档或者排版软件的导出,文字层的顺序就和视觉排布脱节了。用PDFPlumber解析坐标表,经常出现表头和数据错位、同一行的数据被拆到两行、坐标值小数点被当成句号切断这类问题。数据一旦错位,检索系统还浑然不觉,照样把"Au 12.35g/t"和"深度区间145-148m"错误地关联起来。

1.3 网页资料:导航栏和正文混在一起

探矿业务也会大量引用网页资料,比如矿产资源法、地方政府的矿权公示、勘探规范条文、行业协会公告。直接从网页抓下来的HTML里全是导航菜单、广告、页脚版权声明。要是不做正文提取和超链接内容解析,只管整段塞进知识库,检索的时候搜"采矿权"能返回十条,八条是导航栏重复文本。这属于文本层面肉眼看不到、向量检索却会被反复干扰的隐性脏数据。

1.4 检索噪声:探矿术语里的"绝对高频词"

探矿文档里有大量高频出现的通用词——"矿区""钻孔""样品""分析""报告",这些词在普通文档里可能还算有区分度,但在探矿领域的语料库里几乎是无差别高频词。如果不做词频分析和领域停用词处理,向量化的结果会被严重稀释,导致检索精度上不去。这一点后面在分块策略里还会细说,但清洗阶段就得先有意识地把这些词标注出来,而不是等检索阶段再补救。

2. 分格式解析:TXT、Word、PDF、网页各自的处理路径

不同格式有各自的解析难点,不能用一套代码通吃。我的经验是分四条管线处理,每条管线单独写解析脚本,输出统一格式的中间态JSON或Markdown,再进入清洗阶段。

2.1 TXT管线:先认编码,再清符号

对于TXT文件,第一步永远是编码判定。我在实践中用的是编码检测加人工兜底的双重策略。先用chardet和charset_normalizer分别检测,两者结果一致就直接用;不一致就触发人工判定流程,让熟悉这批数据来源的工程师指定编码。

import chardet from charset_normalizer import from_bytes def detect_encoding(file_path): raw = open(file_path, 'rb').read(4096) result1 = chardet.detect(raw) result2 = from_bytes(raw).best() if result1['encoding'] == result2.encoding: return result1['encoding'] else: # 触发人工判断,这里记录到日志等待处理 return None

第二步是符号清理。探矿TXT里常见的符号垃圾包括:制表符混杂空格造成的列错位、ANSI转义序列(老设备输出的\x1b[开头的内容)、不可见控制字符、重复的换行符和全角半角混排。这些符号不需要设计师级别的精细处理,正则批量清洗就行。

import re def clean_control_chars(text): # 去除控制字符但保留 \n \t text = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '', text) # 合并连续空行 text = re.sub(r'\n\s*\n+', '\n\n', text) # 去掉行尾多余空格 text = re.sub(r'[ \t]+$', '', text, flags=re.M) return text

还有一个探矿场景特有的问题:坐标TXT文件里通常有大量"度分秒"格式的数据,比如116°23'45.6",清洗时千万别把这些符号当作特殊字符删掉——那是坐标数据的一部分,删了坐标就废了。我就吃过这个亏,当时图省事用统一正则清掉了所有非字母数字字符,结果一批钻孔坐标的秒符号全没了,数据直接没法用,只能从备份重新清洗。从这个教训之后,我的清洗规则里永远把坐标转义符号放在白名单里。

2.2 Word管线:表格、公式、批注各走各路

Word文档在探矿业务里的角色很特殊,往往是中间成果——野外记录整理版、评审前的修改稿、数据汇总表。处理Word主流的方案是python-docx,光靠它也能解决大半问题,但有几个坑必须单独处理。

第一,正文段落和表格要分开提取。python-docx里document.paragraphs只返回正文段落,表格要用document.tables单独遍历。如果混在一起读,表格内容和正文内容交错,语义就乱了。我封装了一个遍历函数,把段落和表格做一个时间线合并,保留它们在文档中的实际出现顺序:

from docx import Document from docx.table import Table from docx.text.paragraph import Paragraph def iter_block_items(parent): from docx.oxml.ns import qn parent_elm = parent.element.body for child in parent_elm.iterchildren(): if child.tag == qn('w:p'): yield Paragraph(child, parent) elif child.tag == qn('w:tbl'): yield Table(child, parent)

第二,公式问题。探矿报告里的公式虽然不多,但很重要——资源储量估算公式、品位计算公式、变异系数公式等。用python-docx读不到公式内容,因为公式是OLE对象或者OMML格式。我在这个环节的实践是:OMML格式的公式用latex2mathml配合mammoth做转换,转成LaTeX文本;如果是嵌入的Equation对象,赶时间就直接标注[公式占位],在后续处理里人工补录。如果你不需要对公式做语义检索,占位符方案完全够用,别在上面耗太多时间。

第三,批注和修订。评审稿的批注往往包含专家的修改意见,这些内容有没有价值取决于场景——如果建设的是最终成果知识库,批注应该剥离;如果建设的是项目过程档案库,批注反而是精华。我的做法是用python-docx的comments属性把批注单独抽取出来,和正文分开存储,打上不同的元数据标签,这样同一个文档在知识库里可以被灵活检索。

2.3 PDF管线:文字层、物理层和OCR三层递进

PDF是探矿文档的大头,也是最难处理的格式。我把PDF解析分成三个层级,递进处理。

第一层是文字层解析。用pdfplumber提取文本和表格,适合电子版PDF。这里有个关键技巧,extract_tables的table_settings参数能大幅提升表格提取准确率,尤其是对探矿报告里常见的竖线表、三线表。我用的是:

import pdfplumber settings = { "vertical_strategy": "lines", "horizontal_strategy": "lines", "snap_tolerance": 3, } with pdfplumber.open(pdf_path) as pdf: for page in pdf.pages: tables = page.extract_tables(table_settings=settings)

第二层是物理层解析,针对那些文字层位置异常的情况。有些PDF是排版软件导出的,文字虽然可选,但顺序完全错乱,表格被拆成碎片。这种情况下pdfplumber的文字层解析基本失灵,就得用PyMuPDF按坐标块提取文本,再根据坐标位置重排。建议在PyMuPDF的page.get_text("blocks")里提取包含坐标的文本块,然后按垂直和水平坐标聚类,恢复表格的行列结构。

第三层是OCR,针对扫描件。探矿老资料里扫描件比例极高,早期地质报告大多是纸质档案扫描的,有些甚至是黑白低分辨率的老旧扫描。OCR这一层我用PaddleOCR加pdf2image配pytesseract,个人经验是PaddleOCR对中文专业术语的识别率明显高于Tesseract。识别完之后,一定要做的不是直接入库,而是再做一次文本校验——把识别出的专业术语和领域词库比对,发现置信度低的词要标记出来。探矿领域有大量"品位""蚀变""矽卡岩""安山岩"这类词,OCR一旦把"矽卡岩"识别成"砂卡岸",检索系统是认不出来的。

2.4 网页管线:去噪与主体提取

网页资料的清洗用trafilatura比较好。这个库专门做正文提取,能自动去除导航、页脚、侧栏。它在处理政府网站、行业资讯这类结构相对规范的页面时效果不错,但对于用JS动态渲染的页面无能为力,那种情况得改用playwright先渲染再提取。

网页提取完之后还有一个容易被忽略的步骤——链接解析。探矿资料里大量网页正文中提到的法规名称、政策文件编号、矿床名称往往附有超链接,这些链接指向的内容可能是附件PDF或者下级页面。我会用爬虫递归地抓取这些链接指向的文档,和原始网页关联存储,构建一个简单的文档关系网。比如某个矿权公示页面正文提到了评价报告的PDF附件,点击链接才能看到完整内容,如果不递归抓取,知识库里就只有公示页面碎片的公告信息,检索"详查报告结论"时永远找不到答案。

3. 从文本到语料的清洗细节:探矿领域特有的脏数据

原始格式解析派生出文本之后,真正的重头戏才开始。这一节讲的都是探矿领域特有、通用清洗教程里不会写的内容。

3.1 表格行序保护:动过排序就毁了数据关联

探矿文档里的表格,行顺序就是数据关系本身。

你可能觉得这是废话,但清洗时很容易在无意识中破坏行序。比如批量清洗正则,把两个相邻行匹配到一个模式,然后去重合并;或者为了去空白把包含大量空单元格的表格行误判为空行删掉。钻孔编录表里一个空单元格不代表这一行没意义——可能是"未见矿化"而非数据缺失。我的经验是:凡是从表格提取的内容,每一行都保留原始行号和页眉信息,清洗过程中绝不删整行,只允许修改单元格内容。这样出问题还能回溯到源头。

3.2 单位与数值的标准化:Au 12.35g/t vs Au 12.35

探矿文档里同一个数值多种写法是常态。金品位"12.35g/t"可能写成"12.35 g/t""12.35克/吨""12.35×10-6"。不统一,检索时查"品位12.35"和"品位12.35克/吨"就完全匹配不上。

我的标准化思路是做一个单位映射表,把常见的中英文单位变体统一到一个标准表示,并在保留原始文本的同时,额外生成一个"标准化字段"存到元数据里。检索时优先用标准化字段做匹配,原文只做展示。

另外要处理数值格式的一致性,例如统一千分位分隔符,把全角数字转半角,科学计数法转十进制。这里有个教训要提醒:千万别用简单正则把×10-6这种写法里的负号给删了,要写成完整的浮点数,不然数值语义直接变错。

3.3 探矿特有噪声:页眉、水印、图例标注

探矿PDF特别爱在每页顶部放矿权名称和报告编号的页眉,页脚放页码,有些图件页面还有"内部资料,注意保密"的水印。这些文字在PDF解析时会混入正文,形成全篇重复的噪声。对于这类内容,用规则匹配即可,因为格式往往高度固定,可以针对每个项目定制页眉正则,精准删除。

但水印就要小心了。很多水印是矢量文字叠加在正文上的,PDF解析时会反复出现在每一页的同一位置,提取的文本坐标一旦和水印坐标重叠,正文可能被水印文字打断。我的做法是先统计全篇文本坐标的分布,识别出固定坐标反复出现的字符串,标记为水印,在执行正文提取时按坐标剔除。

还有一个探矿特有的坑——图例标注。地质图附录里会有大量"Q 第四系""J3 上侏罗统""γ 花岗岩"这类图例说明,在文字版文档里它们通常是独立的术语表,清洗时会被误当作乱码或噪声删掉。实际上这些图例术语是构建领域词典的黄金素材,我会专门把它们抽取出来,进领域词库而不是删除。

3.4 坐标和方位信息的保护性抽取

探矿文档绕不开坐标。经纬度坐标有十几种写法,度分秒、十进制度、UTM投影坐标各有适用场景。在清洗阶段就要识别并保护这些坐标,不要在通用清洗流程里把它们当作累赘符号删掉,更不要试图强制统一格式——UTM坐标和经纬度坐标本身不能混用。

我的方案是抽取出坐标后单独存储到字段里,标注坐标系(WGS84、北京54、西安80等),并保留原始投影信息。这样后续如果接GIS工具,可以直接从字段里读取坐标做空间检索;不做空间功能时,这些字段也不会干扰文本向量化。

另外探矿文本里大量出现方位描述,比如"沿NE向断裂带……""矿体倾向NW300°"——这些实际上是空间关系的文本表达,保留原文即可,但千万别把它们当作无关方位词清洗掉。

4. 高精度检索的关键:清洗后的分块与元数据策略

解析和清洗做完,接下来是RAG检索精度最敏感的部分——分块和向量化。这一节的经验直接决定了"高精度检索"能不能落地。

4.1 按语义边界分块,而不是按字数硬切

很多RAG教程默认用固定窗口分块,比如每块500字加50字重叠。这个方案在探矿场景效果很差。原因很简单:探矿报告的核心信息高度集中在表格、图件说明、规范条文和储量数据里,固定窗口分块会把一张完整的钻孔编录表切得七零八落,检索时只能召回表格的碎片,模型拿到的是"残缺数据"。

我的做法是优先按语义边界分块——段落、表格、列表各自独立成块,并把块之间的引用关系记录下来。表格单独存一块是原则性的要求,因为表格本身就是一个完整的语义单元。同时,表格块要保留表头和表首行的描述,防止独立成块后丢失上下文。比如一张"ZK01钻孔岩心编录表",如果表头信息没有随内容一起存储,检索系统就不知道这块数据是哪个钻孔的。

def semantic_chunk(doc): chunks = [] for block in doc.blocks: if block.type == 'table': chunk = { 'content': table_to_text(block), 'head': block.table_header, 'meta': block.meta } chunks.append(chunk) elif block.type == 'paragraph': # 按段落语义完整保留,不做硬切 chunks.append({'content': block.text, 'meta': block.meta}) return chunks

4.2 元数据标签:检索之前先过滤

探矿知识库的元数据应该怎么设计?我的经验是至少包含五类:资料类型(勘探报告、储量评审、采样数据、航测图件说明、法律法规)、矿区/矿种标签、时间范围(报告覆盖年份)、地理位置描述、文件来源(内部编制/外部引进/公开网络)。

元数据的作用在于,检索时可以先做粗过滤,再做向量检索。举个例子,用户问"XX矿区2023年最新储量数据",有了时间和矿区的元数据标签,系统先用标签过滤掉所有2022年及之前的报告,只把最近的储量评审报告送入向量召回,检索精度和速度同时提升。如果全部文档一股脑向量化,只靠语义相似度召回,即使TopK设到20,候选里也大量混入旧版本数据。

4.3 领域停用词与高频词处理

前面提到探矿文档里的通用高频词问题,分块阶段要正式处理。以我处理过的某铜矿知识库为例,"矿区""钻孔""样品""ppm""报告"这类词的文档频率超过60%,它们在语义向量中会把更有区分度的词汇权重稀释掉。针对这个问题,除了嵌入模型自带的停用词机制,我还会在构建检索语料时额外维护一个领域停用词表,同时对"品位""储量""矿体""蚀变"这类核心术语增加加权。

需要说明的是,领域停用词表不能一刀切删词,而是要在检索打分阶段做调整。文本内容里保留原词,向量化时对领域词赋予更高的权重或单独建索引字段。我用过的方法是:把领域术语单独抽取出来存入标签字段,检索时在向量相似度和术语标签匹配之间做加权融合,效果比单纯删词好很多。

4.4 嵌入模型的选型:通用模型在探矿场景哪里不够用

清洗得再好,嵌入模型选不对,检索精度也起不来。通用中文嵌入模型在普通新闻、百科领域表现很好,但在探矿专业语料上,我实测的相似度召回效果很一般——"矽卡岩型铜矿"和"矽卡岩型矿床"这种语义相近但字面差异明显的文本,通用模型召回排序不稳定。

我的建议是本地微调一个领域嵌入模型。用探矿领域的公开报告文本做数据,配合bge-large-zh或m3e-base做继续预训练和对比学习微调,领域术语之间的语义相似度会有明显提升。如果你的团队没有条件微调,退而求其次的做法是使用基于bge-m3的模型加领域同义词词典扩展检索——用户查"矽卡岩型"时,先扩展出"矽卡岩""接触交代""热液交代"等同义概念再进向量库检索,副作用是召回率升高、精度略下降,但比纯通用模型好一些。

5. 实测效果:清洗前后的检索精度差距与后续扩展

最后聊一段实测数据,也顺便交代这套方案后续还能怎么扩展。

5.1 一个真实的检索对比案例

我用某金矿项目做了一次对比测试。语料库包含历史地质报告、化探数据、钻孔编录、评审意见等约300份文档,原始处理方式是直接解析PDF后按500字固定窗口切块入库;清洗优化后的方式是本文讲的这套流程——按语义分块、元数据过滤、术语加权、领域词表修正。测试问题是"矿区蚀变带内金品位分布与矿体走向的关系"。

原始方案的检索结果Top5里,有两条来自报告页眉的重复文本,两条是无关的矿权公告,只有一条勉强相关且信息碎片化。清洗优化后的系统Top5全部来自钻孔编录表和化探分析报告,且返回块的语义完整度明显更高,有一条直接包含了需要的蚀变带品位数据和对应矿体走向描述。这轮对比下来,Top5相关度从"基本不可用"提升到"可直接辅助报告编写",差距可以说是质变的。

5.2 工程化落地的几个建议

如果你要把这套清洗流程落地成生产线,有几点工程上的建议。第一,全流程做日志留痕,记录每份文件的编码判定结果、清洗规则命中情况、分块数量、元数据标签,出问题才能回溯。第二,原始文件永远保留备份,清洗流程只产出新文件,不改原文件,这样参数调整可以随时重跑。第三,清洗脚本全部入库并用DAG调度工具编排,每个文件的处理管线互相独立,有一个环节挂掉不影响其他管线。

5.3 后续的扩展方向

关于扩展,我目前在尝试两个方向。一个是在清洗阶段接入GIS属性数据——把坐标字段、矿权边界矢量数据也纳入知识库统一管理,这样用户既能文本检索,又能画个范围框做空间检索,文本和空间两条召回链路融合。另一个是对OCR识别结果做领域模型的二次校正,目前用的是规则加词库匹配,准确率在85%左右,后续打算用N-gram比对加语言模型对整个识别结果做后处理,进一步压低专业术语的误识别率。

那套清洗管线说到底,每一层都是在跟探矿领域里脏乱差的历史资料做对抗。从编码乱码到表格错位再到检索噪声,每解决一层,知识库的可用性就上一格台阶。做探矿数据处理的人,经常得在"维护原始资料的完整性"和"让机器读懂资料"之间找平衡,我的体会是前者一定不能牺牲——原始文本哪怕是乱码也要留着,清洗结果只是它的一个可检索视图。这不仅仅是个技术问题,更是对几十年地质资料价值的一种尊重。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询