文档解析与预处理:从杂乱文档到干净语料的蜕变

做金融RAG系统,最头疼的往往不是模型选型,而是文档解析。我见过太多团队把精力花在调模型上,结果召回率上不去,一查原因——文档解析阶段就丢了30%的信息。说白了,垃圾进垃圾出,这个道理在RAG系统里体现得淋漓尽致。

今天我们就聊聊文档解析与预处理。这部分工作虽然看起来不那么"高大上",但它是整个RAG系统的地基。地基不稳,上面建得再漂亮也是白搭。

PDF解析:金融文档的主力军

金融行业里,PDF是绝对的主角。年报、研报、合同、公告,清一色的PDF。但PDF这玩意儿,解析起来真让人头大。

PDF的两种本质

PDF其实分两种:一种是文本型PDF,文字可以直接提取;另一种是扫描件,本质上是图片。金融文档里,后者占比不低,尤其是那些年代久远的年报。

对于文本型PDF,我常用的工具是PyMuPDF(也就是fitz)和pdfplumber。PyMuPDF速度快,适合大批量处理;pdfplumber提取表格更准,但速度慢一些。

import fitz  # PyMuPDF

def extract_text_from_pdf(pdf_path):
    doc = fitz.open(pdf_path)
    text = ""
    for page_num, page in enumerate(doc):
        text += page.get_text()
    return text

# 我个人习惯先试试PyMuPDF,如果提取效果不好再换pdfplumber

对于扫描件,那就得上OCR了。金融场景下,我推荐PaddleOCR,中文识别率相当能打。不过要注意,OCR的速度是个瓶颈,一台机器一天也处理不了太多文档。

我的经验: 处理大批量PDF时,先做个"体检"——随机抽几页看看是文本型还是扫描件。如果是混合型(部分文字可提取,部分需要OCR),那就得走两套流程。

Word与HTML解析:别小看它们

虽然金融行业PDF是主流,但Word和HTML也不少。招股说明书初稿经常是Word格式,而财经新闻、公告则多是HTML。

Word解析我用python-docx,它处理.docx格式很顺手。但有个坑——Word里的表格,python-docx提取起来有点费劲,尤其是合并单元格的情况。

from docx import Document

def extract_from_word(docx_path):
    doc = Document(docx_path)
    full_text = []
    for para in doc.paragraphs:
        full_text.append(para.text)
    return '\n'.join(full_text)

HTML解析就简单多了,BeautifulSoup基本够用。但金融网站的HTML结构往往很乱,广告、导航栏、版权声明混在一起。我建议先做个"内容提取器",只保留正文区域。

注意: 有些金融网站会动态加载内容,这时候BeautifulSoup就抓瞎了。得用Selenium或Playwright模拟浏览器,但速度会慢很多。我曾经为了抓一个上市公司的公告,写了三版解析器才搞定。

表格提取:金融数据的金矿

金融文档里,表格是信息密度最高的部分。资产负债表、利润表、现金流量表,全是表格。但表格提取,是文档解析里最大的坑。

为什么表格难提取?

PDF里的表格,本质上只是一堆线条和文字。没有"行"和"列"的概念,全靠算法去猜。遇到跨页表格、合并单元格、无边框表格,很多工具就直接跪了。

我试过不少工具,目前比较靠谱的组合是:

  • Camelot:基于视觉的表格提取,准确率高,但速度慢
  • Tabula:基于文本的提取,速度快,但对复杂表格支持一般
  • pdfplumber:介于两者之间,我用的最多
import pdfplumber

def extract_tables(pdf_path):
    tables = []
    with pdfplumber.open(pdf_path) as pdf:
        for page in pdf.pages:
            page_tables = page.extract_tables()
            tables.extend(page_tables)
    return tables

# 注意:返回的是列表的列表,需要自己转成DataFrame
核心建议: 不要指望一个工具搞定所有表格。我通常的做法是:先用pdfplumber试试,如果提取结果乱码,再换Camelot。实在不行,就手动标注几个样本,训练一个简单的表格检测模型。

公式识别:数学与金融的桥梁

金融文档里公式不少,尤其是量化研报和金融工程相关的材料。CAPM模型、Black-Scholes公式、各种风险指标的计算公式,都需要准确识别。

公式识别主要有两种思路:

  • LaTeX识别:用工具把公式转成LaTeX格式,再渲染成可读形式
  • MathML识别:直接提取公式的数学结构

目前比较成熟的是Mathpix,但它是收费的。开源方案里,我试过pix2tex,效果还行,但中文场景下准确率会下降。

# 伪代码示例:公式识别流程
# 1. 用OCR定位公式区域
# 2. 用公式识别模型转成LaTeX
# 3. 用LaTeX渲染器生成可读文本

# 实际项目中,我通常把公式单独存为一个字段
# 不做渲染,保留LaTeX格式,方便后续检索
一个小技巧: 如果公式数量不多,手动录入反而比自动识别更靠谱。我有个项目,200页的研报里只有十几个公式,自动识别错了5个,最后还得人工校对。嗯,有时候"笨办法"反而是最快的。

文档分块策略:chunking的艺术

文档解析完了,接下来就是分块。这一步直接决定了检索效果的好坏。分块太粗,检索不精准;分块太细,上下文丢失。

常见的分块策略

策略 适用场景 优点 缺点
固定长度分块 通用场景 实现简单 可能切断语义
段落分块 结构化文档 语义完整 块大小不一
语义分块 长文档 语义连贯 计算成本高
递归分块 复杂文档 灵活可控 参数调优麻烦

我个人最常用的是递归分块。先按标题分,再按段落分,最后按句子分。这样既能保证语义完整,又能控制块的大小。

from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=512,
    chunk_overlap=50,
    separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""]
)

chunks = text_splitter.split_text(document_text)
避坑指南: 我曾经在分块时没考虑表格,结果一个完整的财务报表被切成了好几块。检索时问"2023年营收",只返回了半张表。后来我加了个规则:表格和图片不参与分块,单独存储。这样检索时先查文本块,再关联对应的表格。

知识体系总览

下面这张图,是我对文档解析与预处理整个流程的总结。你可以看到,从原始文档到可检索的语料,中间要经过多少道工序。

文档解析与预处理流程 原始文档 文档解析 预处理 文档分块 向量化存储 解析细节 • PDF:PyMuPDF / pdfplumber / OCR • Word:python-docx • HTML:BeautifulSoup / Selenium • 表格:Camelot / Tabula • 公式:Mathpix / pix2tex 分块策略 • 固定长度分块 • 段落分块 • 语义分块 • 递归分块(推荐) • 表格单独存储 核心原则:保留语义完整性,控制块大小,特殊元素单独处理 经验总结:没有万能方案,根据文档类型灵活组合工具才是王道

你看,从原始文档到最终可用的语料,中间要经过解析、预处理、分块三道大关。每一关都有不少坑,但踩过之后,你会发现其实有规律可循。

我的核心建议: 先花时间分析你的文档类型,再选择对应的工具组合。金融文档千差万别,一份招股说明书和一份研报的解析策略完全不同。别想着一个方案打天下,那是不可能的。

好了,文档解析与预处理就聊到这儿。这部分工作虽然琐碎,但做好了,后面的检索和生成环节会顺畅很多。记住,RAG系统的天花板,往往取决于你文档预处理的下限。

专注资料整理