3. 文档解析与预处理:PDF/HTML/Word解析技术、OCR在金融文档中的应用、文档分块策略
好,咱们进入第三章。这一章聊的是RAG系统的“入口”——文档解析与预处理。
说实话,我在金融行业干了这么多年,最头疼的从来不是模型选型,而是数据进来那一步。你想想看,一个PDF文件,里面可能既有扫描件,又有表格,还有手写批注。模型再强,喂进去的是垃圾,吐出来的只能是更精致的垃圾。
所以这一章,我带你把这些“脏活累活”捋清楚。
3.1 PDF解析:金融文档的硬骨头
金融圈里,PDF是绝对的主角。招股书、年报、研报、合同……全是PDF。但PDF这玩意儿,说白了就是个“打印格式”,不是“数据结构”。
我常用的解析工具有这么几个:
- PyMuPDF(fitz):轻量级,解析速度快。适合文本型PDF。我在处理招股书时经常用它,因为招股书大多是文字排版,没有太多复杂图片。
- pdfplumber:对表格支持好。如果你要解析财报里的资产负债表,这个很顺手。
- pdfminer.six:老牌工具,能精确提取文字位置信息。适合需要保留排版结构的场景。
举个例子,用PyMuPDF提取文本:
import fitz
doc = fitz.open("annual_report.pdf")
for page_num, page in enumerate(doc):
text = page.get_text()
print(f"--- Page {page_num + 1} ---")
print(text)
嗯,这里要注意。很多金融PDF是“伪PDF”——文字其实是图片。这时候上面的代码就抓瞎了。怎么办?往下看。
3.2 OCR在金融文档中的应用
OCR,光学字符识别。说白了就是把图片里的文字“认”出来。
我在项目中遇到过最典型的场景:某券商发来的历史交易确认单,全是扫描件。文字模糊、印章重叠、表格线断断续续。当时我试了好几种方案。
目前金融场景下,我推荐两个方向:
- Tesseract OCR:开源免费,适合清晰文档。配合中文语言包(chi_sim)效果还行。但遇到复杂版面,准确率会掉。
- PaddleOCR:百度出品,对中文支持极好。我实测过,在金融票据、合同扫描件上,准确率比Tesseract高出一截。而且它自带版面分析,能区分标题、正文、表格。
用PaddleOCR做个简单示例:
from paddleocr import PaddleOCR
ocr = PaddleOCR(use_angle_cls=True, lang='ch')
result = ocr.ocr('scanned_contract.jpg', cls=True)
for line in result[0]:
text = line[1][0]
confidence = line[1][1]
print(f"{text} (置信度: {confidence:.2f})")
3.3 HTML与Word解析
除了PDF,金融文档还有不少来自网页(比如财经新闻、监管公告)和Word文件(比如内部备忘录、尽调报告)。
HTML解析,我习惯用BeautifulSoup。但要注意,金融网站的结构经常变。我建议不要硬编码选择器,而是用更鲁棒的方式——比如按标题层级提取正文。
from bs4 import BeautifulSoup
import requests
url = "https://example.com/financial-news"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
# 提取所有段落文本
paragraphs = soup.find_all('p')
content = '\n'.join([p.get_text() for p in paragraphs])
Word解析,python-docx库是首选。它能处理.docx格式。但金融圈里还有大量老旧的.doc文件,这时候我一般先用LibreOffice转成.docx,再解析。
from docx import Document
doc = Document("internal_report.docx")
full_text = []
for para in doc.paragraphs:
full_text.append(para.text)
print('\n'.join(full_text))
3.4 文档分块策略:RAG系统的关键一步
文档解析完了,接下来就是分块。这一步做不好,后面检索就是空中楼阁。
你想想看,如果一块太大,比如把整份年报塞进去,检索时模型根本找不到具体位置。如果一块太小,比如按句子切,上下文又断了。
我总结了几种分块策略,按场景选:
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 固定长度分块 | 通用文档 | 实现简单,速度快 | 可能切断语义 |
| 语义分块 | 长文本、研报 | 保持段落完整性 | 依赖NLP模型,稍慢 |
| 递归分块 | 结构化文档 | 按标题层级切分 | 需要文档有清晰结构 |
| 滑动窗口分块 | 问答场景 | 上下文有重叠,信息不丢失 | 存储冗余大 |
我个人习惯用递归分块。比如一份招股书,先按章节切,再按段落切,最后按句子切。这样既能保留结构,又能控制块大小。
用LangChain的RecursiveCharacterTextSplitter实现:
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""]
)
chunks = text_splitter.split_text(full_text)
print(f"共切分成 {len(chunks)} 个块")
3.5 本章知识体系
下面这张图,是我梳理的文档解析与预处理的核心流程。你可以把它当作一个检查清单:
核心要点:文档解析不是一锤子买卖。我建议你在项目初期就建立一个“文档质量检查”环节——解析完成后,随机抽几份文档,人工核对提取结果。这个习惯帮我避免过很多次“数据污染”事故。
好了,这一章的内容就到这里。解析和预处理是RAG系统的地基,地基不稳,上层建筑再漂亮也没用。下一章我们会聊向量化与嵌入模型,到时候见。