探矿RAG数据清洗实战:TXT、Word、PDF、网页多源异构文档处理指南
1. 探矿业务文档处理的真实困境1.1 为什么探矿场景下的 RAG 比通用场景难三倍做过探矿项目的人都知道这个行业的数据形态有多野。一个中型勘探项目下来资料能堆满好几个硬盘地质报告是 Word 写的钻孔柱状图是 PDF 扫描件化验数据是 TXT 或者 Excel 导出的历史资料甚至是拍照后 OCR 出来的网页存档。这些文件往 RAG 知识库里一塞检索出来的结果经常让人哭笑不得——问ZK-1203 钻孔的铜品位它给你返回一段乱码或者把表格里的数字全串行了。通用 RAG 方案在探矿业务上翻车核心原因有三个。第一专业术语密度极高像矽卡岩型铜钼矿斑岩型蚀变分带这类词通用分词器直接切碎向量化之后语义全丢。第二表格和公式占比大品位表、坐标表、地层柱状表这些结构化信息一旦被当成普通文本处理行列关系就彻底崩了。第三多源异构同一份数据可能同时存在 TXT、Word、PDF、网页四种形态格式不统一导致清洗管道必须做大量适配工作。我接手过一个探矿知识库项目原始资料大概 2000 多份文档涉及 TXT 化验单、Word 地质报告、PDF 扫描件和内部网页存档。第一版直接用了开箱即用的 RAG 框架检索准确率惨不忍睹问十个问题有六个答非所问。后来花了三周时间重做清洗管道才把准确率拉到可用水平。这篇文章就把这套清洗方法完整拆开讲从 TXT 到 Word 到 PDF 到网页每一类文件的坑和对应解法都过一遍。1.2 清洗管道的整体设计思路我的设计原则是**先分类、再清洗、后校验**而不是一股脑丢进解析器。具体来说管道分四层格式识别层判断文件真实类型不能只看扩展名。很多探矿资料是.doc后缀实际是 RTF或者.txt实际是 GBK 编码的表格。结构化解析层按类型走不同解析器TXT 走编码探测分隔符解析Word 走 XML 解析PDF 走文本层OCR 双通道网页走 DOM 提取。语义清洗层去噪、术语归一化、表格重建、公式转换。质量校验层抽样人工核对自动指标监控。这个分层的好处是每一层的问题可以独立排查。比如检索不准先看是解析层丢了内容还是清洗层把术语改坏了定位效率高很多。提示不要跳过格式识别层直接解析。我见过太多项目因为把 GBK 编码的 TXT 当 UTF-8 读导致整份化验数据变成乱码后面所有环节全废。2. TXT 文件清洗从乱码到结构化数据2.1 编码探测是第一步也是最容易翻车的一步探矿行业的 TXT 文件编码之混乱超出一般人想象。老设备导出的化验数据可能是 GBK地质队内部系统导出的是 GB2312从网页复制粘贴的又是 UTF-8 带 BOM。如果统一按 UTF-8 读中文全部变问号。我的做法是用chardet做初步探测再用业务规则兜底。代码大概长这样import chardet def detect_encoding(file_path): with open(file_path, rb) as f: raw f.read(10000) result chardet.detect(raw) encoding result[encoding] confidence result[confidence] # 置信度低于 0.7 时用业务规则兜底 if confidence 0.7: # 探矿数据常见编码优先级 for enc in [gb18030, gbk, utf-8-sig, utf-8]: try: raw.decode(enc) return enc except UnicodeDecodeError: continue return encoding这里有个经验优先试gb18030而不是gbk。gb18030 是 gbk 的超集能覆盖更多生僻字探矿资料里经常出现地名生僻字用 gbk 会报错。2.2 分隔符解析与表格重建TXT 化验单通常是固定分隔符格式比如逗号、制表符或者多个空格。但实际数据里分隔符经常不统一——有的行用逗号有的行用制表符还有的行用全角逗号。直接split(,)必然出错。我的处理策略是先统计分隔符频率再动态选择def parse_txt_table(content): lines content.strip().split(\n) # 统计前 10 行的分隔符 sep_candidates [,, \t, , |, ] sep_count {sep: 0 for sep in sep_candidates} for line in lines[:10]: for sep in sep_candidates: sep_count[sep] line.count(sep) # 选出现次数最多的 best_sep max(sep_count, keysep_count.get) rows [] for line in lines: # 统一替换全角分隔符 line line.replace(, ,).replace( , ) cells [c.strip() for c in line.split(best_sep) if c.strip()] if cells: rows.append(cells) return rows解析完之后还要做列对齐校验。化验单的列数应该是一致的如果某行列数明显偏离大概率是数据错位或者换行符问题。这时候要标记出来人工核对不能直接入库。2.3 数值字段的归一化处理探矿数据里数值字段特别多品位、厚度、坐标、深度每个字段都有单位问题。比如铜品位可能写成0.85%、0.85、8500ppm三种形式如果不归一化检索品位大于 0.5% 的样品就会漏掉一批。我的归一化规则表原始形式归一化结果处理逻辑0.85%0.85去掉百分号保留数值8500ppm0.85ppm 除以 100000.85 g/t0.85去掉单位统一为百分比85E-20.85科学计数法转换归一化之后还要在元数据里保留原始值方便溯源。这一点很重要探矿数据是要对地质结论负责的不能只存清洗后的值。注意数值归一化一定要做单位白名单校验。我遇到过把0.85m厚度误当成品位处理的 bug因为规则写得太宽泛。厚度、品位、坐标的单位体系完全不同必须分字段处理。3. Word 文档清洗公式、表格与宏的坑3.1 Word 解析的三种技术路线对比Word 文档解析有三条路python-docx、直接解 XML、转成其他格式再解析。我实测下来的结论是方案优点缺点适用场景python-docx上手快API 友好公式、复杂表格支持差纯文本报告直接解 XML信息最全公式可提取实现复杂需处理命名空间含公式的技术文档转 PDF/Markdown格式统一转换过程丢信息批量快速处理探矿地质报告里公式不少比如品位计算公式、储量估算公式这些如果用 python-docx 读公式部分直接丢失。所以我的主力方案是直接解 XML用 python-docx 做辅助。3.2 公式提取与 LaTeX 转换Word 里的公式存在word/document.xml的m:oMath节点里。提取逻辑是遍历 XML找到公式节点转成 LaTeX。这里有个现成的库叫latex2mathml反向转换也有对应工具但 Word 的 OMML 格式需要专门处理。我用的方案是python-docx配合lxml直接读 XMLfrom lxml import etree NSMAP { w: http://schemas.openxmlformats.org/wordprocessingml/2006/main, m: http://schemas.openxmlformats.org/officeDocument/2006/math } def extract_formulas(docx_path): with open(docx_path, rb) as f: tree etree.parse(f) formulas [] for math_node in tree.iter({http://schemas.openxmlformats.org/officeDocument/2006/math}oMath): # 提取公式内的文本和结构 text .join(math_node.itertext()) formulas.append(text) return formulas提取出来的公式文本再走一遍 LaTeX 转换存到知识库里。检索的时候公式作为独立字段不参与全文分词避免干扰。3.3 表格单元格宽度与合并处理Word 表格的坑在于合并单元格。探矿报告里的地层柱状表经常有跨行跨列的合并python-docx 读出来会变成 None 或者重复值。我的处理方式是重建表格矩阵def parse_word_table(table): rows len(table.rows) cols len(table.columns) matrix [[] * cols for _ in range(rows)] for i, row in enumerate(table.rows): for j, cell in enumerate(row.cells): # 处理合并单元格同一个 cell 对象会出现在多个位置 if matrix[i][j] : matrix[i][j] cell.text.strip() return matrix重建之后再把矩阵转成 Markdown 表格或者 JSON存进知识库。这样检索ZK-1203 钻孔在 200 米深度的岩性时能准确定位到对应单元格。实操心得Word 宏安全问题在批量处理时特别烦。如果文档带宏python-docx 打开可能报错。我的做法是先用zipfile检查word/vbaProject.bin是否存在存在就先剥离宏再解析。4. PDF 清洗文本层与 OCR 的双通道策略4.1 判断 PDF 是文本型还是扫描型PDF 分两种文本型有文本层可直接提取和扫描型图片需要 OCR。判断方法很简单用pdfplumber试提取如果提取出的字符数远小于预期就是扫描型。import pdfplumber def is_scanned_pdf(pdf_path, threshold100): with pdfplumber.open(pdf_path) as pdf: first_page pdf.pages[0] text first_page.extract_text() or return len(text) threshold探矿资料里老报告基本都是扫描型新报告是文本型。所以管道必须双通道并行。4.2 文本型 PDF 的表格提取文本型 PDF 的表格提取pdfplumber的extract_tables()是主力。但探矿报告里的表格经常没有边框线靠空白分隔这时候要调table_settingstable_settings { vertical_strategy: text, horizontal_strategy: text, snap_tolerance: 3, join_tolerance: 3, } tables page.extract_tables(table_settings)vertical_strategy设为text表示按文本位置推断列边界适合无边框表格。snap_tolerance控制对齐容差探矿表格数字多容差设小了会切碎设大了会串列我一般用 3。4.3 扫描型 PDF 的 OCR 优化扫描型 PDF 走 OCR但直接 OCR 效果很差因为探矿报告里表格线、印章、手写批注混杂。我的优化流程是图像预处理灰度化、二值化、去噪。版面分析用paddleocr的版面分析功能先切出表格区域和文本区域。分区域 OCR表格区域走表格识别模型文本区域走通用 OCR。后处理用地质术语词典做纠错。地质术语词典是关键。OCR 经常把矽卡岩识别成砂卡岩把斑岩识别成班岩。我整理了一份 2000 多条的地质术语表OCR 后做模糊匹配纠错准确率能提升 15% 左右。注意OCR 后的文本一定要保留置信度信息。低置信度的字段标记出来检索时降权或者提示人工核对。探矿数据错一个数字可能导致整个储量估算偏差。5. 网页资料清洗DOM 提取与正文识别5.1 网页抓取后的正文提取探矿业务里的网页资料主要是内部地质资料库的存档页面或者公开的地质调查报告网页。这些页面结构各异直接抓全文会带一堆导航、广告、页脚。正文提取我用trafilatura它对中文网页的正文识别效果比readability好。核心代码import trafilatura def extract_web_content(html): result trafilatura.extract( html, include_tablesTrue, include_commentsFalse, favor_precisionTrue ) return resultinclude_tablesTrue很重要探矿网页里的数据表必须保留。favor_precisionTrue让提取更保守宁可少提也不提错。5.2 网页表格的特殊处理网页表格和 Word、PDF 表格不同它有明确的 DOM 结构。用pandas.read_html()能直接读但遇到合并单元格rowspan/colspan会出错。我的做法是先用BeautifulSoup解析 DOM展开合并单元格再转 DataFrame。from bs4 import BeautifulSoup def expand_table(table_html): soup BeautifulSoup(table_html, html.parser) table soup.find(table) # 展开 rowspan 和 colspan for cell in table.find_all([td, th]): rowspan int(cell.get(rowspan, 1)) colspan int(cell.get(colspan, 1)) if rowspan 1 or colspan 1: # 复制单元格内容到展开位置 pass return table展开逻辑稍微复杂核心是维护一个二维矩阵遇到合并单元格就填充相邻位置。5.3 网页元数据的保留网页资料有个好处是有元数据比如发布时间、来源单位、作者。这些信息对检索很有价值问2020 年之后某地质队发布的报告时元数据能直接过滤。所以清洗时要把元数据单独抽出来存字段不要混在正文里。6. 语义清洗与知识库构建6.1 地质术语归一化四类文件清洗完之后统一进入语义清洗层。第一步是术语归一化。探矿术语有很多别名比如铜蓝和辉铜矿、黄铁矿和硫铁矿检索时必须能互相匹配。我建了一张同义词表用jieba的自定义词典加载import jieba jieba.load_userdict(geo_terms.txt) # geo_terms.txt 格式术语 词频 词性 # 矽卡岩 1000 n # 斑岩 1000 n同时同义词表用于查询扩展。用户搜辉铜矿系统自动扩展成辉铜矿 OR 铜蓝提升召回率。6.2 表格数据的结构化存储表格数据不要当文本存要拆成结构化记录。比如化验表每一行存成一条 JSON{ sample_id: ZK1203-045, depth_from: 200.5, depth_to: 201.2, cu_grade: 0.85, mo_grade: 0.02, source_file: 2023年化验报告.pdf, page: 12 }这样检索铜品位大于 0.5 的样品时直接走结构化查询比向量检索准得多。这就是结构化知识库和 RAG 知识库的区分——数值型、枚举型字段走结构化描述型、解释型内容走向量。6.3 分块策略的调整通用 RAG 按固定字数分块在探矿场景下不行。地质报告的逻辑单元是章节-段落-表格分块要按语义边界切。我的策略是按标题层级切大块段落内按句子切小块表格整体作为一个块不切分公式单独成块块大小控制在 300-500 字重叠 50 字。表格块可以大一些因为表格拆开就失去意义了。7. 常见问题与排查技巧实录7.1 检索结果乱码的排查路径乱码是最高频的问题。排查顺序看原始文件编码用chardet确认别猜。看解析层输出解析后立刻 dump 一段文本确认没乱。看清洗层是否误改术语归一化可能把正常词改坏。看向量化输入分块后的文本再确认一遍。我遇到过最隐蔽的一次是清洗层把0.85%里的%当特殊字符删了导致数值变成 0.85单位丢失。后来加了单元测试才防住。7.2 表格串行的修复方法表格串行表现为行列错位。原因通常是分隔符识别错误或者合并单元格处理不当。修复步骤问题现象可能原因修复方法整行错位分隔符选错重新统计分隔符频率部分列错位合并单元格重建表格矩阵数值串列空格分隔歧义改用固定宽度解析表头丢失表头行被过滤保留首行作为表头7.3 公式丢失的补救公式丢失多半是解析器不支持。补救方案是双解析器交叉验证python-docx 读一遍XML 解析读一遍对比结果缺失的用另一路补。PDF 里的公式如果 OCR 不出来就标记为图片存图片路径检索时返回图片。实操心得清洗管道一定要有抽样人工核对环节。我一般按 5% 比例抽样重点核对数值字段和表格。自动指标只能发现明显问题细微的语义错误还得靠人眼。8. 清洗效果评估与持续优化8.1 评估指标的设计清洗效果不能只看能不能检索出来要量化。我用的指标解析完整率解析出的字符数 / 原始字符数目标 95%。表格准确率抽样表格的单元格正确率目标 90%。术语召回率地质术语被正确识别的比例目标 95%。检索准确率人工评估 top-5 结果的相关性目标 80%。8.2 持续优化的方向清洗管道不是一次性的要持续迭代。我的做法是建一个 bad case 库每次检索出问题就记下来定期分析原因补充规则。比如发现某类 PDF 的表格总是串行就针对这类 PDF 加专门的解析规则。另外知识库要定期重建。探矿数据会更新新报告要入库旧报告的解析规则可能也要调整。我一般每月重建一次重建前先跑一遍全量测试确认没有回归问题。这套清洗方法我在两个探矿项目上跑过第一个项目 2000 份文档清洗后检索准确率从 40% 提到 82%第二个项目 5000 份文档因为前期规则积累得好直接跑到 85%。核心经验就一条别指望通用方案探矿数据的坑必须一个个填。TXT 的编码、Word 的公式、PDF 的扫描、网页的表格每一类都有专属解法老老实实做分类清洗比堆模型参数管用得多。

相关新闻

HarmonyOS API 22 NDK多线程创建实战:从环境配置到性能压测

HarmonyOS API 22 NDK多线程创建实战:从环境配置到性能压测

1. 新特性到底新在哪:API 22之前,Native层写多线程有多别扭 先说结论:HarmonyOS 6 的 API 22 对 NDK 多线程创建的增强,不是简单的“能用 pthread 了”,而是把整个 Native 层的并发编程体验拉到了现代 C 该有的水准。做…

2026/10/8 9:22:15 阅读更多 →
微电网MPC优化调度与自适应预测修正的Python实践

微电网MPC优化调度与自适应预测修正的Python实践

最近在帮一个园区微电网项目做能量管理算法,把 MPC 优化调度这套东西从头到尾用 Python 写了一遍,又单独加了自适应预测修正模块做对比实验。这个方向网上资料不少,但大多数开源代码的问题在于:只有滚动优化的骨架,没有…

2026/10/9 11:19:01 阅读更多 →
HarmonyOS API 22 NDK多线程实战:并发编程、死锁排查与性能优化

HarmonyOS API 22 NDK多线程实战:并发编程、死锁排查与性能优化

1. 项目背景与核心价值:为什么 API 22 的 NDK 多线程值得你立刻跟进先说结论:HarmonyOS 6 的 API 22 在 NDK 层的多线程能力上,把一批此前只能靠 ArkTS 异步或者奇技淫巧才能实现的并发场景,彻底拉回了标准 C/C 的轨道。这意味着&…

2026/10/8 9:21:56 阅读更多 →

最新新闻

树结构核心术语解析:根、父子兄弟、深度高度与路径的工程意义

树结构核心术语解析:根、父子兄弟、深度高度与路径的工程意义

1. 为什么“树”这个词在计算机里被反复提起,却没人真去种一棵?你打开任何一本算法入门书,翻到第三章,大概率会看到一个分叉的图示:一个圆圈在最上面,下面连着两个圆圈,再往下又分出更多——旁边…

2026/10/9 11:18:10 阅读更多 →
锂电池清洗水枪EMC整改实战:从超标12dB到6dB余量的系统化方案

锂电池清洗水枪EMC整改实战:从超标12dB到6dB余量的系统化方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 11:18:10 阅读更多 →
GCC编译全解析:从预处理到链接的实战指南

GCC编译全解析:从预处理到链接的实战指南

1. 从一行报错说起:为什么值得花时间搞懂gcc很多人第一次接触gcc,不是因为想学它,而是因为被它拦住。编译一个C文件,终端甩出一堆看不懂的英文,什么undefined reference to、implicit declaration of function&#xf…

2026/10/9 11:18:10 阅读更多 →
电机测试数据飘?先查平台再查传感器——机械安装细节全解析

电机测试数据飘?先查平台再查传感器——机械安装细节全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 11:18:10 阅读更多 →
Eclipse导出可执行Jar的原理与三种依赖策略详解

Eclipse导出可执行Jar的原理与三种依赖策略详解

简介:本资源是一份面向Java初学者与中小型项目开发者的Eclipse工程打包实战指南,聚焦解决「如何导出含第三方Jar依赖的可执行Jar文件」这一高频部署痛点。内容基于Eclipse Indigo(3.7)环境,系统讲解Runnable JAR File导…

2026/10/9 11:18:10 阅读更多 →
Codex 额度重置后,开发者如何用 TaoToken 统一管理 API 调用?

Codex 额度重置后,开发者如何用 TaoToken 统一管理 API 调用?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 11:17:09 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →