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识别错误,把“千”识别成“干”
- 特殊符号(如™、®、℃)解析失败
怎么处理?我总结了一套流程:
- 检测乱码:用正则匹配常见乱码字符(如\u0000、\ufffd)
- 编码转换:尝试不同的编码(GBK、UTF-8、GB2312)
- 字体替换:安装常用中文字体(宋体、黑体、微软雅黑)
- 后处理纠错:用规则或模型修正常见错误
举个例子,我曾经处理过一份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文档出发,先判断是文字版还是扫描件,然后选择对应的解析工具,最后统一做表格提取和乱码清洗。
说白了,文档解析没有银弹。不同的文档类型、不同的质量,需要不同的处理策略。我的建议是:先分析,再动手。拿到一批文档后,先抽样看看质量,再决定用什么工具、走什么流程。
好了,这一章就到这里。记住,文档解析是RAG系统的“地基”。地基没打好,上面盖再漂亮的房子也是危楼。