
1. 探矿业务文档处理的真实困境1.1 为什么探矿场景下的RAG落地这么难探矿行业的信息化程度说实话比大多数人想象的要低。地质报告、钻孔编录、化验分析单、物化探数据表、历史勘探总结——这些东西散落在各个年代的文件夹里格式横跨手写扫描件、90年代的Word 97文档、PDF、Excel表格还有大量从内部网页系统导出的HTML页面。我接触过的一个典型项目光是整理一个中型矿区的历史资料就涉及超过12000个文件总体量接近40GB。这些文件如果只是存档那倒没什么问题。但一旦要做RAG知识库问题就全暴露出来了。RAG的核心逻辑是“检索增强生成”检索的质量直接决定了生成的质量。而检索的前提是文档被正确地解析、清洗、分块、向量化。探矿业务的文档恰恰在“解析”这一步就卡死了。我见过太多团队拿着LangChain的默认文档加载器直接往上怼结果就是PDF里的表格变成了一坨乱码Word里的公式全丢了TXT文件的编码五花八门网页导出的内容夹杂着大量导航栏和广告。检索出来的东西驴唇不对马嘴最后得出结论说“RAG不适合探矿业务”。这不是RAG的问题是清洗没做到位。1.2 四类文档各自的“坑”在哪里先给不太熟悉的朋友铺垫一下。探矿业务中常见的文档类型大致可以分成四类每一类的清洗难点完全不同TXT文件看起来最简单实际上最阴险。编码问题首当其冲——GBK、GB2312、UTF-8、UTF-8 with BOM甚至还有早期Unix系统留下的Latin-1编码。一个文件用UTF-8读出来是乱码换成GBK就正常了但另一个文件可能反过来。更麻烦的是有些TXT是从老式数据库导出的字段之间用奇怪的符号分隔比如“|”或者“^”还有的用固定宽度对齐解析时稍不注意就把数据切错了。Word文档.doc和.docx是两个完全不同的世界。.doc是二进制格式Python生态里能处理它的库屈指可数而且效果参差不齐。.docx虽然本质是XML但表格嵌套、公式对象、批注、修订记录这些东西处理起来非常棘手。探矿报告里经常有地质柱状图、品位等值线图这些图片如果直接丢弃检索时就丢失了关键信息。PDF文件这是最让人头疼的。扫描版PDF需要OCR文字版PDF又分单栏、双栏、多栏排版。地质报告里的表格往往跨页表头在上一页数据在下一页解析时如果不知道它们是同一张表就会把表头和数据割裂。还有公式PDF里的公式要么是图片要么是特殊字体编码直接提取出来就是乱码。网页文件从内部系统导出的HTML或者用网页抓取工具采集的数据最大的问题是“噪音”。导航栏、页脚、侧边栏、广告、JavaScript生成的动态内容这些如果不剔除会严重污染检索结果。而且网页的DOM结构千变万化没有一个通用的规则能适配所有页面。1.3 清洗的目标从“能读”到“能检索”很多人对清洗的理解停留在“能把文字提取出来就行”。但在RAG场景下这个标准太低了。清洗的目标应该是让每一段文本都具备独立的语义完整性并且携带足够的元数据以便检索时能精准定位。举个例子。一份钻孔编录报告里有一句话“ZK001孔在125.3米处见矿品位1.2g/t。”如果清洗后变成“ZK001孔在125.3米处见矿品位1.2g/t”孤零零的一段检索时用户问“哪些孔见矿品位超过1g/t”这段文本可能被检索到但用户无法知道它来自哪个报告、哪个矿区、什么时间。但如果清洗时保留了元数据——矿区名称、报告年份、钻孔编号——检索的精准度会大幅提升。所以清洗不只是“去乱码”更是“结构化”和“元数据化”。这是探矿业务RAG清洗的核心思路也是后面所有实操的出发点。2. 整体清洗管道的设计与选型逻辑2.1 为什么不用“一把梭”的方案市面上有不少“一站式”文档解析工具比如某些商业API上传文件就能返回文本。我试过几个在通用场景下表现还行但放到探矿业务里就露怯了。原因很简单探矿文档的领域特异性太强。地质术语、钻孔编号规则、品位单位、地层代号这些通用模型根本没见过解析出来的结果经常把“Ar”识别成“Argon”氩气把“Pt”识别成“铂”而不是“板岩”。所以我的方案是分类型处理每一类文档用专门的工具链最后统一输出成结构化的JSON格式。这样做的好处是每个环节都可以单独调试和优化出了问题容易定位。坏处是管道比较长需要维护的代码多一些。但对于探矿这种对精度要求高的场景这个代价是值得的。整个管道的设计思路是这样的原始文件 → 类型识别 → 分类解析 → 文本清洗 → 结构化输出 → 分块 → 向量化其中“分类解析”是核心TXT、Word、PDF、网页各走各的通道。“文本清洗”是公共环节负责去噪、规范化。“结构化输出”是把解析结果统一成带元数据的JSON方便后续处理。2.2 工具选型的考量与对比工具选型这块我踩过不少坑这里把经验分享一下。TXT处理Python内置的chardet库用来检测编码准确率大概在85%左右剩下的15%需要人工兜底。对于固定宽度的TXTpandas.read_fwf比手动切片靠谱得多。如果是分隔符分隔的csv模块配合自定义dialect就能搞定。Word处理.docx用python-docx这是目前最成熟的方案。.doc的话antiword和textract都试过antiword对中文支持更好但需要单独安装二进制文件。如果服务器环境不允许装额外软件可以考虑用LibreOffice的无头模式转换虽然慢一点但兼容性最好。PDF处理这是工具选型的重灾区。PyPDF2和pdfplumber适合文字版PDFpdfplumber对表格的支持更好。扫描版PDF必须上OCRPaddleOCR中文识别效果不错Tesseract也可以但需要调参。对于公式Mathpix的API效果最好但收费开源方案可以试试pix2tex准确率稍低但够用。网页处理BeautifulSoup是基础但光靠它不够。trafilatura专门用于正文提取能自动剔除导航和广告效果比手动写规则好得多。如果网页是JavaScript渲染的那就得上Playwright或Selenium但速度会慢很多。下面这张表是我实际项目中总结的选型对照文档类型推荐工具备选方案关键考量TXTchardet pandasiconv 手动解析编码检测准确率Word (.docx)python-docxdocx2python表格和公式处理Word (.doc)antiwordLibreOffice无头转换中文兼容性PDF (文字版)pdfplumberPyMuPDF表格提取能力PDF (扫描版)PaddleOCRTesseract中文识别准确率网页trafilaturaBeautifulSoup 规则正文提取精度2.3 元数据设计被大多数人忽略的关键元数据设计是清洗管道里最容易被忽视的环节但它对检索质量的影响极大。我的做法是在解析阶段就为每个文本块打上尽可能丰富的标签。探矿业务的元数据至少应该包括矿区名称、报告类型、年份、钻孔编号、地层代号、坐标范围、数据来源。这些信息有些能从文件名提取有些能从文档内容里正则匹配有些需要人工标注。我的策略是能自动提取的自动提取提取不了的留空后续用规则或人工补全。为什么要这么细因为探矿业务的检索需求往往带有强烈的过滤条件。比如“2020年以后XX矿区的见矿记录”如果元数据里没有年份和矿区检索就只能靠语义相似度准确率会大打折扣。有了元数据就可以先做结构化过滤再做向量检索两者结合精度提升非常明显。提示元数据字段不要贪多先确定3-5个最核心的跑通流程后再逐步扩展。一开始就设计几十个字段最后大概率大部分都是空的。3. 四类文档的清洗实操细节3.1 TXT文件编码检测与结构化解析TXT文件的处理第一步永远是编码检测。我写了一个小函数用chardet检测编码如果置信度低于0.8就尝试用常见编码逐个解码看哪个能解出正常的中文字符。import chardet def detect_encoding(file_path): with open(file_path, rb) as f: raw f.read() result chardet.detect(raw) if result[confidence] 0.8: return result[encoding] # 置信度低时尝试常见编码 for enc in [utf-8, gbk, gb2312, latin-1]: try: raw.decode(enc) return enc except UnicodeDecodeError: continue return utf-8 # 兜底编码搞定之后就是结构化解析。探矿业务的TXT大致分两种一种是自由文本比如地质描述另一种是结构化数据比如化验结果表。自由文本直接按段落切分就行结构化数据需要根据分隔符或固定宽度来解析。我遇到过一个典型的坑某矿区的化验数据TXT字段之间用“|”分隔但有些行的末尾多了一个“|”导致解析时多出一个空字段。这种问题没有通用解法只能针对具体数据写清洗规则。我的经验是先抽样100行看看数据长什么样再写解析逻辑不要上来就写通用代码。3.2 Word文档表格、公式与批注的处理Word文档的处理python-docx是主力。但有几个细节需要注意。表格处理python-docx读取表格时默认是按行读取每个单元格是一个Paragraph对象。如果单元格里有多个段落需要遍历。表格的元数据比如表头要单独提取作为该表格所有数据的上下文。from docx import Document def extract_tables(doc_path): doc Document(doc_path) tables_data [] for table in doc.tables: headers [cell.text.strip() for cell in table.rows[0].cells] for row in table.rows[1:]: row_data {} for i, cell in enumerate(row.cells): row_data[headers[i]] cell.text.strip() tables_data.append(row_data) return tables_data公式处理Word里的公式是OMML格式python-docx不支持直接提取。我的做法是用docx2python它能把公式转成LaTeX。如果公式不多也可以手动处理把公式图片单独OCR。批注和修订探矿报告经常有批注和修订记录这些信息有时候很重要比如“此数据待核实”有时候是噪音。我的策略是批注单独提取作为元数据附加到对应段落修订记录只保留最终版本丢弃修订痕迹。3.3 PDF文档文字版与扫描版的差异化处理PDF处理的第一步是判断它是文字版还是扫描版。方法很简单用pdfplumber打开随便取一页看extract_text()返回的字符数。如果字符数很少比如少于50基本可以判定是扫描版。文字版PDF用pdfplumber提取文本和表格。pdfplumber的表格提取能力比PyPDF2强很多它能识别表格的边界线把表格还原成二维数组。但跨页表格需要特殊处理如果上一页的最后一行和下一页的第一行结构相似就合并成一张表。扫描版PDF必须上OCR。PaddleOCR的安装稍微麻烦一点但中文识别效果确实好。我的流程是先用pdf2image把PDF转成图片再用PaddleOCR逐页识别。识别结果里表格区域需要单独处理因为OCR对表格的识别是按行进行的会丢失表格结构。我的做法是先用PaddleOCR的表格识别模型提取表格再和文本识别结果合并。注意OCR的准确率再高也会有错误。探矿数据里的数字特别关键一个小数点错了品位就差十倍。所以OCR结果一定要有校验环节比如用正则表达式检查数字格式异常值人工复核。3.4 网页文件正文提取与噪音剔除网页文件的清洗核心是“去噪”。从内部系统导出的HTML往往包含大量模板代码。我的做法是分三步第一步用trafilatura提取正文。trafilatura会自动识别文章主体剔除导航、页脚、侧边栏。它的准确率在通用网页上能达到90%以上。第二步对提取结果做二次清洗。trafilatura有时候会把一些无关的段落也带进来比如“相关阅读”“上一篇/下一篇”。这些可以用关键词黑名单过滤。第三步提取元数据。网页的title、meta标签、URL路径都包含有用的信息。比如URL里如果有“/report/2023/”就能提取出年份和文档类型。import trafilatura def extract_web_content(html_path): with open(html_path, r, encodingutf-8) as f: html f.read() text trafilatura.extract(html, include_tablesTrue, include_linksFalse) metadata trafilatura.extract_metadata(html) return text, metadata如果网页是JavaScript渲染的trafilatura就无能为力了。这时候需要用Playwright先渲染页面拿到完整的HTML后再用trafilatura处理。但Playwright的速度慢建议只对必要的页面使用。4. 清洗后的分块与向量化策略4.1 分块策略固定长度还是语义分块分块是RAG里另一个容易被轻视的环节。很多人直接用RecursiveCharacterTextSplitter设个chunk_size500就完事了。但在探矿业务里这样做会出问题。探矿文档的语义单元往往不是固定长度的。一段地质描述可能只有两句话但一个化验表格可能有几十行。如果强行按500字切分表格会被切得七零八落检索时只能检索到半张表信息不完整。我的策略是混合分块对于自由文本按段落分块段落太长再按句子切分对于表格整张表作为一个块如果表格太大按行分组但每组都带上表头对于列表按列表项分块但保留列表的上下文标题。具体参数上我一般设chunk_size800chunk_overlap100。800字大约是一页A4纸正文的量语义相对完整。重叠100字是为了避免关键信息刚好被切在边界上。4.2 向量化模型的选择与微调向量化模型的选择直接决定了检索的语义匹配能力。通用场景下text-embedding-ada-002或者开源的bge-large-zh都够用。但探矿业务有大量专业术语通用模型对这些术语的语义理解不够精准。我的做法是先用通用模型跑一版看看检索效果。如果专业术语的检索准确率低就用领域数据微调一个模型。微调的数据不需要太多几百条“查询-文档”对就够了。微调后的模型对“品位”“见矿”“蚀变”这些术语的语义区分度会明显提升。如果不想微调还有一个折中方案在向量化之前先用领域词典做一次术语规范化。比如把“g/t”统一成“克/吨”把“ZK”统一成“钻孔”。这样通用模型也能更好地理解。4.3 元数据过滤与向量检索的结合前面强调了元数据的重要性这里说说怎么用。检索时我的流程是用户输入查询先用规则或小模型提取过滤条件矿区、年份、文档类型。用过滤条件在元数据库里筛选候选文档。对候选文档的向量做相似度检索。返回Top-K结果附带元数据。这样做的好处是检索范围大幅缩小准确率提升。比如用户问“2020年XX矿区的见矿记录”如果没有元数据过滤向量检索可能会返回其他矿区的相似记录有了过滤就只会在2020年XX矿区的文档里检索。提示元数据过滤的字段不要太多否则候选集太小可能漏掉相关结果。一般2-3个过滤条件就够了。5. 常见问题与排查技巧实录5.1 乱码问题的系统排查方法乱码是清洗过程中最常见的问题但乱码的原因有很多种需要系统排查。第一步确认乱码类型。如果显示的是“锟斤拷”那是UTF-8被误读为GBK如果显示的是“佔那是UTF-8被误读为Latin-1如果显示的是“????”那是编码转换时丢失了字符。第二步定位乱码环节。是文件本身编码不对还是读取时编码设错了还是输出时编码设错了我的做法是在管道的每个环节都打印前100个字符看乱码从哪一步开始出现。第三步针对性修复。如果是文件本身编码问题用chardet检测后重新读取如果是读取时设错了改encoding参数如果是输出时的问题检查输出文件的编码设置。下面这张表是我总结的乱码速查表乱码表现可能原因解决方法锟斤拷UTF-8误读为GBK用UTF-8重新读取ä½ÂUTF-8误读为Latin-1用UTF-8重新读取????编码转换丢失字符找到原始编码避免转换方框或问号字体不支持更换字体或编码部分乱码混合编码分段检测编码5.2 表格解析错位的修复思路表格解析错位是PDF和Word处理中的高频问题。表现是表格的列对不齐或者表头和数据错行。PDF表格错位通常是因为PDF里的表格没有明确的边界线pdfplumber只能靠文字位置推断列边界。如果文字间距不均匀推断就会出错。解决方法是调整pdfplumber的table_settings参数比如snap_tolerance和join_tolerance让边界推断更宽松或更严格。Word表格错位通常是因为合并单元格。python-docx读取合并单元格时会把合并后的单元格内容重复填充到每个子单元格。解决方法是检测单元格的_tc属性判断是否是合并单元格然后去重。5.3 OCR识别准确率提升的实战技巧OCR准确率是扫描版PDF处理的核心指标。我试过很多方法总结下来有几个技巧最有效图像预处理在OCR之前先对图像做二值化、去噪、纠偏。OpenCV的adaptiveThreshold和fastNlMeansDenoising效果不错。如果扫描件有倾斜用HoughLines检测直线后旋转校正。分区域识别把页面分成文本区、表格区、图片区分别用不同的OCR模型处理。文本区用通用模型表格区用表格识别模型图片区单独保存。后处理校验OCR结果里的数字用正则表达式校验格式。比如品位数据通常是“数字单位”如果识别出“1.2g/t”但单位缺失就标记为可疑人工复核。词典辅助把探矿领域的专业术语做成词典OCR时用词典做后处理把识别错的术语纠正过来。比如“板岩”被识别成“板岩”词典里没有“板岩”就自动纠正为“板岩”。5.4 清洗管道的性能优化清洗管道跑起来之后性能往往是个瓶颈。12000个文件如果每个文件处理要10秒总共就是33小时。我的优化经验是并行处理用multiprocessing或concurrent.futures做多进程并行。注意OCR是CPU密集型并行度不要超过CPU核心数IO密集型任务可以适当提高并行度。缓存中间结果解析结果、OCR结果都缓存到磁盘避免重复处理。用文件哈希作为缓存键文件没变就直接读缓存。增量处理只处理新增或修改的文件。用文件的修改时间和大小做判断没变的文件跳过。资源限制OCR很吃内存如果并行度太高容易OOM。我的做法是给每个进程设内存上限超了就排队等待。6. 从清洗到检索的完整链路验证6.1 构建小规模测试集验证清洗效果清洗管道写完之后不要急着上全量数据。先构建一个小规模测试集比如100个文件覆盖四种类型人工检查清洗结果。测试集的构建要讲究代表性TXT要包含不同编码的Word要包含表格和公式的PDF要包含文字版和扫描版的网页要包含不同模板的。每个类型至少20个文件。验证的指标包括文本提取完整率提取出的文本占原文的比例、表格还原准确率表格结构是否正确、元数据提取准确率关键字段是否提取正确。我的经验是完整率要达到95%以上表格准确率要达到90%以上元数据准确率要达到85%以上才算合格。6.2 检索效果的人工评估方法清洗效果最终要体现在检索上。我的评估方法是准备50个典型查询人工标注每个查询应该返回哪些文档然后看实际检索结果和标注的匹配度。查询的设计要覆盖不同场景简单查询“XX矿区的见矿记录”、复杂查询“2020年以后品位超过1g/t的钻孔”、模糊查询“关于蚀变带的地质描述”。每个场景至少10个查询。评估指标用召回率和准确率。召回率是检索到的相关文档占所有相关文档的比例准确率是检索到的文档中相关文档的比例。我的目标是召回率90%以上准确率80%以上。6.3 持续迭代从反馈中优化清洗规则清洗管道不是一次性的工作需要持续迭代。我的做法是在检索界面加一个“反馈”按钮用户觉得检索结果不对就点一下记录下查询和实际期望的结果。每周汇总一次反馈分析是清洗问题还是检索问题然后针对性优化。常见的优化方向包括补充元数据字段、调整分块参数、更新术语词典、微调向量模型。每次优化后重新跑一遍测试集看指标是否提升。提示迭代要有节奏不要频繁改动。我的节奏是两周一次小优化一个月一次大优化。频繁改动会导致无法判断哪个改动有效。7. 一些踩坑后的个人体会探矿业务的RAG清洗说到底是一个“脏活累活”。没有哪个工具能一键搞定必须针对每一类文档、每一种数据格式写专门的规则。我最大的体会是不要追求通用要追求可用。一个只适用于本矿区文档的清洗规则比一个号称通用的但准确率只有70%的方案价值大得多。另外元数据的重要性怎么强调都不为过。我见过太多团队花大力气优化向量模型却忽略了元数据结果检索效果始终上不去。实际上在探矿这种强结构化场景下元数据过滤带来的精度提升往往比换一个更强的向量模型更明显。最后分享一个小技巧清洗管道里加一个“人工复核队列”。对于置信度低的解析结果比如OCR置信度低于0.9的、编码检测置信度低于0.8的自动进入复核队列人工确认后再入库。这样既保证了效率又保证了质量。这个队列一开始可能很长但随着规则优化会越来越短。