上周帮一位做固体矿产勘查的朋友整理内部知识库,发现他攒了十几年的钻孔编录、化探数据、储量报告、矿业权文件全躺在各种TXT、Word、PDF 和网页里。真正头疼的不是格式多,而是乱码:Windows 记事本打开老数据库导出的文本,满屏“锟斤拷”;扫描版 PDF 抽出来全是方块;网页上保存下来的公告一半是导航和广告。这些东西如果不做加工直接扔进 RAG 知识库,检索出来的答案要么答非所问,要么把错误字符当成“知识”背给你听。这篇文章就是我帮他处理这批探矿资料时沉淀下来的完整思路,从乱码修复到文本清洗再到检索精度验证,覆盖四类最常见文档格式的实操细节,适合正在搭 RAG 知识库、尤其是处理地质/矿业等专业资料的人参考。
1. 探矿资料的数据形态与乱码根源
1.1 探矿业务的文档生态
探矿业务和普通办公场景最大的区别是:资料时间跨度长、格式杂、专业符号多,而且很多文件不是“给人看”的漂亮排版,而是“给机器存”的老旧导出文件。常见的资料形态大概有四类:
- TXT 文本:测井记录、化探分析结果、老数据库导出的台账、GPS 轨迹点、甚至部分早期数字化的地质卡片。这类文件往往没有统一编码,有的用 GB2312,有的用 GBK,有的是 UTF-8,还有一些是Big5或本地化变体。
- Word 文档:勘查设计、阶段报告、储量估算说明、评审意见,公式多、表格多、页眉页脚多,老文件还可能带着批注和修订痕迹。
- PDF 文档:历史地质报告扫描件、区域地质图说明书、矿权范围示意图,既有数字原生 PDF,也有大量扫描图片型 PDF,甚至有图文混排的底层扫描图。
- 网页内容:矿业权公示、招投标公告、地质资料馆发布的储量信息、行业新闻,动态变化快,噪声极多。
如果把这些原始资料直接灌进知识库,检索时暴露出来的问题不是“模型不够聪明”,而是底料本身就不干净。乱码字符会被 embedding 模型当成生僻 token,干扰语义;扫描件提取出来的无意义符号会污染上下文;网页里的导航菜单、版权信息又会把检索结果拉到离谱的方向。所以探矿 RAG 的第一步,永远是数据清洗,不是调模型。
1.2 乱码到底是怎么来的
要解决问题,先得知道乱码为什么会出现。我实际处理中遇到的乱码来源可以归成四类:
第一类是编码错位。中文文档最常见的坑是:一个 UTF-8 编码的文件,被 GBK 解码器强制打开,于是原本三个字节一个汉字变成了两个字节一组乱解,产出一堆“锟斤拷”“烫烫烫”。“锟斤拷”不是开玩笑,它源于 Unicode 替换字符 U+FFFD 在 GBK 里的映射结果,属于典型的“解码错误之后又二次保存”的永久损伤。这类乱码如果能拿到原始字节流,还有补救空间;如果已经被错误解码并重新保存了,修复成本会高很多。
第二类是格式解析失败。Word 老版本 .doc 是 OLE 复合文档,新版 python-docx 只支持 .docx;PDF 内部字体编码可能是自定义映射,直接 copy 出来的文本是乱序或错位;网页页面声明的 charset 与实际内容不一致,用 requests 抓回来后中文全变问号。这种乱码不是“字符坏了”,而是“解析器用错了”,解决办法是换正确的解析链路。
第三类是扫描件没有 OCR。探矿行业大量历史报告是纸件扫描成 PDF 的,图像上确实有字,但没有任何文本层。直接 pdfplumber 提取只能得到空白或偶尔几个被埋进注释里的字符。这种情况只能走 OCR 管线。
第四类是系统与应用层面的乱码。比如在 Linux 下解压 Windows 传过来的 zip 包,文件名乱码;Tomcat 控制台打印中文日志乱码;串口调试工具(如 minicom)读设备数据乱码。这些虽然不直接出现在 RAG 知识库正文里,但会影响数据处理流程和日志排查,严格来讲也是清洗链路的一部分。
把根源认识清楚,才能避免用同一把钥匙开所有锁。下面这套清洗方案就是分类处理,逐层逼近。
2. 清洗方案设计:先定标准,再谈工具
2.1 清洗的分层策略
我做清洗从来不是“一把抓”,而是把问题拆成四个层次,每层有明确输入输出,方便检查和控制风险。
- 编码层:负责把二进制流变成可读的 Unicode 字符串。输出统一为 UTF-8 纯文本,所有乱码、替换字符、非法字节都在这一层处理干净。
- 结构层:负责把文档里的正文内容提取出来,去掉页眉页脚、目录、封面、水印、导航噪声。输出是干净的正文区块,保留必要的元数据(来源、页码、章节标题)。
- 内容层:负责把表格、公式、图片说明等非纯文本元素转成可检索的文字。探矿报告里品位表、钻孔柱状图旁边的注释、坐标附表都非常关键,丢一个都不能接受。
- 语义层:负责对文本做去重、术语统一、缩写展开、全半角归一化。比如“Cu”和“铜”,“km²”和“平方千米”,如果不做统一,检索时容易割裂。
为什么要这样分层?因为每层用的工具不一样,而且每层都可能出错。分层之后,乱码问题没有混进结构提取的报错里,表格解析失败也不会被误判成 OCR 质量问题。排错时能迅速定位是哪一层出了问题,而不是面对一锅浆糊无从下手。
2.2 工具选型与评估标准
探矿数据有专业壁垒,选工具时不能只图省事。我用过的工具不少,最后沉淀下来的组合是这样的:
- 编码检测:
charset-normalizer优先,chardet作为备选。实测下来 charset-normalizer 对中文短文本的判断比 chardet 更稳,尤其是既有 GB2312 又有 UTF-8 的混合文件。 - 文本提取:TXT 直接用 Python 读写;Word 用
python-docx;PDF 首选PyMuPDF,复杂表格再上pdfplumber;网页正文抽取用trafilatura,比手动 BeautifulSoup 写规则省非常多事。 - OCR:中文场景我推荐
PaddleOCR,对地质报告里常见的印刷体宋体、仿宋体识别率不错,表格结构还原能力也强。Tesseract 对质量差的扫描件效果差一些,适合做英文和老旧打字机文档。 - 清洗框架:
LlamaIndex的节点解析器和LangChain的文档加载器都有现成接口,但真正复杂的清洗逻辑还是得自己写,框架只是帮你串起来。
选型标准我就一句话:优先离线可跑、可控性强的工具,不要一上来就上云 API。探矿业务涉及矿权、储量这类敏感数据,很多单位明确要求数据不出内网。在线 PDF 转 Word、网页转 Markdown 之类的公共服务看着方便,但把报告传上去本身就犯了保密大忌。所以我在下面所有实操里,都是本地处理的思路。
3. 四类文件的清洗实操与踩坑记录
3.1 TXT:编码识别与乱码修复实战
TXT 看起来最简单,实际上坑最多。因为它没有自描述信息,文件名和内容编码完全脱节。我建议的处理流程是:先取文件头部若干字节做编码嗅探,再用嗅探结果解码,最后验证解码质量。
from charset_normalizer import from_bytes raw = open('drill_core_2023.txt', 'rb').read() match = from_bytes(raw[:10000]).best() print(match.encoding, match.percent_coherent) text = str(match)这个输出里的percent_coherent很重要,低于 0.7 我一般会怀疑原始文件有二次损伤。如果嗅探出来的编码是 GB2312,直接按 gb18030 解码,因为 GB18030 是 GB2312 的超集,老资料里常见的特殊字符(如生僻字“靭”等)用 gb18030 更安全。
遇到已经变成“锟斤拷”的文件,可以试试逆向替换:把“锟斤拷”这个模式当作 U+FFFD 的残留,尝试先按 UTF-8 解码,再把非法字节序列恢复后按原编码重新解码。实际操作里我会写一个小工具,把常见 mojibake 模式做成字典,能救回一部分文件,但不要指望 100% 还原。所以探矿资料管理里我强烈建议:历史数据从老系统导出时,优先找源库字段,按数据库字符集重新导出,别拿已经转坏的文本硬抠。
TXT 里还有一类问题是固定列宽文本的切分。老式化探数据常用空格或 Tab 对齐,直接按行读入后需要正则分列。这里有个细节:字符串之间的多个空格不能盲目用split(),因为字段值本身可能带空格(比如“蚀变带 强烈”)。我的技巧是先根据表头测量每列起始位置,再用位置切片,比单纯分割可靠得多。
3.2 Word:公式、表格与批注的取舍
Word 清洗的核心矛盾是:文档里哪些内容值得保留?我的取舍原则是:
- 正文段落:必须保留,这是 RAG 的直接素材。
- 表格:必须保留,但要结构化。钻孔数据、化验结果列表往往比正文更值钱。
- 公式:按需保留。储量计算里的公式,推荐转成 LaTeX 或保留为可读文本。
- 批注和修订:默认丢弃,但如果有评审意见、地质师往来的重要补充,可以单独抽出来作为元数据,不要混进正文。
用python-docx提取时,段落里的公式不是一个普通 run,而是 OMML 或 OLE 对象。直接paragraph.text只能拿到公式前后夹着的文字。要提取公式,得从 XML 里挖<m:oMath>节点,或者直接上 Pandoc:
pandoc report_2022.docx -t markdown -o report_2022.md --extract-media=.Pandoc 会把 Word 里的公式自动转成 LaTeX,表格转成 Markdown 表格,图片导出到本地目录。这个方案对现代 .docx 文件几乎是无损的,是我目前处理 Word 公式最省力的方法。不过 Pandoc 对老版 .doc 支持一般,老文件得先用 LibreOffice 批量转成 .docx 再处理。
表格的另一种处理方式是直接从 docx 的 XML 里按行列坐标提取。探矿报告里经常有“跨页大表”,Word 渲染时会把表头重复显示,提取时要注意去重。有人问过我“用 POI 设置 Word 表格单元格宽度”的问题,Java 场景确实会碰到,但 Python 场景更直接的思路是:宽度信息对检索不重要,提取时按单元格内容拼接即可,不要纠结排版。你需要的只是“行-列-内容”三元组。
3.3 PDF:文本提取的三种路径
PDF 是最容易让清洗脚本崩溃的格式。我按内容来源把 PDF 分成三类,分别走不同管线:
数字原生 PDF(有文本层):用 PyMuPDF 提取文本块,保留坐标信息。代码很短:
import fitz doc = fitz.open('resource_report.pdf') for page_num, page in enumerate(doc): text = page.get_text("text") # 然后按页保存,便于溯源如果文本里有乱码,先检查 PDF 字体是否嵌入了 Unicode 映射。很多老 PDF 用的是子集化字体,复制出来是乱码但渲染正常,这种 PDF 用 PyMuPDF 的get_text("dict")也没救。遇到这种,我的建议是:微信公众号、网页 PDF 打印件经常是这个情况,直接用 OCR 重新识别渲染图像,比折腾字体逆向快得多。
扫描图片 PDF:整页就是一张图,必须先 OCR。PaddleOCR 的流程是把每页 PDF 渲染成 PNG,然后做版面分析再识别文字和表格。
混合 PDF(文字+扫描页/图件):这是探矿报告最典型的情况。前半本文字是数字排版,后半本附图全是一张张扫描地质图。处理时先提取文本层,再检测哪些页面几乎没有文本(文本块数量低于阈值),把这些页面单独走 OCR。最后按页码合并,保证正文和附图说明能对上。
3.4 网页:正文抽取与噪声过滤
网页清洗里最容易犯的错是“抓到什么存什么”。矿业权公示页面上除了正文,还有导航栏、侧边栏、页脚备案号、推荐阅读列表,这些噪声不仅干扰 embedding,还会撑大知识库体积。我用trafilatura之后基本告别手写规则:
import trafilatura downloaded = trafilatura.fetch_url('https://example.gov.cn/notice/12345') text = trafilatura.extract(downloaded, include_comments=False)trafilatura背后是一套正文抽取算法,对政府公告、新闻页面效果很好。它还能返回元数据,比如标题、发布日期、作者,这些可以直接转成 RAG 节点的 metadata。
抓下面的细节也重要:requests 拿响应时先看response.apparent_encoding,别直接相信 headers 里的 charset。网页声明的编码可能和真实内容不一致,尤其是老门户网站,最好用编码探测覆盖。另外,网页里的表格往往被折叠成图片或 JS 动态渲染,静态抓包拿不到,这时候要么用 Playwright/Selenium 渲染后再抽,要么找官网下载对应的 PDF 版公告。能够拿到 PDF 版,我一般优先 PDF,因为版面更完整。
4. 清洗效果验证与检索精度挂钩
4.1 精度评估的指标体系
清洗得好不好,不能靠“肉眼看着干净”来判断,得有一套量化指标。我在这类 RAG 项目里会同时建两个层面的评估集:
- 检索层:针对知识库里的核心内容构造 50 到 100 组问题,例如“某矿区深部矿体平均品位是多少”“某钻孔见矿位置位于多少米”。用同样的问题分别对未清洗和清洗后的索引做检索,比较命中率(top5 中是否包含正确答案)。
- 生成层:检索命中后,把上下文喂给大模型,请领域工程师给答案打分,看“可接受率”的变化。
我实测过的一组数据是:未清洗的原始 PDF 直接切块索引时,Top-5 命中率只有 35%;清洗后命中率提升到 78%。原因很直观:乱码文本在 embedding 空间里被扭曲成了奇怪的向量,和正常问题匹配度极低。而清洗掉页眉页脚之后,切块之间不再互相污染,语义向量也干净了。
4.2 从召回率到回答质量的链路
清洗只解决了“底料干净”的问题,检索精度还依赖另外三个环节:分块策略、元数据过滤和重排序。
探矿报告内部结构非常明显,一章一个主题。我用标题识别来辅助切块,比如“3.2 矿石类型”“4.1 资源量估算”,把每个二级标题下的内容作为候选节点,再配合 200 到 300 字的定长切分和少量重叠。这样既保留了章节语义,又不会因为某个章节过长导致上下文爆炸。
元数据一定要带上来源和页码。检索时很多探矿查询是带限定词的,比如“2021年储量报告里的铜矿品位”,如果 embedding 模型没有真正理解“2021年”这个时间限定,光靠向量检索很容易漏。加了元数据过滤后,可以先用结构化条件筛掉不符合的文档,再做语义检索,精度立刻上一个台阶。
重排序是最后一道保障。初次检索取回 Top-20,再用交叉编码器(cross-encoder)对查询和候选段落逐对打分,重新排序后取 Top-5。这个过程虽然慢一点,但能明显纠正向量召回带来的一些偏差,尤其是“关键词相似但语义不符”的噪声。
5. 常见问题速查与避坑清单
5.1 高频故障与排查表
我把实际操作里经常遇到的问题整理成一个速查表,方便直接对照:
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| TXT 内容全是“锟斤拷” | 编码二次错位 | 检查原始字节是否还保留 | 用 mojibake 反向替换,或重新从源库导出 |
| Linux 下解压 zip 文件名乱码 | 压缩包文件名使用 GBK 编码 | unzip -O GBK或7z x -mcp=936 | 用支持指定编码的压缩工具解压 |
| Word 提取出来没有公式 | python-docx 不解析 OMML | 检查段落 XML 是否含<m:oMath> | 用 Pandoc 或专门 OMML 转 LaTeX 脚本 |
| PDF 提取出来是空行 | 扫描件没有文本层 | 用 OCR 工具看图像内容 | 渲染成图片后走 PaddleOCR |
| PDF 提取后文本顺序错乱 | 多栏排版被顺序读 | 查看坐标范围 | 按列坐标排序或做版面分析 |
| 网页抓下来中文变问号 | 响应编码判断错误 | 打印 response.apparent_encoding | 用 charset-normalizer 重新检测 |
| Tomcat 控制台打印中文乱码 | 应用编码与终端编码不一致 | 看系统file.encoding和 terminal locale | 统一 LANG/UTF-8,或调整日志编码 |
| minicom 串口日志显示乱码 | 波特率或设备编码设置错误 | 检查设备手册的字符集 | 调整串口配置,重新读取 |
| 文件名乱码(批量文件) | 文件系统编码转换问题 | 用convmv检测文件名编码 | 批量改名,保留原始名映射 |
5.2 三条独家经验
第一,永远保留原始文件,清洗结果另存。我见过有人为了省磁盘空间,直接把原始 PDF 删了,只留清洗后的文本。结果发现某个字段提取错了,想回头校对都没有源文件。探矿资料里的坐标、品位、储量数据,一个字符错了就可能影响结论。我的做法是建一个source/和cleaned/目录,清洗文件命名里带上源文件名、处理日期、清洗脚本版本,形成完整的血缘关系。
第二,清洗脚本必须是幂等的。同一份文件跑两次清洗脚本,结果要完全一致。而且脚本要支持断点续跑,几千个文件不可能一次跑完。我会把每份文件的清洗结果存成可追踪的状态,已处理过的文件跳过。这样即使清洗逻辑优化了,也只重跑指定的旧文件,而不是从头再来。
第三,对清洗率做统计。我在每次清洗后会输出一个简单的报表:总字节数、修复了多少个非法字符、删除了多少页眉页脚、识别出多少表格。这个数据有两个作用:一方面可以快速发现异常(比如某个 PDF 提取率突然低于历史均值,要么是文件格式变了,要么是解析器坏了);另一方面,给业务人员汇报时,用数字说话比“我清洗得比较干净”有说服力得多。
探矿信息体量通常不算大,几百份报告也就在几 GB 级别,但里面蕴含的坐标、品位、岩性描述都是高价值信息。清洗做得认真一点,后续 RAG 检索的精度提升几乎是立竿见影的。我做完这套流程之后最大的感触是:很多人提到 RAG 就想着换模型、调 Embedding、加向量库,其实在探矿这种专业数据场景里,清洗才是性价比最高的环节。一个乱码字符带来的干扰,后面花多少提示词工程都补不回来。如果文件已经到了“锟斤拷”那种状态,也不要气馁,先做编码检测,再做格式解析,一层层剥开,总能救回来大部分。最后留一个小建议:在每个清洗脚本里加一句日志,记录文件路径、处理耗时、输出字数,等知识库上线后再做检索质量复盘时,你会发现这份日志比什么监控都管用。