news 2026/10/7 5:23:25

探矿多格式文档清洗:从TXT到网页的高精度RAG检索实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
探矿多格式文档清洗:从TXT到网页的高精度RAG检索实践

探矿这行当,数据来源的杂乱程度远超大多数人的想象。地质报告可能是八十年代手写的扫描件,钻孔编录是同事用 Word 敲的,化验数据躺在 Excel 里,而矿区批复文件又是 PDF 或者某个内部网页上扒下来的。把这些东西一股脑丢给 RAG 系统,检索出来的结果基本没法看——问"XX 矿区铜品位多少",它给你返回一段讲矿区交通位置的文字,因为两段里都有"XX 矿区"四个字。问题不在模型,在于喂进去的原料根本没洗干净。

这篇内容就是聊怎么把 TXT、Word、PDF 和网页这四类最常见的探矿资料,从"能打开"处理到"能被高精度检索"。适合正在搭地质知识库、矿业 RAG 应用的朋友,也适合任何被多格式文档清洗折磨过的从业者。核心思路一句话:清洗的目标不是把文字提取出来,而是把文字还原成它原本的结构和语义单元。下面按格式逐个拆,中间穿插我踩过的坑和实测有效的参数。

1. 先搞清楚探矿资料为什么这么难洗

1.1 探矿文本的三种"脏"

普通文档清洗,脏在格式;探矿文档清洗,脏在语义。我把它归成三类。

第一类是结构脏。一份地质勘查报告,正文里嵌着几十张表格,表格里又有合并单元格、跨页续表、斜线表头。PDF 提取出来之后,表格的行列关系全塌了,变成一堆用空格隔开的数字。你根本分不清哪个数字是品位、哪个是厚度、哪个是标高。

第二类是符号脏。地质行业有一套自己的符号体系:γ 表示伽马,ω 表示含水,还有各种上下标、希腊字母、特殊单位符号(如 ×10⁻⁶)。Word 里这些是公式对象,PDF 里这些是矢量图形,TXT 里这些直接变成乱码或者问号。检索时用户输入"伽马异常",文档里存的是"γ异常",字面匹配直接失效。

第三类是语境脏。这是最要命的。探矿文本里大量使用指代和省略:"该孔""上述层位""本区""前者"。单独抽出一段话,你完全不知道"该孔"是哪个孔,"本区"是哪个区。RAG 检索是按块(chunk)召回的,如果切块时把指代关系切断了,召回的内容就是废的。

1.2 清洗的目标不是"干净",是"可检索"

很多人对清洗有个误解,以为把乱码去掉、把格式统一就算完事。实际上对 RAG 来说,清洗的验收标准只有一个:用户的问题和文档的块之间,能不能建立准确的语义匹配。

这意味着几件事。一是要保留结构信息,表格要还原成结构化的键值对或者 Markdown 表格,不能拍平成纯文本。二是要统一术语和符号,把 γ 和"伽马"归一化到同一个表示。三是要在切块时保留足够的上下文,让每个块都能独立成义。

我见过太多项目,清洗阶段图省事,直接调个通用解析库把文档转成纯文本就完事,结果检索准确率死活上不去,回头查半天发现是清洗阶段把表格结构丢了。这个坑,下面每个格式都会再遇到一次。

2. TXT 与结构化文本:最容易被轻视的一环

2.1 TXT 的乱码问题本质是编码问题

TXT 看起来最简单,实际上编码问题能坑死人。国内探矿资料常见的编码有 GBK、GB2312、GB18030、UTF-8,还有少量 Big5。更麻烦的是,同一批资料里可能混着多种编码,甚至单个文件内部编码都不统一(比如从不同系统导出的拼接文件)。

处理的第一步是编码探测。Python 里用chardet或者charset-normalizer做批量探测,但别全信它的结果,中文短文本的探测准确率并不高。我的做法是:先用探测库给出候选编码,然后按候选顺序逐个尝试解码,解码后检查是否包含大量替换字符(U+FFFD)或者生僻字,选替换字符最少的那个。

import chardet def detect_and_read(path): with open(path, 'rb') as f: raw = f.read() # 先试 UTF-8,失败再探测 try: return raw.decode('utf-8') except UnicodeDecodeError: pass result = chardet.detect(raw) enc = result['encoding'] or 'gb18030' # gb18030 是 GBK 的超集,优先用它兜底 for candidate in [enc, 'gb18030', 'gbk', 'big5']: try: text = raw.decode(candidate) if text.count('\ufffd') < len(text) * 0.001: return text except (UnicodeDecodeError, LookupError): continue return raw.decode('gb18030', errors='replace')

注意:gb18030能覆盖绝大多数简体中文场景,遇到 GBK 解不开的字符,直接上 gb18030 基本都能救回来。别用errors='ignore',那会把乱码字符直接吞掉,你连哪里出了问题都不知道。

2.2 结构化 TXT 的解析要按"记录"而不是按"行"

探矿数据里有一类 TXT 是结构化的,比如钻孔编录导出、化验数据导出,通常是固定分隔符(逗号、制表符、竖线)或者固定宽度。这类文件清洗的关键是按记录解析,而不是按行切分。

固定宽度的文件尤其要注意:字段之间没有分隔符,全靠列位置对齐。如果中间有中文(一个中文占两个字符宽度),列位置就会错位。处理办法是先按字节或者显示宽度重新计算列边界,再切分。

def parse_fixed_width(line, widths): # widths 是每个字段的显示宽度列表 fields = [] pos = 0 for w in widths: # 按显示宽度切,中文算两个宽度 seg = '' width_count = 0 while pos < len(line) and width_count < w: ch = line[pos] seg += ch width_count += 2 if ord(ch) > 127 else 1 pos += 1 fields.append(seg.strip()) return fields

解析完之后,别急着拼成一大段文本喂给 RAG。结构化数据的正确做法是转成自然语言描述再入库。比如一行化验数据ZK001, 120.5, 0.85, Cu,转成"钻孔 ZK001 在 120.5 米处铜品位为 0.85%"。这样检索时用户问"ZK001 铜品位",语义匹配才能命中。

2.3 一个实测有效的经验:给每个块补"来源指纹"

TXT 文件往往没有明显的章节结构,切块之后很容易丢失来源信息。我的做法是在每个块前面强制拼接一段元信息:文件名、文件内的行号范围、如果知道的话还有数据来源(比如"XX 矿区 2023 年化验数据")。

这段元信息在检索时可能不直接匹配用户问题,但它能帮你在召回后做重排序,也能在生成答案时提供引用来源。实测下来,加了来源指纹的块,人工评估时的可用率能提升两成左右。

3. Word 文档:公式、表格与隐藏的坑

3.1 Word 解析的两条路线:python-docx 与直接解 XML

Word(.docx)本质是个 zip 包,里面是一堆 XML。解析有两条路:用python-docx这类库,或者自己解压读document.xml。

python-docx的好处是 API 友好,坏处是它对公式和复杂表格支持很差。公式(OMML 格式)它基本读不出来,合并单元格的表格读出来行列也是乱的。所以我的建议是:正文用python-docx读,公式和表格自己解 XML。

公式在document.xml里是<m:oMath>节点,里面是 OMML 标记。要把它转成可读文本,可以提取里面的<m:t>文本节点拼起来,但这样会丢失上下标和分式结构。更好的做法是转成 LaTeX,用latex2mathml的反向工具或者自己写映射。如果项目里公式不多,退而求其次转成线性文本(比如H2O写成H2O,x²写成x^2)也能接受。

3.2 表格还原:合并单元格是最大的敌人

Word 表格的合并单元格在 XML 里用gridSpan(横向合并)和vMerge(纵向合并)表示。解析时要重建一个二维网格,把合并的单元格填充到它覆盖的每个位置上。

from docx import Document def extract_table(table): # 先算出表格的列数 grid = [] for row in table.rows: cells = [] for cell in row.cells: cells.append(cell.text.strip()) grid.append(cells) return grid

上面这段是python-docx的常规写法,但它对合并单元格的处理是"重复填充"——合并的单元格在每个位置都返回同样的文本。这会导致表格里出现大量重复值。清洗时要检测这种重复,把它还原成真正的合并结构,或者至少在转 Markdown 表格时做去重。

我的处理策略是:能还原成 Markdown 表格的就还原,还原不了的转成"字段-值"列表。比如一个跨页的钻孔数据表,与其硬拼成一张大表,不如拆成多条记录,每条记录带上表头信息。这样检索时反而更精准。

3.3 Word 里的"隐形内容":批注、修订、文本框

探矿报告经常是多人协作的,Word 里会残留批注、修订记录、文本框。这些内容在python-docx的默认读取里是读不到的,但它们可能包含重要信息(比如"此处品位数据待核实")。

要读批注,得解comments.xml;要读修订,得处理<w:ins>和<w:del>节点;要读文本框,得找<w:txbxContent>。这些内容要不要入库,取决于你的业务。我的建议是:批注和修订单独存一个"辅助信息"字段,不混进正文,但在生成答案时可以作为置信度参考。

提示:清洗 Word 时一定要先确认文档有没有开启"修订模式"。如果开了,正文里可能同时存在删除和插入的内容,直接读会把已删除的文字也读进来,造成数据污染。

4. PDF:扫描件、双栏排版与公式图片

4.1 先分清 PDF 的三种类型

PDF 清洗的第一步不是选工具,是分类。探矿资料里的 PDF 大致分三种:

  • 文本型 PDF:文字是可选的,底层有文本层。这类最好处理。
  • 扫描型 PDF:整页是图片,没有文本层。必须走 OCR。
  • 混合型 PDF:部分页面是文本,部分是扫描图。这类最坑,需要逐页判断。

判断方法很简单:用pdfplumber或者PyMuPDF读一页,看提取出的文本长度。如果一页文本少于 50 个字符但页面明显有内容,基本就是扫描页。

import fitz # PyMuPDF def classify_page(page): text = page.get_text().strip() images = page.get_images() if len(text) < 50 and images: return 'scanned' return 'text'

4.2 文本型 PDF 的排版还原

文本型 PDF 的难点在排版。地质报告常见双栏排版,直接按阅读顺序提取会把左右栏的文字交错在一起,读起来前言不搭后语。

PyMuPDF提供了get_text("blocks")能拿到文本块的位置信息,可以根据 x 坐标判断栏位,先左栏后右栏地重组。表格则要用pdfplumber的extract_tables(),它对表格线的识别比 PyMuPDF 好。

import pdfplumber with pdfplumber.open('report.pdf') as pdf: for page in pdf.pages: # 先判断是不是双栏 words = page.extract_words() mid = page.width / 2 left = [w for w in words if w['x0'] < mid] right = [w for w in words if w['x0'] >= mid] # 如果左右都有足够多的词,按双栏处理 if len(left) > 20 and len(right) > 20: text = ' '.join(w['text'] for w in left) + '\n' + \ ' '.join(w['text'] for w in right) else: text = page.extract_text()

4.3 扫描件 OCR:预处理比 OCR 引擎更重要

扫描件 OCR 的准确率,七分靠预处理,三分靠引擎。探矿扫描件常见的问题是:纸张发黄、有装订线阴影、有手写批注、有印章遮挡。

预处理我一般做这几步:灰度化、二值化(用自适应阈值,别用全局阈值)、去噪(中值滤波)、倾斜校正(霍夫变换检测直线角度)。做完这几步再送 OCR,准确率能明显提升。

OCR 引擎的选择上,中文场景我实测下来,PaddleOCR 对印刷体中文的识别效果比较稳,尤其是竖排和表格场景。Tesseract 需要自己训练或者调参,默认模型对中文地质术语的识别率一般。

注意:OCR 出来的文本一定要做后处理纠错。地质术语里"品位"容易被识别成"品住","勘探线"容易被识别成"勘採线"。建一个领域词典做替换,能救回不少错误。

4.4 公式图片:转 LaTeX 还是转描述

PDF 里的公式如果是图片,OCR 很难直接识别成 LaTeX。这时候有两个选择:用专门的公式识别工具(如 pix2tex)转 LaTeX,或者人工/半自动转成文字描述。

我的经验是:核心公式转 LaTeX,次要公式转描述。比如品位计算公式、储量估算公式,这些必须精确,转 LaTeX。而一些中间推导过程,转成"根据质量平衡方程计算得出"这样的描述就够了。全部转 LaTeX 成本太高,而且 RAG 检索时用户也不会用 LaTeX 去搜。

5. 网页资料:抓取、正文提取与动态内容

5.1 网页抓取先解决"抓什么"

探矿业务里的网页资料,来源很杂:矿业权公示页面、行业资讯、地质队官网的技术分享、在线地图服务。抓取前先明确抓什么——是抓正文,还是抓附件(很多公示页面正文没内容,内容在 PDF 附件里)。

抓取工具上,静态页面用requests+BeautifulSoup就够,动态渲染的页面(比如用 JS 加载数据的)得上Playwright或者Selenium。我一般优先用 Playwright,因为它对现代前端框架的兼容性更好,而且能直接导出渲染后的 HTML。

5.2 正文提取:别用正则,用可读性算法

网页正文提取最忌讳用正则去匹配<div class="content">,因为每个网站的 class 名都不一样,维护成本极高。正确做法是用可读性算法,比如readability-lxml或者trafilatura。

trafilatura我实测下来对中文网页的正文提取效果不错,它能自动去掉导航、广告、页脚,保留正文和标题。用法也简单:

import trafilatura downloaded = trafilatura.fetch_url(url) text = trafilatura.extract(downloaded, include_tables=True, include_links=False)

include_tables=True很重要,探矿网页里的数据经常在表格里,丢了表格等于丢了核心信息。

5.3 动态内容与反爬的边界

有些矿业数据平台是动态加载的,直接抓 HTML 拿不到数据。这时候要用 Playwright 等页面加载完再取内容。但要注意,抓取要遵守目标网站的规则,控制频率,别给人家服务器造成压力。

另外,网页抓取下来的内容往往带有大量噪声:版权声明、相关推荐、评论。这些在入库前要过滤掉。我的做法是维护一个噪声模式列表(比如包含"版权所有""转载请注明"的段落直接丢弃),配合正文提取算法一起用。

6. 清洗之后的统一处理:归一化、切块与元数据

6.1 术语与符号归一化

四类格式清洗完,得到的还是四套不同风格的文本。入库前要做统一归一化。核心是两件事:术语统一和符号统一。

术语统一靠领域词典。比如"品位"" grade""含量"要映射到同一个标准术语;"钻孔""孔""ZK"要能互相识别。符号统一则是把 γ、ω 这类特殊符号,在保留原符号的同时,附加一个文本别名,比如γ(伽马)。这样无论用户搜符号还是搜中文,都能命中。

6.2 切块策略:按语义切,不按字数切

切块是 RAG 清洗里最影响效果的一步。按固定字数切(比如每 500 字一块)是最省事但最差的做法,因为它会把一个完整的语义单元拦腰截断。

我的策略是按结构切,按语义合并。有标题的按标题切,有表格的表格单独成块,段落之间语义连贯的合并到一块。具体实现上,可以用langchain的RecursiveCharacterTextSplitter,但分隔符要自定义成中文的句号、分号、换行,而不是默认的英文标点。

from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=600, chunk_overlap=80, separators=['\n\n', '\n', '。', ';', ',', ' '], length_function=len, )

chunk_overlap设 80 左右,是为了让相邻块之间有上下文重叠,缓解指代断裂的问题。但重叠别设太大,否则检索时会召回一堆重复内容。

6.3 元数据:每个块都要能回答"我从哪来"

每个块必须带元数据,这是高精度检索的基础。元数据至少包括:来源文件、文件类型、页码或行号、所属章节、数据时间。如果是从表格转来的,还要带上表头信息。

这些元数据在检索时有两个用途:一是可以做过滤(比如只搜 2020 年之后的资料),二是可以做重排序(比如优先召回正文而不是附录)。实测下来,带完整元数据的知识库,检索准确率比裸文本库高出一大截。

7. 几个我踩过的坑和对应的解法

7.1 坑一:PDF 提取出来的"空格地狱"

PDF 提取文本时,经常出现每个字之间都有空格的情况,比如"铜 品 位 为 0.85"。这是因为 PDF 底层把每个字符都当成独立定位的对象。直接入库会导致检索时匹配不上。

解法是做空格压缩:连续的单字符空格,如果两侧都是中文,就合并掉。但要小心,英文单词之间的空格不能动。判断逻辑是看空格两侧字符的 Unicode 范围。

7.2 坑二:Word 表格跨页导致的表头丢失

Word 表格跨页时,第二页往往没有表头。解析出来就是一堆没有列名的数据,完全没法用。

解法是检测表格的续表:如果一张表的第一行和上一张表的表头结构一致,就认为是续表,把表头补上。或者更简单粗暴——在清洗阶段就把跨页表格合并,用python-docx读的时候按表格对象读,而不是按页读。

7.3 坑三:OCR 把数字识别错导致的"数据污染"

这个最危险。OCR 把"0.85"识别成"0.35",把"120"识别成"720",这种错误在检索时看不出来,但会直接污染答案。

解法是加校验规则。比如品位值一般在 0 到 100 之间,超过范围的标记为可疑;钻孔编号一般有固定格式,不符合格式的标记出来人工复核。另外,关键数据(品位、储量、坐标)建议做双引擎 OCR 交叉验证,两个引擎结果不一致的挑出来人工看。

7.4 坑四:网页抓取抓到了"登录墙"

有些数据平台需要登录才能看内容,抓下来的是登录页。这种别硬抓,要么走正规的数据申请渠道,要么放弃这个来源。硬抓既不合规,抓到的也是垃圾数据。

8. 一套可复用的清洗流水线

把上面的东西串起来,我给出一条实测可用的流水线,按顺序执行:

  1. 格式识别:按文件扩展名和内容特征分类,PDF 再细分文本型/扫描型/混合型。
  2. 分格式解析:TXT 走编码探测,Word 走 docx+XML,PDF 走 pdfplumber/PyMuPDF+OCR,网页走 trafilatura。
  3. 结构还原:表格转 Markdown 或键值对,公式转 LaTeX 或描述,双栏重排。
  4. 噪声过滤:去页眉页脚、去版权声明、去重复内容。
  5. 归一化:术语映射、符号别名、空格压缩。
  6. 切块:按语义切,带重叠。
  7. 元数据注入:来源、页码、章节、时间。
  8. 质量抽检:随机抽样人工看,重点查数字和表格。

这条流水线跑下来,一份几百页的地质报告,从原始文件到可检索的知识块,大概需要十几分钟(不含 OCR 的话更快)。关键是每一步都要留日志,出了问题能回溯到是哪一步引入的。

9. 关于"清洗到什么程度算够"的个人判断

最后聊个实在的问题:清洗没有终点,做到什么程度算够?我的判断标准是看检索的 bad case。先跑一批真实问题,看召回结果里有多少是因为清洗问题导致的(比如表格结构丢了、公式没转、指代断了)。如果 bad case 里清洗问题占比低于一成,就可以先停,把精力放到检索和生成环节。如果超过三成,那说明清洗还没到位,继续磨。

我自己的项目里,最初清洗做得很粗,检索准确率大概六成;把表格还原和术语归一化补上之后,到了八成左右;再补上切块优化和元数据,稳定在九成上下。后面再想提升就非常难了,投入产出比很低。所以别追求完美清洗,追求"够用且可迭代"。

另外提一句,清洗规则一定要配置化,别硬编码在代码里。探矿资料的格式会变,今天能用的正则明天可能就失效了。把编码列表、术语词典、噪声模式、切块参数都放到配置文件里,改起来才不痛苦。这个经验是我被反复改需求折磨之后才悟出来的,希望你少走点弯路。

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

ES实战:本地安装、Routing与Java异步写入全解析

后端和运维同学聊到 es&#xff08;Elasticsearch&#xff09;时&#xff0c;经常发现同一批人在各个维度上“鸡同鸭讲”&#xff1a;有人关心它搜得快不快&#xff0c;有人关心它数据丢不丢&#xff0c;有人关心它装没装、怎么在 Windows 上验证&#xff0c;还有人一上来就问 …

作者头像 李华
网站建设 2026/10/7 5:22:59

DICOM批量转JPG/PNG全攻略:工具选型与Python脚本实战

干医学影像这行的&#xff0c;几乎人手都逃不过一个“转格式”的活儿。科室里 DICOM 文件堆成山&#xff0c;一张 CT 一个序列就是几百帧&#xff0c;想发个微信给主任看一眼、想给模型做批训练数据、想把典型病例整理成 PPT 课件&#xff0c;结果对方一看到 .dcm 就傻眼&#…

作者头像 李华
网站建设 2026/10/7 5:22:58

数字后端入门:Innovus中Floorplan与Powerplan实战指南

1. 数字后端入门&#xff1a;Floorplan与Powerplan到底在做什么刚接触数字后端的人&#xff0c;十有八九会在Floorplan这一步卡住。前端设计给过来一个综合后的网表&#xff0c;你打开Innovus&#xff0c;面对一个空白的die区域&#xff0c;第一反应往往是——我该从哪儿下手&a…

作者头像 李华
网站建设 2026/10/7 5:22:55

2026企业敏捷宣言拆解:从团队敏捷到组织级敏捷落地

“2026《企业敏捷宣言》”这个标题让我想了很多。这些年大大小小参与过不少企业的敏捷转型&#xff0c;从团队级的Scrum导入&#xff0c;到部门级的看板推广&#xff0c;再到公司级的规模化敏捷框架落地&#xff0c;几乎每隔两三年就会冒出一波新概念。团队敏捷这个层面&#x…

作者头像 李华
网站建设 2026/10/7 5:22:54

从312条到9条:AI日报自动化筛选与摘要实战

1. 一份AI日报的诞生&#xff1a;从信息洪流到结构化输出每天早上七点&#xff0c;我的手机屏幕上会准时弹出一份自己搭建的AI资讯日报。它不是某个平台推送的&#xff0c;也不是花钱订阅的&#xff0c;而是我用一套跑了快两年的自动化流程&#xff0c;从几十个信息源里筛出来、…

作者头像 李华
网站建设 2026/10/7 5:21:34

基于Simulink的二分之一车辆悬架半车模型建模与仿真全流程解析

前段时间我一直在折腾车辆悬架系统的建模与仿真&#xff0c;把一个老掉牙的课题——二分之一车辆悬架半车模型&#xff08;简称半车模型&#xff09;——用Simulink完整搭建并跑通了。这个项目听起来不像四分之一模型那样入门&#xff0c;也不像整车模型那样复杂庞大&#xff0…

作者头像 李华