4. 文档解析与清洗:PDF解析、OCR、表格提取与乱码处理

做金融RAG,最头疼的环节是什么?

我个人觉得,不是模型选型,也不是Prompt设计,而是——文档解析

你想想看,金融数据长什么样?招股说明书、年报、研报、合同、票据……清一色的PDF。而且这些PDF质量参差不齐:有的是扫描件,有的是文字版但带表格,有的干脆是图片套了一层PDF壳。我刚开始做金融RAG项目时,就栽在了解析这一步。数据进不去,后面的RAG全是空谈。

今天我们就来聊聊,怎么把这堆“脏数据”洗干净。

核心观点:文档解析是RAG系统的“入口关卡”。入口没做好,后面所有环节都是白费。金融场景下,PDF解析的难点在于:格式多样、表格复杂、乱码频发、扫描件居多。

4.1 PDF解析:PyMuPDF vs pdfplumber

PDF解析,说白了就是把PDF里的文字、图片、表格“抠”出来。市面上工具很多,但我最常用的就两个:PyMuPDF(fitz)pdfplumber

PyMuPDF,速度快,解析能力强。它底层用的是MuPDF引擎,对文字提取非常高效。我在处理上千页的招股书时,首选就是它。

pdfplumber,精度高,尤其擅长表格提取。它基于PDFMiner,对文字位置、字体信息抓得很准。如果你需要保留排版信息,pdfplumber是更好的选择。

怎么选?我一般这样判断:

  • 纯文字PDF,量大,追求速度 → PyMuPDF
  • 带表格、需要保留排版 → pdfplumber
  • 扫描件 → 先OCR,再解析

来个代码示例,看看实际怎么用:

# PyMuPDF 快速提取文字
import fitz

doc = fitz.open("年报.pdf")
for page_num, page in enumerate(doc):
    text = page.get_text()
    print(f"第{page_num+1}页:{text[:200]}...")

# pdfplumber 精确提取
import pdfplumber

with pdfplumber.open("年报.pdf") as pdf:
    for page in pdf.pages:
        text = page.extract_text()
        tables = page.extract_tables()
        print(f"提取到 {len(tables)} 个表格")

我的经验:PyMuPDF提取文字时,偶尔会漏掉一些特殊字符。遇到这种情况,我会用pdfplumber做二次校验。两个工具互补使用,效果最好。

4.2 OCR:PaddleOCR实战

金融文档里,扫描件占比很高。尤其是老合同、传真件、手写票据,根本没法直接解析。这时候就得靠OCR。

OCR工具我试过不少,Tesseract、EasyOCR、PaddleOCR……PaddleOCR是我目前用得最顺手的。原因有三:

  • 中文识别率高,尤其针对金融场景的专有名词
  • 支持版面分析,能区分标题、正文、表格
  • 部署简单,GPU/CPU都能跑

我记得有一次,客户给了一批90年代的合同扫描件,纸张都发黄了。Tesseract识别出来全是乱码,换成PaddleOCR后,准确率直接飙到95%以上。

怎么用?看代码:

from paddleocr import PaddleOCR

ocr = PaddleOCR(use_angle_cls=True, lang='ch')
result = ocr.ocr('合同扫描件.jpg', cls=True)

for line in result[0]:
    text = line[1][0]
    confidence = line[1][1]
    print(f"识别文字:{text},置信度:{confidence:.2f}")

注意:OCR不是万能的。遇到印章盖住文字、手写体潦草、纸张褶皱严重的情况,识别率会下降。我的做法是:先OCR,再用规则或模型做后处理纠错。

4.3 表格提取:从PDF到结构化数据

金融文档里,表格是信息密度最高的部分。利润表、资产负债表、现金流量表……全是表格。但PDF里的表格,提取起来特别麻烦。

为什么?因为PDF本身没有“表格”这个概念。它只有线条、文字、位置。所谓的表格提取,本质上是根据线条和文字位置,还原出表格结构。

pdfplumber在这方面做得不错。它能把表格识别成二维数组,方便后续处理。

import pdfplumber

with pdfplumber.open("财报.pdf") as pdf:
    for page in pdf.pages:
        tables = page.extract_tables()
        for table in tables:
            for row in table:
                print(row)  # 每一行是一个列表

但这里有个坑——合并单元格。pdfplumber对合并单元格的处理不太理想,经常把合并的单元格拆成多个空值。我遇到过好几次,提取出来的表格全是None,排查了半天才发现是合并单元格的问题。

怎么解决?我的方案是:

  • 先用pdfplumber提取原始表格
  • 再用规则做后处理,合并空值单元格
  • 如果表格特别复杂,考虑用Camelot或Tabula

小技巧:对于标准格式的财务报表,可以先用正则匹配表头,再按列对齐。这样比纯表格提取更稳定。

4.4 乱码处理:那些年我们踩过的坑

乱码,是金融文档解析的“老朋友”了。

我遇到过各种乱码:

  • PDF本身编码问题,提取出来全是方框
  • 字体缺失,导致某些字符显示为乱码
  • OCR识别错误,把“千”识别成“干”
  • 特殊符号(如™、®、℃)解析失败

怎么处理?我总结了一套流程:

  1. 检测乱码:用正则匹配常见乱码字符(如\u0000、\ufffd)
  2. 编码转换:尝试不同的编码(GBK、UTF-8、GB2312)
  3. 字体替换:安装常用中文字体(宋体、黑体、微软雅黑)
  4. 后处理纠错:用规则或模型修正常见错误

举个例子,我曾经处理过一份PDF,提取出来的“营业收入”变成了“营?收入”。排查后发现,是PDF里用了特殊字体,PyMuPDF无法识别。换成pdfplumber后,问题解决了。

# 乱码检测与处理示例
import re

def clean_text(text):
    # 检测乱码字符
    if re.search(r'[\ufffd\u0000\u0001]', text):
        print("检测到乱码,尝试修复...")
        # 尝试编码转换
        try:
            text = text.encode('latin1').decode('gbk')
        except:
            pass
    return text

避坑指南:我曾经因为乱码问题,导致RAG检索到的文档全是“?????”,用户反馈说“你们这系统是不是坏了?”……从那以后,我每次解析完都会做一次乱码检测,确保数据质量。

4.5 本章知识体系

下面这张图,是我对本章内容的总结。你可以把它当作一个“解析决策树”:

金融文档解析与清洗流程 PDF文档 文字版PDF 扫描件PDF PyMuPDF 快速提取 pdfplumber 精确提取 PaddleOCR 识别 表格提取与结构化 乱码检测与清洗 ✅ 结构化数据输出

嗯,这张图基本概括了今天的核心内容。从PDF文档出发,先判断是文字版还是扫描件,然后选择对应的解析工具,最后统一做表格提取和乱码清洗。

说白了,文档解析没有银弹。不同的文档类型、不同的质量,需要不同的处理策略。我的建议是:先分析,再动手。拿到一批文档后,先抽样看看质量,再决定用什么工具、走什么流程。

好了,这一章就到这里。记住,文档解析是RAG系统的“地基”。地基没打好,上面盖再漂亮的房子也是危楼。


专注资料整理