news 2026/10/9 4:23:19

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
探矿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提取出一堆空字符。

我的分流方法是:用PyMuPDF(fitz)打开PDF,遍历前几页,统计每页的文本字符数。如果平均每页字符数大于50,判定为文本型;否则判定为扫描型。这个阈值可以根据实际情况调整,我试过30和100,50在探矿数据上表现最稳。对于混合型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 'scanned'

5.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(strip=True)) link_len = sum(len(a.get_text(strip=True)) 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', strip=True) 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 持续优化的方向与经验总结

清洗不是一劳永逸的。新数据进来,可能带来新的格式变体;业务需求变化,可能要求保留之前丢弃的信息。我的做法是建立一个反馈闭环:每次检索失败案例都记录下来,定期分析,把新的模式补充到清洗规则里。

另外,我强烈建议保留原始文件和清洗中间结果。有时候清洗规则改错了,需要回滚;有时候需要对比不同规则的效果。没有原始数据,这些都做不了。存储成本相比重新采集数据的成本,不值一提。

最后分享一个小心得:清洗规则不要写得太复杂。我见过有人写了上千行的正则表达式,维护起来简直是噩梦。我的原则是每个规则只做一件事,规则之间用流水线串联。这样出问题时容易定位,修改时也不会牵一发而动全身。探矿数据的脏法虽然多,但拆解成小问题后,每个都不难解决。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 4:23:18

AGENTS.md 真的有用吗?用 GPT-4o 在 SWE-bench Lite 上跑一遍验证

/* 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 4:23:00

多AI协作与Agent搭建实战:从模式到部署避坑指南

1. 本期速览:这一周AI圈到底在忙什么先交代一下背景。亨嘉AI周刊是面向一线AI从业者、AI产品负责人和深度玩家的行业周刊,每周梳理过去七天最值得关注的技术趋势、工具动态和实战方法论。第40周的观察周期是9月28日到10月4日,正好跨了一个国庆…

作者头像 李华
网站建设 2026/10/9 4:22:20

NeurIPS时间序列论文解读:基础模型、上下文学习与VLM成主流

1. 论文速览:这届NeurIPS的时间序列到底在卷什么NeurIPS 2026的时间序列论文放出来之后,我花了两天整块时间把标题全部过了一遍,又挑了十几篇和工作相关的精读了一遍。整体感觉是:这届时间序列不再是"算法调参大会"&…

作者头像 李华
网站建设 2026/10/9 4:21:30

SAP Fiori落地实践:从设计原则到Launchpad配置与运维排查

1. 先搞清楚 Fiori 到底在解决什么:从“功能清单”到“任务闭环”1.1 传统SAP界面的复杂度陷阱做SAP项目的人,应该都有过这种体验:一张屏幕挤满了四五十个字段,十几个标签页来回切,业务流程要走三四步操作才完得成&…

作者头像 李华
网站建设 2026/10/9 4:21:30

预训练模型做多标签专利分类:高频标签筛选如何提升精度?

简介:面向自然语言处理与专利信息挖掘领域研究者,这份文档系统阐述了基于预训练模型的多标签专利分类方法。内容围绕IPC小类级别的细粒度分类难题,详细介绍了如何构建可扩展的大规模专利数据集,并对BERT、RoBERTa、RBT3三种预训练…

作者头像 李华
网站建设 2026/10/9 4:20:30

大模型轻量化推理:KV Cache压缩与动态稀疏注意力实战

1. 这不是一篇“翻译作业”,而是一次技术思想的本地化转译“TowardsArtificialIntelligence 博客中文翻译(五十五)”——看到这个标题,很多人第一反应是:又一篇海外AI博客的搬运稿?配个机翻人工润色&#x…

作者头像 李华