ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

探矿业务RAG数据清洗实战:TXT、Word、PDF与网页四类数据源处理方案

探矿业务RAG数据清洗实战:TXT、Word、PDF与网页四类数据源处理方案 1. 探矿业务数据清洗的底层逻辑与方案选型1.1 为什么探矿场景的RAG清洗比通用方案难三倍探矿业务的数据源有个很要命的特点格式极度碎片化而且每种格式背后都藏着行业特有的“脏数据”。地质报告可能是扫描版PDF钻孔数据可能是带合并单元格的Word表格历史资料可能是编码混乱的TXT而最新的矿权公示信息又散落在各种网页里。我做过一个统计一个中型探矿项目的资料库里TXT占35%、Word占25%、PDF占30%、网页存档占10%但真正能直接喂给RAG系统的干净文本不到15%。通用RAG清洗方案在这里会直接翻车。比如常见的PDF解析库对地质图件里的文字标注基本无能为力Word表格里的合并单元格会让文本提取变成一团乱麻TXT文件里GBK和UTF-8混编的情况更是家常便饭。更麻烦的是探矿领域有大量专业术语和坐标数据清洗时稍有不慎就会把“Au品位3.5g/t”这种关键信息切碎导致后续检索时完全找不到。所以这套清洗方案的核心思路不是追求“通用”而是针对探矿业务的四个数据源分别设计清洗管道最后再统一到同一个向量化入口。这样做的好处是每个环节都能针对性地处理该格式特有的噪声而不是用一个万能解析器硬扛所有问题。1.2 四类数据源的清洗优先级与工具选型先给结论TXT优先做编码归一化Word重点处理表格和公式PDF必须走OCR版面分析双通道网页则要解决动态渲染和正文提取两个问题。工具选型上我试过不少组合最终稳定下来的方案是这样的数据源核心工具辅助工具关键参数TXTchardet iconv自定义正则清洗器编码探测置信度阈值0.85Wordpython-docx lxmlpandoc公式转换表格单元格合并标记保留PDFPaddleOCR pdfplumberLayoutParserOCR置信度阈值0.75网页Playwright readabilityBeautifulSoup等待网络空闲后提取选PaddleOCR而不是Tesseract是因为地质报告里经常出现竖排文字和特殊符号PaddleOCR对中文竖排的识别率明显更高。pdfplumber用来提取PDF里的表格线条信息配合OCR结果做交叉验证能大幅降低表格数据错位的概率。网页部分用Playwright而不是requests是因为很多矿权公示页面是动态加载的直接抓HTML只能拿到空壳。注意不要试图用一个工具解决所有格式。我见过有人用LangChain的通用加载器一把梭结果Word表格里的数据全变成了按行拼接的字符串坐标和品位值的对应关系完全丢失这种数据喂给RAG还不如不喂。1.3 清洗管道的整体架构设计整个清洗流程我设计成四段式管道每段之间用统一的中间格式JSON Lines做衔接。这样做的好处是任何一段出问题都可以单独重跑不用从头再来。第一段是格式识别与路由。拿到一个文件先判断真实格式不能只看扩展名。我遇到过把.doc改成.pdf的也见过TXT文件里其实是HTML代码。这里用python-magic做二进制签名检测准确率比扩展名靠谱得多。第二段是分格式清洗。TXT走编码归一化正则清洗Word走结构化提取公式转换PDF走OCR版面还原网页走动态渲染正文抽取。每个子管道都有独立的配置文件和日志方便调试。第三段是领域实体识别与保护。这是探矿场景特有的环节。用正则词典的方式把矿种符号Au、Cu、Pb等、品位单位g/t、%、坐标格式度分秒、十进制标记出来在后续分块时确保这些实体不被切断。第四段是统一分块与向量化。按语义段落分块块大小控制在512-768个token之间重叠128个token。分块时优先在段落边界切分如果段落超长则在实体标记之间切分。这个架构跑下来我们那个中型项目的资料清洗通过率从15%提升到了82%检索准确率Top-5命中率从41%提升到了79%。下面我按数据源逐个拆解具体怎么做。2. TXT与Word清洗的实操细节2.1 TXT编码归一化的三步走策略TXT文件看起来最简单实际上坑最多。探矿行业的历史资料跨度大从九十年代的DOS系统到现在的Linux服务器编码格式五花八门。我遇到过GB2312、GBK、GB18030、UTF-8、UTF-8 with BOM、甚至还有Big5的混编情况。更麻烦的是有些文件是多次转码后的产物里面既有乱码又有正常文字。我的处理策略分三步第一步编码探测。用chardet做初步检测但不要完全信任它的结果。chardet对短文件的判断经常出错所以我会同时用cchardet更快和charset-normalizer更准做交叉验证。如果两个库的结果不一致就取置信度高的那个同时把文件标记为“需人工复核”。第二步试解码与回退。按探测到的编码尝试解码如果失败就按优先级回退UTF-8 → GB18030 → GBK → Big5 → Latin-1。这里有个技巧GB18030是GBK的超集能解码GBK的文件基本都能用GB18030解所以回退链里GB18030要排在GBK前面。第三步乱码修复。对于已经产生乱码的文件比如UTF-8被误读为GBK用ftfy库做修复。ftfy对“锟斤拷”这类典型乱码的修复效果很好但对探矿专业术语的乱码修复有限所以修复后还要过一遍领域词典做校验。import chardet import cchardet from charset_normalizer import from_bytes def detect_encoding(raw_bytes): results [] r1 chardet.detect(raw_bytes) results.append((chardet, r1[encoding], r1[confidence])) r2 cchardet.detect(raw_bytes) results.append((cchardet, r2[encoding], r2[confidence])) best max(results, keylambda x: x[2]) if best[2] 0.85: return None, low_confidence return best[1], ok实操心得对于置信度低于0.85的文件不要强行解码。我试过强行用GB18030解码一个实际是Big5的文件结果整篇报告的地名全变成了乱码后续实体识别完全失效。这种文件单独放一个队列用人工确认编码后再处理。2.2 Word文档的结构化提取与公式处理Word文档在探矿业务里主要承载两类内容文字报告和表格数据。文字报告相对好处理python-docx能直接提取段落文本。麻烦的是表格尤其是带合并单元格的钻孔数据表。python-docx提取表格时合并单元格会被重复填充或者留空导致数据错位。我的做法是先用lxml直接解析document.xml拿到表格的原始XML结构然后根据w:gridSpan和w:vMerge标签还原合并关系。还原后的表格转成二维数组每个单元格记录它的行跨度、列跨度和合并标记。from docx import Document from lxml import etree def extract_table_with_merge(docx_path): doc Document(docx_path) tables_data [] for table in doc.tables: xml table._tbl ns {w: http://schemas.openxmlformats.org/wordprocessingml/2006/main} rows xml.findall(.//w:tr, ns) grid [] for row in rows: cells row.findall(.//w:tc, ns) row_data [] for cell in cells: span cell.find(.//w:gridSpan, ns) colspan int(span.get({http://schemas.openxmlformats.org/wordprocessingml/2006/main}val)) if span is not None else 1 vmerge cell.find(.//w:vMerge, ns) rowspan 1 if vmerge is not None: val vmerge.get({http://schemas.openxmlformats.org/wordprocessingml/2006/main}val) if val restart: rowspan -1 # 标记为合并起始 text .join(cell.itertext()).strip() row_data.append({text: text, colspan: colspan, rowspan: rowspan}) grid.append(row_data) tables_data.append(grid) return tables_data公式处理是另一个难点。探矿报告里经常出现品位计算公式、坐标转换公式这些公式在Word里可能是OMML格式Office Math Markup Language也可能是嵌入的图片。OMML格式的公式可以用pandoc转成LaTeX但pandoc对复杂公式的转换经常丢括号。我的做法是先用pandoc转一遍然后用sympy的LaTeX解析器做校验如果解析失败就回退到图片OCR。注意Word宏安全问题在探矿行业很突出。很多老报告是带宏的.doc文件直接用python-docx打不开。这种情况先用libreoffice做无头转换转成.docx再处理。转换命令是libreoffice --headless --convert-to docx --outdir /tmp input.doc实测比用antiword靠谱。2.3 表格数据与正文的关联重建探矿报告里表格和正文是强关联的。比如正文说“ZK001钻孔见矿厚度3.2米”对应的表格里就有ZK001的详细数据。如果清洗时把表格和正文分开处理RAG检索时就会出现“找到了正文但找不到数据”或者“找到了数据但不知道对应哪个钻孔”的问题。我的做法是在分块前做一次关联重建。具体来说用正则从正文里提取钻孔编号如ZK\d、探槽编号如TC\d等实体然后在表格数据里搜索这些实体。如果匹配成功就把表格的摘要信息比如“ZK001见矿厚度3.2m品位2.1g/t”作为元数据附加到正文块上。这样处理之后检索“ZK001的见矿情况”时既能命中正文描述也能带出表格里的详细数据。实测下来这种关联重建能让检索准确率再提升12个百分点左右。3. PDF与网页清洗的硬骨头怎么啃3.1 PDF解析的双通道策略OCR与版面分析并行PDF是探矿资料里最难啃的格式。原因有三一是大量扫描版报告没有文字层必须走OCR二是地质图件里的文字标注方向各异横排竖排斜排都有三是表格线条复杂经常有跨页表格。我的方案是双通道并行通道A用pdfplumber提取文字层和表格线条通道B用PaddleOCR做全页OCR。两个通道的结果做交叉验证文字层可信的区域用通道A的结果文字层缺失或质量差的区域用通道B的结果。import pdfplumber from paddleocr import PaddleOCR def dual_channel_pdf(pdf_path): ocr PaddleOCR(use_angle_clsTrue, langch) results [] with pdfplumber.open(pdf_path) as pdf: for page_num, page in enumerate(pdf.pages): # 通道A文字层提取 text_layer page.extract_text() or tables page.extract_tables() # 通道BOCR img page.to_image(resolution300).original ocr_result ocr.ocr(np.array(img), clsTrue) ocr_text \n.join([line[1][0] for line in ocr_result[0]]) if ocr_result[0] else # 交叉验证文字层字符数少于OCR的60%时以OCR为准 if len(text_layer) len(ocr_text) * 0.6: final_text ocr_text source ocr else: final_text text_layer source text_layer results.append({ page: page_num 1, text: final_text, tables: tables, source: source }) return results版面分析用LayoutParser做区域划分把页面分成正文区、表格区、图件区、页眉页脚区。正文区和表格区的内容进入后续清洗管道图件区单独存档探矿图件后续可以走图像向量化但那是另一个话题了页眉页脚直接丢弃。实操心得PaddleOCR的use_angle_clsTrue参数一定要开地质图件里竖排文字很多不开角度分类的话竖排文字会被识别成乱码。另外resolution300是实测下来OCR质量和速度的平衡点再高速度下降明显再低识别率掉得厉害。3.2 网页动态渲染与正文提取的配合探矿业务的网页数据主要来自矿权公示、地质资料馆藏目录、行业新闻。这些页面有个共同特点正文内容往往在动态加载的iframe或者异步请求里直接抓HTML只能拿到导航和页脚。我的方案是用Playwright做动态渲染等页面网络空闲后再提取。关键参数是wait_untilnetworkidle这个比wait_untilload靠谱得多因为很多页面的正文是load之后才通过AJAX加载的。from playwright.sync_api import sync_playwright from readability import Document def extract_webpage(url): with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page() page.goto(url, wait_untilnetworkidle, timeout30000) # 等待可能的懒加载内容 page.wait_for_timeout(2000) html page.content() browser.close() # readability提取正文 doc Document(html) title doc.title() content doc.summary() # 用BeautifulSoup清理残留标签 from bs4 import BeautifulSoup soup BeautifulSoup(content, lxml) text soup.get_text(separator\n, stripTrue) return {title: title, text: text}readability对新闻类页面的正文提取效果很好但对矿权公示这种表格密集型页面效果一般。这种情况我会先用pandas的read_html提取表格如果表格数量大于3且表格内容占比超过页面文本的50%就判定为表格密集型页面走表格提取通道而不是readability通道。3.3 跨页表格与复杂版面的还原技巧PDF里的跨页表格是个老大难问题。一个钻孔数据表可能横跨两三页表头在第一页数据在后续页。如果按页独立处理后续页的数据就会丢失表头信息。我的做法是在双通道解析之后加一个表格拼接步骤。具体来说检测相邻页面的表格结构相似度列数相同、列宽比例相近如果相似度超过阈值就把它们拼接成一个逻辑表格。拼接时把第一页的表头复制到后续页确保每行数据都有完整的列名。def merge_cross_page_tables(pages_tables): merged [] buffer None for page_tables in pages_tables: for table in page_tables: if buffer is None: buffer table else: # 检查列数是否一致 if len(table[0]) len(buffer[0]): # 检查第一行是否像表头包含孔号品位等关键词 header_keywords [孔号, 品位, 厚度, 坐标, 深度] is_header any(kw in str(table[0]) for kw in header_keywords) if is_header: merged.append(buffer) buffer table else: buffer.extend(table) else: merged.append(buffer) buffer table if buffer: merged.append(buffer) return merged这个逻辑看起来简单但实测下来能解决80%以上的跨页表格问题。剩下的20%主要是表格中间插入了图件或者分栏排版这种就只能靠人工介入了。4. 常见问题排查与避坑经验实录4.1 清洗质量问题的快速定位方法清洗管道跑起来之后最怕的是“看起来跑完了但数据不能用”。我总结了一套快速定位问题的方法按以下顺序排查第一查编码。随机抽10个TXT文件用file -i命令看编码如果出现unknown-8bit或者iso-8859-1说明编码探测环节有问题。这种情况通常是chardet对短文件的误判需要调低置信度阈值或者增加人工复核队列。第二查表格。从Word和PDF里各抽5个表格检查合并单元格是否正确还原。重点看钻孔数据表的“孔号”列如果出现空值或者重复值说明合并单元格处理有问题。第三查实体。用正则统计清洗后文本里矿种符号、品位单位的出现次数和原始文件做对比。如果清洗后实体数量下降超过20%说明分块时切碎了实体需要调整分块策略。第四查检索。拿几个已知答案的问题去检索看Top-5结果里有没有正确答案。如果答案在原文里但检索不到说明向量化或者分块有问题。这套排查流程走下来基本能在30分钟内定位到问题环节。4.2 常见问题速查表问题现象可能原因排查方法解决方案TXT文件解码后全是乱码编码探测错误用file -i查看真实编码手动指定编码重新解码Word表格数据错位合并单元格未还原检查gridSpan和vMerge标签用lxml直接解析XMLPDF文字层缺失扫描版无文字层pdfplumber提取为空强制走OCR通道OCR识别率低分辨率不足或角度未校正检查OCR置信度提高分辨率到300dpi开启角度分类网页正文提取为空动态加载未完成检查页面加载状态用networkidle等待检索找不到已知答案分块切碎了关键实体检查分块边界在实体标记之间切分表格跨页数据丢失未做跨页拼接检查相邻页表格结构实现表格拼接逻辑公式转换后括号丢失pandoc转换问题用sympy校验LaTeX回退到图片OCR4.3 独家避坑技巧与经验总结技巧一TXT清洗时保留原始行号。探矿报告里经常有“见第3页表2”这样的交叉引用如果清洗时丢了行号这些引用就失效了。我的做法是在每行文本前加一个行号标记分块时行号作为元数据保留。技巧二Word公式转LaTeX时加一层校验。pandoc转出来的LaTeX经常有括号不匹配的问题用sympy的parse_latex做校验解析失败的公式回退到图片OCR。虽然麻烦一点但能保证公式数据的准确性。技巧三PDF OCR结果和文字层做字符级对齐。不要简单二选一而是做字符级对齐文字层可信的字符用文字层不可信的用OCR。这样能最大程度保留原始信息。技巧四网页抓取时保存原始HTML。清洗后的文本可能丢失一些上下文信息保存原始HTML方便后续回溯。存储成本很低但排查问题时很有用。技巧五分块时保留章节标题作为元数据。探矿报告的章节结构很重要“第三章 矿床地质特征”下面的内容检索时应该能按章节过滤。分块时把章节标题附加到每个块的元数据里检索时可以按章节筛选。技巧六建立领域词典做实体保护。把矿种符号、品位单位、常见地名、钻孔编号格式整理成词典分块时确保这些实体不被切断。这个词典需要持续维护每遇到一个新的实体格式就加进去。技巧七清洗管道要能断点续跑。探矿资料动辄几百上千个文件清洗过程中难免出错。管道设计时要支持断点续跑每个文件处理完就写一个完成标记重跑时跳过已完成的文件。技巧八定期做清洗质量抽样检查。不要等全部清洗完再检查每处理100个文件就抽10个做质量检查。发现问题及时调整参数避免批量返工。这套清洗方案在我们几个探矿项目上跑下来数据可用率稳定在80%以上检索准确率Top-5从最初的40%左右提升到了接近80%。最耗时的环节其实是PDF的OCR和表格还原大概占了总处理时间的60%但这部分做扎实了后续的RAG检索效果才有保障。TXT和Word的清洗相对快但编码和表格的坑也不少建议先用小批量数据把管道调通再上全量。
返回列表