多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

探矿RAG数据清洗实战:TXT、Word、PDF与网页异构文档的高精度处理

探矿RAG数据清洗实战:TXT、Word、PDF与网页异构文档的高精度处理 1. 探矿数据为什么总在清洗环节翻车做过探矿项目的人都有一个共同体会数据拿不到的时候愁数据拿到了更愁。地质队给过来一个压缩包里面塞着几十个TXT格式的钻孔编录、几份Word写的勘探报告、一堆扫描版PDF的化验单外加从内部系统导出的网页表格。这些东西单看都能打开但你要把它们喂给RAG做检索问题就全冒出来了。我最早接触这类需求是在一个铜矿的储量核实项目上。当时的目标很朴素让工程师能用自然语言问ZK1203钻孔在320米到380米之间的铜品位是多少系统能直接给出答案并附上出处。听起来不难但真正动手才发现光是把这些异构文件变成能检索的文本块就花掉了整个项目七成的时间。探矿业务的RAG清洗和通用文档处理有本质区别。通用场景下你丢一段乱码进去模型猜一猜也能凑合。但探矿数据不行一个品位数字错位、一个地层代号被截断可能导致整个矿体圈定出现偏差。所以这里的清洗不是尽量干净而是必须精确到字段级。这篇文章想聊的就是这件事面对TXT、Word、PDF和网页这四类最常见的探矿数据源怎么一步步把它们清洗成高精度可检索的RAG知识库。我会把每一类文件的坑、处理逻辑、参数选择和实测经验都摊开讲适合正在做地质、矿业、勘探相关RAG项目的同行参考也适合任何需要处理专业领域异构文档的开发者。2. 先搞清楚探矿RAG到底要检索什么2.1 探矿数据的四类信息颗粒度在动手写清洗代码之前必须先明确一件事你的RAG最终要回答什么问题。这决定了清洗的颗粒度。探矿业务的信息大致分四层钻孔级一个钻孔的编号、坐标、孔深、开孔日期、终孔层位。这是最粗的颗粒通常一个钻孔一条记录。回次级每个回次的起止深度、岩芯长度、采取率、岩性描述。一个钻孔有几十到上百个回次。样品级样品编号、采样深度、化验元素、品位值、分析方法。这是检索频率最高的层级。报告级整份勘探报告里的段落、结论、附表。这类信息适合做语义检索而非精确匹配。很多项目失败的原因是把这四层混在一个向量库里检索。结果就是用户问一个精确的品位数字系统返回一段报告里的定性描述答非所问。正确的做法是分层建库或者至少用元数据把颗粒度标记清楚。2.2 为什么通用清洗方案在探矿场景会失效我见过不少团队直接拿开源的文档解析工具往上套结果在探矿数据上全军覆没。原因有三个第一探矿TXT大量使用固定宽度或制表符对齐而不是标准的CSV。通用解析器按逗号或空格切分遇到岩性描述里本身带空格的情况字段直接错位。第二Word报告里的表格经常是合并单元格加嵌套表格通用解析器要么丢数据要么把表头和数据混在一起。第三扫描版PDF的化验单是图片不做OCR根本拿不到文字而通用工具默认只提取文本层。提示判断一份PDF是不是扫描版最直接的方法是看能不能选中文字。如果鼠标拖过去选不中任何内容那就是图片型PDF必须走OCR流程。2.3 高精度检索的判定标准什么叫高精度我给一个可量化的标准对于样品级查询系统返回的品位值必须与原始记录完全一致误差为零对于钻孔级查询返回的深度区间必须覆盖用户询问的范围对于报告级查询返回的段落必须包含答案且附有页码或章节出处。达不到这三条就不能叫高精度。这个标准听起来苛刻但探矿业务的容错率就是这么低。一个品位数字从0.45%变成4.5%矿体就从贫矿变富矿经济评价完全两回事。所以清洗环节的每一个决策都要以不引入错误为第一原则。3. TXT钻孔编录的字段对齐与乱码修复3.1 固定宽度文本的切分逻辑探矿TXT最典型的格式是这样的每行一条回次记录字段之间用多个空格或制表符分隔但字段本身可能包含空格。比如岩性描述灰绿色蚀变安山岩 含黄铜矿细脉中间的空格和字段分隔符长得一模一样。直接按空格split必然出错。我的处理方式是先统计每列字符的起始位置。具体做法是读取前100行对每一行记录每个非空格字符的索引然后统计哪些索引位置在所有行中都出现过字符。这些稳定出现的列位置就是字段边界。def detect_column_boundaries(lines, sample_size100): sample lines[:sample_size] max_len max(len(line) for line in sample) col_has_char [0] * max_len for line in sample: for i, ch in enumerate(line): if ch ! : col_has_char[i] 1 # 出现频率超过80%的位置视为字段起始候选 threshold len(sample) * 0.8 boundaries [i for i, cnt in enumerate(col_has_char) if cnt threshold] return boundaries拿到边界后用切片而不是split来提取字段。这样即使岩性描述里有空格也不会被误切。实测下来这个方法对地质队常用的几种编录模板都能自动适配不需要为每个模板写正则。3.2 中文乱码的三种成因与对应处理TXT乱码是探矿数据清洗绕不开的坎。我总结下来主要有三种第一种是编码不一致。地质队的老系统可能用GBK新系统用UTF-8混在一起就乱。处理方式是先用chardet检测编码检测置信度低于0.8的手动指定GBK重试。第二种是字节截断。文件在传输过程中被截断一个中文字符的两个字节只保留了一个显示成问号或方块。这种只能丢弃该行或该字段无法恢复。第三种是字体映射错误常见于从老式仪器导出的数据字符本身是错的但编码是对的。这种需要建立映射表把错误字符替换回正确字符。import chardet def read_txt_safe(filepath): with open(filepath, rb) as f: raw f.read() result chardet.detect(raw) encoding result[encoding] confidence result[confidence] if confidence 0.8: # 置信度低尝试GBK try: return raw.decode(gbk) except UnicodeDecodeError: return raw.decode(utf-8, errorsreplace) return raw.decode(encoding, errorsreplace)注意errorsreplace会把无法解码的字节替换成特殊符号方便后续定位问题行。不要用errorsignore那样会静默丢数据在探矿场景里是致命的。3.3 回次记录与样品记录的关联重建TXT文件里回次记录和样品记录经常是分开的两段中间用空行或分隔线隔开。清洗时要做的关键一步是把它们关联起来。关联的钥匙是深度区间样品记录的采样深度落在哪个回次的起止深度之间就属于哪个回次。这个关联逻辑看似简单但有个坑深度单位可能不统一。有的文件用米有的用厘米还有的用m和cm混写。清洗时必须先统一单位。我的做法是提取深度字段后检查数值范围如果最大值超过10000基本可以判定是厘米除以100转成米。关联完成后每个样品记录都会带上所属回次的岩性信息。这样用户检索某个样品时系统能同时返回品位和岩性描述信息更完整。4. Word勘探报告的表格与公式处理4.1 合并单元格表格的结构还原Word勘探报告里最麻烦的是表格。地质报告喜欢用合并单元格做表头比如化验结果下面横跨三列分别是铜品位钼品位金品位。用python-docx读取时合并单元格会重复出现或者只在一个位置有值。我的处理策略是先把表格转成一个二维数组记录每个单元格的合并跨度。然后根据表头行的合并情况重建列名。具体来说如果第一行某个单元格横跨3列那这3列的表头就是该单元格的值加上第二行对应列的值。from docx import Document def parse_word_table(table): rows [] for row in table.rows: cells [] for cell in row.cells: cells.append(cell.text.strip()) rows.append(cells) # 处理合并单元格相邻重复值合并 for i, row in enumerate(rows): for j in range(len(row) - 1, 0, -1): if row[j] row[j-1] and row[j] ! : row[j] return rows这个逻辑对大多数地质报告表格有效。但遇到嵌套表格表格里还有表格时python-docx支持不好需要降级到解析XML。我的经验是嵌套表格在探矿报告里占比不高如果遇到单独标记出来人工处理比写复杂的自动逻辑更划算。4.2 公式与特殊符号的保留策略勘探报告里经常有品位计算公式、换算系数、单位符号。这些内容如果被清洗掉检索时就找不到。比如用户问铜当量怎么算报告里有个公式但公式被转成了图片或丢失了系统就答不上来。Word里的公式有两种一种是OMML格式Word原生公式一种是图片。OMML可以用python-docx的XML接口提取转成LaTeX或纯文本。图片公式则需要OCR但公式OCR准确率普遍不高我的建议是保留图片并在旁边标注公式图片需人工确认。特殊符号方面地质报告常用‰、℃、°、±等符号。这些在UTF-8里都有对应编码清洗时不要过滤掉。我见过有团队为了干净把所有非中文字符都删了结果把品位单位g/t也删了检索直接废掉。4.3 段落层级与章节编号的提取报告级检索需要保留章节结构。用户问第三章结论是什么系统得知道哪段属于第三章。Word的标题样式Heading 1、Heading 2是天然的层级标记但地质报告经常手动加粗而不套用样式。我的处理方式是双管齐下优先读样式样式为空时用正则匹配章节编号模式如第三章3.1二。匹配到的段落打上层级标签存入元数据。检索时可以用层级过滤比如只在结论章节里搜。import re def extract_section_level(paragraph): text paragraph.text.strip() style paragraph.style.name if paragraph.style else if Heading 1 in style: return 1 if Heading 2 in style: return 2 # 正则兜底 if re.match(r^第[一二三四五六七八九十]章, text): return 1 if re.match(r^\d\.\d, text): return 2 return 0这套组合拳实测下来章节识别准确率能到九成以上。剩下的长尾情况靠人工补标也花不了多少时间。5. PDF化验单与扫描件的OCR精度控制5.1 文本型PDF与图片型PDF的分流PDF处理的第一步是分流。文本型PDF直接用pdfplumber或PyMuPDF提取文字速度快、精度高。图片型PDF必须走OCR慢且容易出错。分流的方法很简单用PyMuPDF打开尝试提取第一页的文字如果字符数少于某个阈值比如50就判定为图片型。import fitz def is_scanned_pdf(filepath, threshold50): doc fitz.open(filepath) text doc[0].get_text() doc.close() return len(text.strip()) threshold这个阈值不是拍脑袋定的。化验单即使有文字层通常也就几十个字符表头加几个数字。如果第一页提取出的文字少于50个字符大概率是扫描件。实测中这个判断的准确率很高。5.2 化验单表格的OCR后处理化验单OCR出来最大的问题是表格线丢失数字和文字挤在一起。我的处理流程是先用OCR引擎如PaddleOCR拿到带坐标的文字块然后根据坐标重建表格。具体来说把文字块按y坐标聚类成行按x坐标排序成列再根据列的对齐情况判断表格结构。化验单里最关键的字段是样品编号和品位值。这两个字段的OCR必须零错误。我的做法是对这两个字段做二次校验样品编号通常有固定格式如ZK1203-H1用正则校验品位值是数字检查是否在合理范围内比如铜品位0.01%到10%之间。超出范围的标记出来人工复核。提示PaddleOCR对中文和数字混排的识别效果比Tesseract好很多尤其是化验单这种密集数字场景。如果预算允许建议用PaddleOCR而不是Tesseract。5.3 多页PDF的跨页表格拼接化验单经常跨页一个表格从第3页延续到第4页。如果按页处理表格会被截断。处理方式是提取每页的表格后检查第一页表格的最后一行和第二页表格的第一行如果列数相同且第二页第一行不是表头就判定为跨页表格合并处理。跨页拼接的难点在于表头识别。有的PDF每页都重复表头有的只在第一页有。我的判断逻辑是如果某行的内容与已知表头高度相似用编辑距离判断就视为表头行不纳入数据。这套逻辑跑下来化验单的表格还原准确率能到95%以上。剩下的5%主要是扫描质量太差或手写体这种只能人工介入。6. 网页端矿权数据的抓取与结构化6.1 动态渲染页面的数据获取矿权数据经常发布在网页上有的是静态HTML有的是JavaScript动态渲染。静态页面用requests加BeautifulSoup就够了动态页面需要用到浏览器自动化工具。我的选择标准是先看页面源码里有没有数据。如果源码里有完整的表格HTML说明是服务端渲染直接解析。如果源码里只有一个空的div数据是JS加载的那就需要浏览器自动化。浏览器自动化工具我常用Playwright它比Selenium快而且对异步加载的处理更自然。from playwright.sync_api import sync_playwright def fetch_dynamic_page(url): with sync_playwright() as p: browser p.chromium.launch() page browser.new_page() page.goto(url, wait_untilnetworkidle) content page.content() browser.close() return contentwait_untilnetworkidle是关键参数它确保所有网络请求都完成了再取内容。如果页面有懒加载还需要滚动到底部触发加载。6.2 表格数据的字段映射网页抓下来的表格列名往往和我们的数据库字段对不上。比如网页上叫许可证号我们库里叫license_no。这需要一个字段映射层。我的做法是维护一个映射字典把常见的中文列名映射到标准字段名。FIELD_MAPPING { 许可证号: license_no, 项目名称: project_name, 矿种: mineral_type, 面积: area, 有效期起: valid_from, 有效期止: valid_to, }映射不上的列不要直接丢弃而是存入一个extra字段保留原始信息。这样后续如果需要还能补映射。6.3 抓取频率与数据更新策略矿权数据更新频率不高通常按月或按季度。没必要频繁抓取。我的策略是首次全量抓取之后每周增量检查一次。增量检查的方法是比对页面的更新时间戳或记录总数有变化才触发全量更新。抓取频率还要考虑对方服务器的承受能力。我一般设置请求间隔至少3秒避免给对方造成压力。这不是技术问题是基本的网络礼仪也能降低被封的风险。7. 四类数据统一入库的向量化策略7.1 分块粒度与重叠窗口的设定四类数据清洗完后要统一入库。入库前最关键的一步是分块。分块粒度直接决定检索精度。我的经验是样品级数据一条记录一个块不拆分。因为样品记录本身就是最小完整单元。回次级数据一个回次一个块如果岩性描述超过500字按段落拆。报告级数据按章节拆每个块不超过800字块之间重叠100字。网页表格一行一个块表头信息附加到每个块上。重叠窗口的作用是防止答案被切断。比如一个结论跨了两个段落没有重叠的话检索可能只命中一半。100字的重叠对中文来说大约是一到两句话足够覆盖大多数跨段情况。7.2 元数据设计让检索能按钻孔和深度过滤光有向量还不够探矿检索经常需要精确过滤。比如ZK1203钻孔320米到380米之间的样品这是一个范围查询纯向量检索做不好。必须靠元数据过滤。我的元数据设计包含这些字段钻孔编号、起始深度、结束深度、数据类型样品/回次/报告、来源文件、页码或行号。检索时先用元数据过滤出ZK1203且深度在320到380之间的块再在这些块里做向量相似度排序。metadata { hole_id: ZK1203, depth_from: 320.0, depth_to: 380.0, data_type: sample, source_file: ZK1203编录.txt, line_number: 156, }这套元数据设计让范围查询的准确率从纯向量的六成提升到了接近百分之百。7.3 混合检索关键词与向量的权重分配纯向量检索对数字不敏感。用户问铜品位0.45%的样品有哪些向量检索可能返回一堆品位相近但不等于0.45%的结果。这时候需要关键词检索兜底。我的方案是混合检索先用关键词精确匹配数字和编号再用向量做语义补充。权重分配上如果查询里包含明确的数字或编号关键词权重占七成向量占三成如果查询是纯语义描述如哪些钻孔见矿效果好则反过来。这个权重不是固定的可以根据实际检索日志动态调整。我一般会记录每次检索的点击情况用点击数据反过来优化权重。8. 清洗质量的验证与常见返工原因8.1 抽样比对清洗前后的一致性检查清洗完必须验证。我的方法是随机抽100条记录把清洗后的结构化数据和原始文件逐字段比对。重点检查数字字段和编号字段这两个字段错一个就是大问题。比对可以用脚本自动化从原始文件里用正则提取关键字段和清洗结果对比。不一致的标记出来人工看。实测中自动化比对能发现八成以上的错误剩下的两成靠人工抽查。8.2 检索命中率的量化评估清洗质量最终要体现在检索效果上。我会构造一组测试查询覆盖四类数据和各种查询类型然后统计命中率。命中率的定义是返回的结果中包含正确答案且正确答案排在前三位。如果命中率低于八成说明清洗或分块有问题。常见原因是分块太大导致噪声多或者元数据缺失导致过滤失效。这时候要回到分块和元数据环节调整。8.3 我踩过的三个返工坑第一个坑是编码检测的置信度阈值设太低。有次一个GBK文件被误判成UTF-8清洗出来全是乱码但脚本没报错直到检索测试才发现。后来我把阈值提到0.9宁可多试几种编码也不放过可疑文件。第二个坑是Word表格的合并单元格处理不彻底。有个报告的表头跨了四列我的脚本只合并了三列导致最后一列的数据全部错位。后来我加了一个校验合并后的列数必须和表头声明的列数一致不一致就报警。第三个坑是PDF的OCR没有做数字校验。有张化验单把0.45识别成了0.4S字母S混进了数字。后来我加了正则校验所有品位值必须是纯数字加小数点含字母的直接标记复核。这三个坑的共同点是错误不会导致程序崩溃但会静默产生错误数据。在探矿场景里静默错误比崩溃更可怕。所以清洗流程里必须有多重校验宁可误报不可漏报。9. 一些实际项目中的取舍心得做探矿RAG清洗最大的体会是不要追求全自动。我早期总想把所有环节都自动化结果发现异常情况太多自动处理反而引入更多错误。后来我调整了策略常规数据自动处理异常数据自动标记、人工处理。人工处理的比例大概占5%到10%但这部分投入换来的是整体数据质量的可靠。另一个心得是关于工具选型。PDF解析我试过pdfplumber、PyMuPDF、camelot最后发现没有哪个工具能通吃。我的做法是组合使用文本型PDF用PyMuPDF快表格型PDF用camelot准扫描件用PaddleOCR识别率高。根据文件特征自动路由到不同工具。最后说一个容易被忽视的点清洗日志。每次清洗都要记录处理了哪些文件、用了什么参数、遇到什么异常、人工干预了什么。这些日志在后续排查问题时价值极高。我有个项目隔了半年要复现清洗结果全靠当时的日志才搞清楚参数是怎么设的。探矿数据的RAG清洗没有一劳永逸的方案每个项目的数据源都有差异。但只要你把字段对齐、编码处理、表格还原、OCR校验这几个核心环节做扎实再配合元数据和混合检索高精度检索是完全可以做到的。
返回列表