资讯详情

资讯详情

探矿RAG知识库文档清洗实战:TXT、Word、PDF与网页处理

1. 探矿业务文档处理的真实困境搞过探矿项目的人都知道一个矿区从普查到详查再到勘探积累下来的资料能把人逼疯。地质报告是Word写的化验数据是TXT存的老专家留下的扫描件是PDF还有大量矿权公示、区域地质资料散落在各种网页上。这些资料不是放在一个文件夹里等你慢慢看而是分散在十几个项目组、几代地质队员的硬盘里格式五花八门编码千奇百怪。我接手过一个铜多金属矿的RAG知识库搭建项目光是整理资料就花了整整三周。最让人崩溃的不是资料多而是打开一个TXT文件全是乱码——你根本不知道是GBK还是UTF-8更不知道里面是化验数据还是钻孔编录。Word文档里嵌着几十个公式图片PDF扫描件连文字层都没有网页抓下来的内容带着一堆导航栏和广告。这些东西直接喂给RAG系统检索出来的结果能把你气笑问“矿区地层产状”它给你返回一段“版权所有”的页脚。所以这篇文章我想聊的就是探矿业务场景下怎么把TXT、Word、PDF和网页这四类最常见的资料从“乱码状态”清洗成“高精度检索状态”。这不是一个纯技术问题而是一个业务理解加工程实践的综合问题。适合正在搭建地质领域RAG知识库的工程师、地质信息化从业者以及被文档格式折磨过的项目负责人参考。下面我会从整体设计思路开始一步步拆到每个格式的具体处理方案最后给出排查技巧和避坑经验。2. 整体清洗管线的设计思路2.1 为什么不能直接上通用RAG框架很多人搭RAG知识库的第一反应是找个开源框架把文档往里面一扔就完事。我试过在探矿业务里这条路走不通。通用框架的文档加载器默认假设你的文件是规范的、编码是标准的、结构是清晰的。但地质资料恰恰相反一个TXT文件可能是用GB2312编码写的化验数据字段之间用制表符分隔但前五行是表头说明中间夹杂着空行和注释一个Word文档的表格里藏着品位数据但表格没有表头你得根据上下文推断哪一列是铜品位、哪一列是标高。通用框架处理这类文档要么直接报错要么把表格拍平成一行文本检索时完全丢失结构信息。所以我的思路是先做格式归一化再做语义结构化最后才进入向量化环节。格式归一化解决编码和容器问题语义结构化解决业务字段识别问题向量化解决检索问题。这三步分开做每一步都可以单独调试和验证出了问题容易定位。2.2 清洗管线的四个核心阶段我把整条管线拆成四个阶段每个阶段有明确的输入输出和质量标准。第一阶段是编码探测与文本提取。目标是拿到“干净的纯文本”不管原始文件是什么格式。TXT要解决编码问题Word要解决公式和表格问题PDF要解决扫描件和文字层问题网页要解决正文提取问题。这个阶段的输出是统一的UTF-8纯文本保留基本的段落结构。第二阶段是业务字段识别与结构化。探矿资料里有大量结构化信息比如钻孔编号、坐标、品位、厚度、产状。这些信息如果只是作为普通文本进入向量库检索精度会大打折扣。我的做法是用正则加规则引擎把能识别的字段抽出来存成结构化的JSON同时保留原始文本用于全文检索。第三阶段是分块与元数据注入。地质文档的分块不能按固定字数切因为一个钻孔的描述可能跨好几段切开就断了语义。我通常按“地质单元”分块比如一个钻孔一个块、一个剖面一个块同时给每个块注入元数据矿区名称、资料类型、年份、坐标范围。这些元数据在检索时可以做过滤大幅提升精度。第四阶段是向量化与索引构建。这个阶段反而是最标准的选一个适合中文地质文本的embedding模型建向量索引和全文索引的双路检索。关键是前三个阶段的质量决定了这个阶段的上限。2.3 方案选型的几个关键取舍在工具选型上我踩过不少坑。TXT编码探测Python的chardet库在短文本上准确率一般后来换成charset-normalizer加人工规则兜底准确率明显提升。Word解析python-docx能处理大部分文档但遇到嵌入的OLE公式对象就无能为力需要额外用公式转LaTeX的工具链。PDF解析pdfplumber对文字层PDF效果好但扫描件必须走OCR我试过几个OCR方案最终选了对中文和表格支持较好的组合。网页抓取这块我放弃了通用爬虫框架改用针对性的正文提取算法。因为矿权公示网页的结构相对固定用Readability算法加自定义规则比通用爬虫的准确率高很多。这些取舍背后的逻辑都是一样的探矿业务的文档有很强的领域特征通用方案在细节上一定会出问题必须在关键环节做领域适配。3. TXT文件清洗从乱码到结构化数据3.1 编码探测的实战策略TXT文件的编码问题在探矿资料里特别严重。早期地质队用的系统五花八门有GB2312、GBK、GB18030还有用UTF-8但带BOM的甚至遇到过用Latin-1编码存中文的奇葩情况。直接读进来就是一堆问号和方块。我的处理策略是三级探测。第一级用charset-normalizer做统计探测它对长文本的准确率很高但短文本容易误判。第二级用业务规则兜底如果探测结果是GB系列但文本里出现了大量非常用汉字就尝试用GB18030重新解码如果探测结果是UTF-8但解码报错就检查是否有BOM头。第三级是人工确认对于关键文件我会抽样打印前200个字符肉眼确认是否正常。这里有个经验不要相信单一工具的探测结果一定要做交叉验证。我写了一个小函数同时用chardet和charset-normalizer探测如果两者结果不一致就标记为“待确认”人工介入。这个策略把编码错误率从15%降到了2%以下。import chardet from charset_normalizer import from_bytes def detect_encoding(raw_bytes): # 双工具交叉验证 r1 chardet.detect(raw_bytes[:10000]) r2 from_bytes(raw_bytes[:10000]).best() enc1 r1.get(encoding, ).lower() enc2 str(r2.encoding).lower() if r2 else if enc1 enc2: return enc1, high # 结果不一致时优先信任charset-normalizer return enc2 or enc1, low3.2 化验数据TXT的结构化提取探矿业务里最常见的TXT是化验数据导出文件。这类文件通常有固定的格式前几行是说明然后是表头接着是数据行字段之间用制表符或逗号分隔。但不同实验室导出的格式不一样有的用中文表头有的用英文缩写有的甚至没有表头。我的做法是先做格式嗅探统计每行的分隔符数量如果制表符数量稳定就按制表符切分如果逗号数量稳定就按逗号切分。然后识别表头行表头行通常包含“样品编号”“Cu”“品位”等关键词用关键词匹配加位置规则确定表头位置。数据行则按表头字段映射抽取出样品编号、元素、品位、坐标等结构化字段。这里有个坑有些化验数据用“-”表示未检出用“0.01”表示低于检测限。这些特殊值如果直接当字符串处理后续做数值分析会出错。我的处理是把它们转成统一的表示未检出存为null低于检测限存为检测限的一半同时保留原始字符串在元数据里。3.3 钻孔编录TXT的段落还原钻孔编录TXT比化验数据更麻烦因为它是半结构化的叙述性文本。一个典型的钻孔编录可能长这样ZK001 孔深0-5m 第四系覆盖层 黄褐色粘土 ZK001 孔深5-12m 强风化花岗岩 褐黄色 岩芯采取率75% ZK001 孔深12-35m 花岗闪长岩 灰白色 见黄铜矿化 品位0.3%这种文本如果按固定字数切块很可能把一个钻孔的信息切到两个块里。我的做法是按钻孔编号分组同一个钻孔的所有编录行合并成一个块然后在块内按孔深排序。这样检索“ZK001的矿化信息”时能一次性拿到完整的钻孔描述。同时我会从每行里抽取结构化字段钻孔编号、起始深度、终止深度、岩性、颜色、矿化类型、品位。这些字段存成JSON和原始文本一起进入索引。检索时可以用结构化字段做精确过滤比如“找所有见黄铜矿化且品位大于0.2%的钻孔”比纯语义检索准得多。4. Word文档清洗公式、表格与正文的分离4.1 Word文档的三种内容类型地质报告Word文档里通常混着三种内容正文段落、表格、公式。这三种内容的处理方式完全不同如果混在一起解析结果一定是一团糟。我的做法是先把Word文档拆成三个流正文流、表格流、公式流分别处理后再按原始位置合并。正文流用python-docx的paragraph遍历保留段落样式和标题层级。表格流用docx的table遍历每个表格转成二维数组同时记录表格在文档中的位置。公式流最麻烦python-docx只能拿到公式的OLE对象拿不到公式内容。我的方案是用LibreOffice做一次转换把docx转成docx是的听起来很怪在转换过程中把OLE公式转成MathML或LaTeX然后再解析。4.2 表格数据的结构化处理地质报告里的表格往往是核心数据所在比如品位表、储量表、剖面数据表。这些表格如果直接转成文本列和行的对应关系就丢了。我的处理方式是先把表格转成二维数组然后做表头识别。表头识别用规则加启发式第一行如果包含“编号”“名称”“品位”“厚度”等关键词就认定为表头如果第一行是合并单元格就往下找。识别到表头后把每一行数据转成“字段名: 值”的键值对形式这样进入向量库后检索“铜品位0.5%的样品”时能精确匹配到对应行。同时我会把整个表格的Markdown表示也存一份用于需要看完整表格的场景。from docx import Document def extract_tables(docx_path): doc Document(docx_path) tables_data [] for i, table in enumerate(doc.tables): rows [] for row in table.rows: cells [cell.text.strip() for cell in row.cells] rows.append(cells) # 表头识别 header rows[0] if rows else [] records [] for row in rows[1:]: record dict(zip(header, row)) records.append(record) tables_data.append({ table_index: i, header: header, records: records, raw_markdown: to_markdown(rows) }) return tables_data4.3 公式图片转LaTeX的可行方案Word里的公式有两种一种是OLE对象一种是图片。OLE对象可以用LibreOffice转换图片公式就得走OCR。我试过几个公式OCR方案对简单公式效果还行复杂公式比如带积分、矩阵的准确率明显下降。我的策略是分级处理对于OLE公式优先用LibreOffice转LaTeX对于图片公式先用公式OCR识别识别结果标记置信度低置信度的公式保留图片链接在检索时返回图片而不是文本。这样虽然不能100%还原公式但至少不会因为错误的OCR结果误导检索。这里有个经验地质报告里的公式大部分是简单的品位计算公式、厚度计算公式复杂公式很少。所以公式OCR的准确率在实际场景中是可以接受的。如果遇到特别复杂的公式人工补录的成本也比全量OCR低。4.4 正文与表格的合并策略三个流分别处理完后需要按原始位置合并。我的做法是在解析时记录每个元素的文档顺序索引合并时按索引排序。正文段落直接拼接表格插入到对应位置公式插入LaTeX或图片链接。这样合并后的文本既保留了原始文档的结构又方便后续分块。合并后的文本我会做一次清洗去掉页眉页脚、去掉空段落、统一标点符号。页眉页脚识别用规则如果某段文字在多个页面重复出现就判定为页眉页脚。这个规则在python-docx里可以通过section的header/footer属性直接拿到比事后识别更准。5. PDF与网页清洗扫描件与正文提取5.1 PDF文字层与扫描件的分流处理PDF在探矿资料里分两类一类是原生PDF有文字层可以直接提取另一类是扫描件本质上是图片必须走OCR。我的分流策略是先尝试用pdfplumber提取文字如果提取到的文字数量少于阈值比如每页少于50个字符就判定为扫描件转走OCR流程。原生PDF的提取相对简单但要注意表格和分栏。pdfplumber的extract_tables能处理大部分表格但遇到跨页表格会断。我的做法是把跨页表格按页提取后用表头匹配的方式合并。分栏PDF用pdfplumber的layout模式按x坐标切分栏位再按阅读顺序拼接。扫描件OCR我选的是对中文和表格支持较好的方案识别后做后处理去掉识别噪声、修正常见错字比如“花岗岩”识别成“花冈岩”、恢复段落结构。OCR结果的质量直接决定后续检索效果所以这一步不能省。5.2 网页正文提取的领域适配矿权公示、区域地质资料这类网页正文提取的难点在于去掉导航栏、广告、页脚。通用Readability算法在新闻网站上效果好但在政府公示网站上经常把正文也去掉。我的做法是在Readability基础上加领域规则先定位包含“矿权”“地质”“储量”等关键词的区块以这些区块为中心向外扩展直到遇到导航栏或页脚的特征元素。网页抓取还要注意动态加载。有些公示页面用JavaScript渲染直接请求HTML拿不到内容。我的方案是用无头浏览器渲染后再提取但要注意控制频率避免对目标网站造成压力。抓下来的内容存成HTML和纯文本两份HTML用于追溯原始结构纯文本用于后续处理。5.3 四类格式的统一输出规范四类格式处理完后统一输出成一种中间格式。我定义的中间格式是一个JSON对象包含以下字段字段名类型说明doc_idstring文档唯一标识source_typestringtxt/word/pdf/webraw_textstring清洗后的纯文本structured_dataobject抽取的结构化字段metadataobject矿区、年份、资料类型等chunksarray分块后的文本块列表这个中间格式的好处是后续的分块、向量化、索引构建都不需要关心原始格式只需要处理统一的JSON。这样整条管线的耦合度低任何一个环节出问题都容易替换和调试。6. 常见问题与排查技巧实录6.1 编码问题的快速定位方法TXT乱码是最常见的问题但定位起来有技巧。我的排查顺序是先看文件头有没有BOM有BOM的基本是UTF-8然后用file命令看系统猜测的编码再用chardet和charset-normalizer交叉验证。如果三者结果不一致就抽样打印十六进制看中文字符的字节范围。GB系列的中文字节范围是0xB0-0xF7UTF-8是0xE4-0xE9开头。这个方法能快速定位编码类型。6.2 Word公式解析失败的兜底方案python-docx解析公式失败是常态兜底方案是直接用zipfile打开docx找到word/embeddings/目录下的OLE对象用olefile库解析。如果还是失败就退回到LibreOffice转换方案。我通常把LibreOffice转换作为标准流程的一部分而不是兜底因为它的公式转换成功率最高。6.3 PDF表格跨页断裂的修复跨页表格断裂是PDF解析的经典问题。我的修复方法是提取每页的表格后比较相邻页表格的表头。如果表头相同或相似就判定为同一个表格合并数据行。如果表头不同就检查最后一行的列数是否和下一页第一行一致一致的话也合并。这个规则能修复大部分跨页表格。6.4 网页抓取内容重复的去除网页抓取经常遇到内容重复比如同一篇公示在多个栏目下出现。我的去重方法是先做URL规范化去掉参数和锚点然后对正文做SimHash相似度超过阈值的判定为重复只保留最早抓取的那份。这个策略把重复率从30%降到了5%以下。6.5 常见问题速查表问题现象可能原因排查方法解决方案TXT打开全是乱码编码不匹配十六进制查看字节范围用GB18030重新解码Word表格数据错位合并单元格检查表格XML结构按合并单元格展开PDF提取文字为空扫描件无文字层检查每页字符数转OCR流程网页正文提取不全动态渲染查看HTML源码用无头浏览器渲染检索结果不相关分块过大检查块内文本长度按地质单元重新分块公式显示为乱码OLE对象未转换检查embeddings目录用LibreOffice转换7. 分块与元数据注入的实操细节7.1 按地质单元分块的逻辑地质文档的分块不能按固定字数因为地质描述有很强的单元性。一个钻孔、一个剖面、一个矿体都是独立的语义单元。我的分块规则是钻孔编录按钻孔编号分块剖面数据按剖面线号分块矿体描述按矿体编号分块。这样每个块都是一个完整的地质单元检索时不会出现“半截钻孔”的情况。对于没有明确单元标识的文本比如区域地质概述我按段落分块但设置最大块长和重叠窗口。最大块长控制在500字左右重叠窗口100字保证跨块的语义连续性。7.2 元数据字段的设计元数据是提升检索精度的关键。我给每个块注入的元数据包括矿区名称、资料类型普查/详查/勘探、年份、坐标范围、钻孔编号如果有、矿种。这些字段在检索时可以做过滤比如“在XX矿区找2015年以后的铜矿化信息”比纯语义检索准得多。元数据的来源有两个一是从文档本身抽取比如从标题里提取矿区名称二是从文件路径和文件名推断比如路径里包含“XX矿区”就注入这个元数据。两种来源冲突时以文档内容为准。7.3 双路索引的构建最终索引我建了两路向量索引和全文索引。向量索引用embedding模型编码适合语义检索全文索引用倒排索引适合关键词精确匹配。检索时两路同时查结果做融合排序。融合策略是关键词命中加权语义相似度加权元数据过滤做硬性条件。这个双路索引的方案在探矿业务里的检索精度比单路向量索引提升了30%以上。特别是查具体品位、坐标这类精确信息时全文索引的优势非常明显。8. 一些实操中的个人体会这套清洗管线我在三个探矿项目里跑过最大的体会是清洗的质量决定了RAG的上限而清洗的难点不在技术在业务理解。同样一个TXT文件不懂地质的人看到的就是一堆数字懂地质的人能看到品位、厚度、产状。所以做地质RAG清洗一定要和地质人员泡在一起让他们告诉你哪些字段重要、哪些格式常见、哪些错误不能忍。另一个体会是不要追求一步到位要迭代。我第一版清洗管线只处理了TXT和WordPDF和网页是后来加的。每加一种格式都会发现之前的设计有需要调整的地方。所以管线要设计得松耦合每个格式的处理模块可以独立替换。最后分享一个小技巧清洗过程中一定要保留原始文件和中间结果。我遇到过好几次清洗后的文本出了问题回溯到中间结果发现是某一步的规则写错了。如果只保留最终结果排查起来会非常痛苦。保留原始文件和中间结果虽然占空间但排查问题时能省大量时间。这个内容后续还可以这样扩展把结构化字段接入图数据库构建地质知识图谱和RAG做混合检索。结构化数据查精确关系RAG查语义描述两者互补检索精度还能再上一个台阶。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →