探矿RAG数据清洗实战:TXT、Word、PDF与网页四类脏源处理全解析

发布时间:2026/10/9 4:23:21
探矿RAG数据清洗实战:TXT、Word、PDF与网页四类脏源处理全解析 1. 探矿数据清洗为什么成了RAG落地的第一道坎做过探矿业务数据的人都有一个共同感受数据不是没有而是太杂。一个中型勘探项目跑下来地质报告是Word钻孔编录是Excel导出的TXT化验单是PDF扫描件历史资料甚至是几十年前的纸质档案翻拍再加上从公开地质资料网站抓下来的HTML页面。这些数据堆在一起你想用RAG搭一个能问答的知识库第一步就卡住了——检索出来的东西驴唇不对马嘴。我最初接手这类项目时天真地以为把文件往向量库里一扔就完事了。结果测试阶段问“某矿区ZK1201钻孔的见矿深度是多少”系统返回的却是一段无关的矿权登记公告。问题出在哪出在清洗环节。探矿领域的文本有几个非常要命的特点大量专业符号和单位混排、表格结构复杂、PDF里嵌着扫描图片、网页正文被导航栏和广告淹没。如果不做针对性清洗嵌入模型拿到的就是一堆噪声检索精度自然惨不忍睹。这篇文章想聊的就是我在实际探矿RAG项目里踩过的坑和总结出的一套清洗方法。核心围绕四类数据源展开TXT、Word、PDF和网页。每一类都有它的脾气处理方式完全不同。我会把每一步的操作逻辑、参数选择理由、以及实测效果都讲清楚让你能直接照着复现。不管你是刚接触RAG的数据工程师还是被脏数据折磨过的地质信息化从业者这些内容应该都能帮你少走弯路。2. 先搞清楚探矿文本的脏法再谈清洗策略2.1 探矿数据的四类脏源与它们的破坏力在动手写清洗代码之前我花了整整一周时间做数据摸底。把项目里所有待处理的文件过了一遍按来源和格式分类统计每类的数量、平均大小和典型问题。这一步看起来笨但极其关键——你连数据长什么样都不知道怎么设计清洗规则摸底结果大致是这样的TXT文件占比约35%主要来自钻孔编录仪导出的原始记录和部分老系统的数据导出Word文档占25%是各类地质报告和设计书PDF占30%包括扫描版化验报告、公开刊物和归档的评审意见网页数据占10%是从地质资料馆网站和行业资讯站抓取的。这四类数据的“脏法”完全不同。TXT的问题在于编码混乱和分隔符不统一同一个项目里可能同时存在GBK、UTF-8甚至GB2312编码的文件打开就是乱码。Word的问题在于格式嵌套太深表格里套表格还有大量批注和修订痕迹混在正文里。PDF最麻烦扫描件需要OCR而OCR对地质专业术语的识别率堪忧“花岗闪长岩”被识别成“花岗闪长岩”还算好的有时候直接变成一串乱码。网页数据则是正文和导航、广告、版权信息搅在一起需要精准提取。提示做数据摸底时建议随机抽取每类文件各20个人工阅读并记录问题类型。这个时间投入会在后续清洗规则设计时加倍回报给你。2.2 清洗目标不是“干净”而是“可检索”这里有一个认知误区需要先打破。很多人觉得清洗就是把数据弄干净越干净越好。但在RAG场景下清洗的目标不是绝对干净而是让文本块在向量空间中能被正确检索到。什么意思就是你要保证用户问“见矿深度”时包含这个信息的文本块和问题的向量距离足够近。这就引出一个关键判断哪些信息该保留哪些该丢弃。比如钻孔编录里的“岩芯采取率”这个字段如果清洗时把它和后面的数值拆散了检索时可能就匹配不上。但如果你把整行保留又可能因为格式符号太多影响嵌入质量。我的经验是对于结构化程度高的TXT和Word表格尽量保持“字段名数值单位”的完整语义单元对于PDF和网页优先保证段落完整性不要为了去噪把句子切碎。另一个容易被忽略的点是元数据的保留。探矿数据里钻孔编号、坐标、标高这些信息往往出现在页眉页脚或表格标题里。清洗时如果把这些当噪声删了检索时就丢失了关键的过滤维度。我的做法是在清洗阶段就把这些元数据抽取出来作为独立字段存储后续检索时可以配合元数据过滤来缩小范围。2.3 清洗流程的整体设计思路基于上面的认知我设计了一套分层的清洗流水线。整体思路是先做格式归一化把所有数据转成统一的中间格式再做内容提取把正文从各种容器里剥离出来然后做语义分块按照探矿文本的逻辑结构切分最后做质量校验用检索测试来验证清洗效果。这个流程不是线性的中间有反复。比如语义分块后可能发现某些块质量太差需要回到内容提取阶段调整参数。我建议在每一步都保留中间结果方便回溯和对比。具体到每类数据源的处理方法下面几个章节会逐一展开。3. TXT文件的编码陷阱与结构化重建3.1 编码检测为什么chardet经常靠不住TXT文件处理的第一道关就是编码。我试过用Python的chardet库自动检测结果在探矿数据上准确率只有七成左右。原因在于探矿TXT里大量出现专业符号和数字这些字符在不同编码下的字节模式很相似chardet容易误判。比如一个包含“γ射线测量”的GBK文件chardet可能因为γ字符的字节特征而判断成ISO-8859-1。后来我改用组合策略先用chardet给一个候选编码然后用该编码尝试解码前1000个字符如果出现替换字符或异常符号就换下一个候选。候选列表按优先级排列UTF-8、GBK、GB18030、GB2312、Latin-1。实测下来这套组合拳的准确率能到95%以上。对于仍然失败的文件我会记录到异常列表人工确认编码后再批量处理。import chardet def detect_encoding(file_path): with open(file_path, rb) as f: raw f.read(10000) candidates [utf-8, gbk, gb18030, gb2312, latin-1] result chardet.detect(raw) if result[encoding]: candidates.insert(0, result[encoding]) for enc in candidates: try: raw.decode(enc) return enc except (UnicodeDecodeError, LookupError): continue return utf-8注意GB18030是GBK的超集能覆盖更多生僻字。如果数据里出现地质专用字符优先用GB18030解码。3.2 分隔符归一化与字段对齐编码问题解决后下一个坑是分隔符。钻孔编录TXT常见的有逗号分隔、制表符分隔、竖线分隔甚至还有用多个空格对齐的。更麻烦的是同一个文件里可能混用多种分隔符。我遇到过一份文件表头用制表符数据行用逗号备注行用竖线简直是在考验解析器的耐心。我的处理策略是分两步走。第一步统计每行中各种候选分隔符的出现次数取出现次数最多且位置最一致的那个作为主分隔符。第二步对于主分隔符切分后字段数异常的行尝试用备选分隔符重新切分或者用正则表达式按固定宽度切分。这里的关键是建立一个字段数基线比如表头有12个字段那么数据行也应该是12个偏离这个数字的行就需要特殊处理。字段对齐之后还要处理缺失值。探矿数据里缺失值有多种表示空字符串、“-”、“/”、“无”、“NULL”。我统一把它们映射为None后续入库时再根据字段类型决定是填默认值还是标记为缺失。这个映射表需要根据实际数据不断补充没有一劳永逸的方案。3.3 从自由文本到结构化记录的转换有些TXT文件不是表格型的而是自由文本比如地质日志、野外记录。这类文本没有固定分隔符但往往有隐含的结构。比如钻孔日志通常以日期开头后面跟着班次、进尺、岩性描述等信息。我的做法是用正则表达式提取关键字段把自由文本转成半结构化的JSON。以钻孔日志为例我定义了一组模式日期模式匹配“2023年5月12日”或“2023-05-12”进尺模式匹配“进尺3.5m”或“回次进尺3.5米”岩性描述则抓取从“岩性”到下一个字段名之间的文本。这些模式不是拍脑袋想的而是从实际数据里归纳出来的。我建议你先人工阅读50到100条记录把常见的表达方式列出来再写正则。转换后的结构化记录每个字段都有明确的语义标签。这样在后续分块时我可以按“一个回次”或“一个班次”作为语义单元来切分而不是机械地按字数切。实测表明这种基于语义单元的分块方式检索准确率比固定长度分块高出不少。4. Word文档的格式泥潭与正文提取4.1 表格嵌套与合并单元格的解析难题Word文档在探矿业务里主要用于地质报告和设计书这类文档的典型特征是表格多、嵌套深。一个钻孔数据表可能嵌在“资源量估算”章节里而章节本身又在另一个大表格的单元格中。python-docx库能读取表格但对嵌套表格和合并单元格的支持有限直接遍历会丢失层级关系。我的解决方案是递归解析。写一个函数输入是一个表格对象输出是一个二维数组其中每个单元格如果是表格就递归调用自身把结果作为该单元格的内容。对于合并单元格python-docx会把合并区域的每个格子都返回相同的文本我通过比较相邻单元格的文本来判断是否合并然后只保留第一个其余标记为合并占位。from docx import Document def parse_table(table): rows [] for row in table.rows: cells [] prev_text None for cell in row.cells: if cell.tables: content parse_table(cell.tables[0]) else: content cell.text.strip() if content prev_text: cells.append(None) # 合并单元格占位 else: cells.append(content) prev_text content rows.append(cells) return rows解析出来的表格数据我会转成Markdown表格或JSON格式存储。Markdown表格的好处是可读性强方便人工校验JSON则更适合后续程序处理。两种格式我都会保留一份。4.2 批注、修订与隐藏文本的取舍地质报告在评审过程中会产生大量批注和修订。这些内容对RAG检索来说大部分是噪声但有些批注包含了重要的补充说明直接删掉可惜。我的做法是分类处理修订痕迹插入/删除直接接受最终版忽略修订过程批注则提取出来作为独立字段附加在对应段落后面用特殊标记包裹比如“[批注...]”。这样检索时如果匹配到批注内容也能返回给用户但不会干扰正文的语义完整性。隐藏文本的处理更微妙。有些Word文档里设置了隐藏文字可能是草稿内容或内部备注。python-docx默认不读取隐藏文本但如果你需要可以通过检查run的font.hidden属性来判断。我的建议是默认忽略隐藏文本除非有明确证据表明其中包含关键信息。4.3 样式驱动的章节结构还原地质报告有严格的章节层级章、节、小节、条目。这些层级通常通过Word的标题样式Heading 1、Heading 2等来体现。但实际文档里很多人不用样式而是手动加粗、放大字号来模拟标题。这就导致程序无法直接通过样式判断章节结构。我的应对策略是双管齐下。首先优先读取样式信息如果段落样式是Heading系列直接采用其层级。对于没有样式的段落用启发式规则判断如果段落长度小于30字、以数字编号开头如“3.2.1”、且字体加粗或字号大于正文就判定为标题。这些规则需要根据实际文档调整阈值我一般会先跑一遍把判定结果输出成大纲人工抽查后再微调。章节结构还原后分块就有了天然的依据。我通常按最小章节单元比如三级标题下的内容作为一个块如果内容太长再按段落细分。这样每个块都有明确的章节路径作为元数据检索时可以按章节过滤精度提升很明显。5. PDF解析从扫描件到可检索文本的完整链路5.1 文本型PDF与扫描型PDF的分流判断PDF是探矿数据里最棘手的格式因为它分两种完全不同的类型文本型PDF和扫描型PDF。文本型PDF可以直接提取文字扫描型PDF本质上是图片必须走OCR。如果不对这两种类型分流要么对文本型PDF做了无谓的OCR浪费算力要么对扫描型PDF提取出一堆空字符。我的分流方法是用PyMuPDFfitz打开PDF遍历前几页统计每页的文本字符数。如果平均每页字符数大于50判定为文本型否则判定为扫描型。这个阈值可以根据实际情况调整我试过30和10050在探矿数据上表现最稳。对于混合型PDF部分页文本、部分页扫描则按页分别处理。import fitz def classify_pdf(pdf_path): doc fitz.open(pdf_path) total_chars 0 sample_pages min(5, len(doc)) for i in range(sample_pages): total_chars len(doc[i].get_text().strip()) doc.close() avg_chars total_chars / sample_pages if sample_pages 0 else 0 return text if avg_chars 50 else scanned5.2 OCR引擎选型与地质术语识别优化扫描型PDF的OCR我对比过Tesseract、PaddleOCR和商业API。Tesseract免费但对手写体和低质量扫描件效果差PaddleOCR中文识别率更高但部署稍复杂商业API效果最好但成本高。综合考虑我最终选了PaddleOCR作为主力Tesseract作为备选。地质术语的识别优化是关键。默认的OCR模型对“矽卡岩”“斑岩型铜矿”这类专业词汇识别率不高。我的做法是构建一个地质术语词典在OCR后处理阶段做纠错。具体来说用编辑距离匹配把识别结果中与词典词汇编辑距离小于2的字符串替换为词典中的正确词汇。这个词典我从项目报告里提取了大约3000个高频术语纠错后OCR准确率从82%提升到了94%。提示OCR后的文本一定要做后处理包括去除多余空格、修复断行、统一全角半角符号。这些看似琐碎的操作对嵌入质量影响很大。5.3 版面分析与表格重建PDF里的表格是另一个难点。文本型PDF的表格提取我推荐用pdfplumber库它对表格线的识别比较准。但探矿报告里很多表格没有明显的表格线靠的是文字对齐。这种情况下pdfplumber的表格提取会失败需要退回到基于文本坐标的聚类方法。我的做法是先用pdfplumber尝试提取表格如果提取结果的单元格内容为空或错乱就改用坐标聚类。具体来说获取页面上所有文本块的坐标按y坐标聚类成行按x坐标聚类成列然后填充到二维数组中。这个方法对无框线表格效果不错但需要调整聚类阈值。我一般把y坐标差小于3像素的归为同一行x坐标差小于5像素的归为同一列。表格重建后同样转成Markdown或JSON存储。对于跨页表格我会在解析时记录表格的连续性合并时把同一表格的多个片段拼接起来。6. 网页抓取后的正文提取与噪声剥离6.1 从HTML到纯文本的正文定位网页数据在探矿RAG里占比不大但质量往往最差。地质资料馆的网页通常有大量导航链接、侧边栏、版权声明正文可能只占页面内容的20%。如果直接抓取整个页面的文本噪声会严重干扰检索。正文提取我试过几种方案。Readability算法就是浏览器阅读模式用的那套效果不错但对中文网页的支持一般。后来我改用基于密度的算法统计每个HTML节点的文本长度和链接密度文本长度大且链接密度低的节点更可能是正文。具体实现时我用BeautifulSoup遍历DOM树计算每个div或article的“文本长度/链接文本长度”比值取比值最大的节点作为正文容器。from bs4 import BeautifulSoup def extract_main_content(html): soup BeautifulSoup(html, html.parser) best_node None best_score 0 for node in soup.find_all([div, article, section]): text_len len(node.get_text(stripTrue)) link_len sum(len(a.get_text(stripTrue)) for a in node.find_all(a)) if text_len 0: continue score text_len / (link_len 1) if score best_score: best_score score best_node node return best_node.get_text(separator\n, stripTrue) if best_node else 6.2 动态渲染页面的处理策略有些地质资料网站的内容是JavaScript动态加载的直接请求HTML拿不到正文。这种情况需要用无头浏览器渲染。我一般用Playwright它比Selenium更轻量API也更现代。渲染时设置合理的等待条件比如等待某个正文容器出现而不是固定等待几秒。但无头浏览器不是万能的。有些网站有反爬机制或者渲染后内容仍然在iframe里。对于iframe需要切换到对应的frame再提取。对于反爬我的原则是不硬碰优先找是否有公开的API或数据下载入口。探矿数据很多来自公开地质资料通常有正规的下载渠道没必要去破解网页。6.3 网页元数据的利用与时间戳处理网页数据有一个其他格式没有的优势元数据丰富。发布时间、作者、来源网站、URL这些信息对检索很有价值。我会在清洗时把这些元数据抽取出来作为独立字段存储。特别是时间戳探矿资料有时效性用户可能只想检索近五年的报告时间过滤能大幅缩小范围。时间戳的处理要注意格式统一。网页上的时间格式五花八门“2023-05-12”“2023年5月12日”“May 12, 2023”“12/05/2023”。我写了一个解析函数用dateutil库做模糊解析统一转成ISO格式。对于解析失败的情况记录原始字符串并标记为未知时间。7. 分块策略让检索精度从60%到90%的关键一步7.1 固定长度分块为什么在探矿场景下失效最开始我用的是最简单的固定长度分块每500个字符切一块重叠50字符。结果检索测试惨不忍睹。问“某钻孔的岩芯采取率”返回的块里包含这个信息但块的开头是上一个钻孔的结尾结尾是下一个钻孔的开头语义被切碎了。嵌入模型拿到这种块生成的向量自然不能准确代表“岩芯采取率”这个语义。固定长度分块的问题在于它假设文本是均匀的但探矿文本的结构性很强。一个钻孔的数据是一个完整的语义单元不应该被随机切断。同样一个表格、一个章节都有其内在的边界。无视这些边界的分块就是在制造噪声。7.2 基于语义单元的自适应分块方法我的改进方案是语义单元优先的分块。具体规则因数据源而异TXT钻孔数据按“回次”或“班次”分块Word文档按最小章节分块PDF按段落或表格分块网页按正文段落分块。每个块的大小不固定但保证语义完整。对于超长语义单元比如一个章节有3000字我会做二次切分但切分点选在段落边界而不是字符边界。对于超短语义单元比如一个只有标题的章节我会把它和下一个单元合并避免产生无意义的碎片。分块后每个块会附加元数据来源文件、章节路径、钻孔编号、时间范围等。这些元数据在检索时可以用来过滤和排序。实测下来语义分块比固定长度分块的检索准确率提升了约30个百分点。7.3 重叠窗口与上下文补全的取舍语义分块的一个潜在问题是块与块之间可能丢失上下文。比如一个段落被切到两个块里前一个块有主语后一个块有谓语单独看都不完整。为了解决这个问题我会在块的前后各加一个“上下文窗口”把相邻块的首尾各50字附加进来用特殊标记分隔。但这个窗口不能太大否则会引入无关信息。我试过100字、200字最终50字在探矿数据上效果最好。另外对于表格类块我会把表头复制到每个数据块的前面确保每个块都能独立理解。这个操作看起来简单但对检索精度的提升非常明显。8. 清洗效果的验证与迭代8.1 用检索测试反推清洗质量清洗做得好不好不能靠感觉要用数据说话。我构建了一个小型测试集50个问题每个问题对应一个已知答案所在的文件。然后跑检索看Top 5结果里是否包含正确答案。这个测试集覆盖了四类数据源和多种问题类型数值查询、事实查询、对比查询。第一轮测试Top 5命中率只有58%。分析失败案例发现大部分问题出在PDF扫描件的OCR错误和Word表格的解析错乱上。针对性地优化后第二轮提升到76%第三轮到89%。这个过程让我深刻体会到清洗和检索是迭代关系不是一次性工作。8.2 常见清洗故障的排查清单在迭代过程中我整理了一份排查清单遇到检索效果下降时按顺序检查排查项检查方法常见问题编码随机抽样阅读乱码导致语义丢失分隔符统计字段数分布字段错位表格解析对比原始与解析结果合并单元格丢失OCR质量人工抽查识别文本专业术语错误分块边界检查块首尾语义语义截断元数据验证字段完整性过滤维度缺失这份清单帮我节省了大量排查时间。每次检索效果波动按清单过一遍基本都能定位到问题。8.3 持续优化的方向与经验总结清洗不是一劳永逸的。新数据进来可能带来新的格式变体业务需求变化可能要求保留之前丢弃的信息。我的做法是建立一个反馈闭环每次检索失败案例都记录下来定期分析把新的模式补充到清洗规则里。另外我强烈建议保留原始文件和清洗中间结果。有时候清洗规则改错了需要回滚有时候需要对比不同规则的效果。没有原始数据这些都做不了。存储成本相比重新采集数据的成本不值一提。最后分享一个小心得清洗规则不要写得太复杂。我见过有人写了上千行的正则表达式维护起来简直是噩梦。我的原则是每个规则只做一件事规则之间用流水线串联。这样出问题时容易定位修改时也不会牵一发而动全身。探矿数据的脏法虽然多但拆解成小问题后每个都不难解决。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询