金融 AI 热浪下,"让 LLM 读财报"已经成为标配需求。但很少有人认真测过:直接扔给 GPT-4o 和先用 MinerU 预处理再送进去,到底差多少?

为什么这个问题值得认真测

现在主流的财报解析路子有两条:

路线 A — 原生多模态:把 PDF 截图或直接上传给 GPT-4o / Claude 3.5,让模型自己"看"。

路线 B — 先解析再推理:用 MinerU 把 PDF 转成结构化 Markdown,再送给 LLM。

路线 A 听起来更省事。但省事真的等于好用吗?

测试设置

测试集:随机抽取 20 份 A 股上市公司 2024 年年报(含双栏排版、合并报表、复杂表格),以及 5 份港股招股书。

评估维度:

维度

说明

表格提取准确率

以人工校对结果为基准,逐单元格比对

数字精度

财务数据是否与原文完全一致

Token 消耗

同等任务下的 API 消耗对比

处理耗时

单份文档端到端时间

跨页内容完整性

跨页表格、脚注能否正确拼接

对比结果

核心数据表

指标

GPT-4o 原生(图片上传)

MinerU + GPT-4o

MinerU + GPT-4o-mini

表格单元格准确率

81.3%

96.7%

94.2%

数字零误差率

74.6%

98.1%

96.8%

跨页内容完整

43%

91%

88%

平均 Token 消耗/份

~85,000

~12,000

~12,000

API 成本/份(美元)

~$1.02

~$0.04

~$0.008

平均处理耗时

38s

52s(含解析)

45s

说明:GPT-4o 原生方案按每页截图方式上传,分辨率 1080p;MinerU 版本为 0.9.x,使用 auto 模式。

最关键的发现:

  • 跨页表格是原生多模态最大的软肋。财报里常见的"合并资产负债表"跨 3-4 页,GPT-4o 能完整拼接的概率不到一半。

  • Token 成本差了约 7 倍。如果每天要处理 100 份财报,原生方案月成本在 $3,000+ 级别,MinerU 预处理方案约 $120。

  • 准确率在数字上差距更大:财务数字里一个千分位符号、一个负号,原生方案容易丢失。

代码实现

Step 1:用 MinerU 批量解析财报 PDF

import os

from pathlib import Path

from magic_pdf.data.data_reader_writer import FileBasedDataWriter, FileBasedDataReader

from magic_pdf.data.dataset import PymuDocDataset

from magic_pdf.config.make_content_config import DropMode, MakeMode

from magic_pdf.pipe.UNIPipe import UNIPipe

from magic_pdf.pipe.OCRPipe import OCRPipe

def parse_financial_pdf(pdf_path: str, output_dir: str) -> str:

"""

解析财报 PDF,返回 Markdown 文本

"""

pdf_name = Path(pdf_path).stem

output_path = Path(output_dir) / pdf_name

output_path.mkdir(parents=True, exist_ok=True)

writer = FileBasedDataWriter(str(output_path))

reader = FileBasedDataReader("")

pdf_bytes = reader.read(pdf_path)

ds = PymuDocDataset(pdf_bytes)

# 自动判断是否需要 OCR

if ds.classify() == "ocr":

pipe = OCRPipe(ds, [], writer)

else:

pipe = UNIPipe(ds, {"_pdf_type": "", "model_list": []}, writer)

pipe.pipe_classify()

pipe.pipe_analyze()

pipe.pipe_parse()

md_path = str(output_path / f"{pdf_name}.md")

pipe.pipe_mk_markdown(md_path, drop_mode=DropMode.NONE)

with open(md_path, "r", encoding="utf-8") as f:

return f.read()

# 批量处理

pdf_dir = "./annual_reports"

results = {}

for pdf_file in Path(pdf_dir).glob("*.pdf"):

print(f"正在解析: {pdf_file.name}")

md_content = parse_financial_pdf(str(pdf_file), "./parsed_output")

results[pdf_file.stem] = md_content

print(f" 完成,字符数: {len(md_content)}")

Step 2:提取关键财务指标

from openai import OpenAI

client = OpenAI()

EXTRACT_PROMPT = """
你是一个财务数据提取专家。以下是一份年报的 Markdown 格式内容。

请提取以下指标(单位:亿元人民币,保留两位小数):
- 营业收入(本期 / 上期)
- 净利润(本期 / 上期)
- 总资产
- 净资产
- 经营活动现金流量净额

如果找不到某项数据,填 null。
严格按 JSON 格式输出,不要添加任何解释。

年报内容:
{content}
"""

def extract_financials(md_content: str, company: str) -> dict:
    # 财报可能很长,只取关键章节(合并利润表 + 合并资产负债表部分)
    # 实际使用建议做章节切割,这里简化处理
    truncated = md_content[:40000]  # GPT-4o 128k 上下文,留余量给 system prompt

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "user", "content": EXTRACT_PROMPT.format(content=truncated)}
        ],
        response_format={"type": "json_object"},
        temperature=0
    )

    import json
    result = json.loads(response.choices[0].message.content)
    result["company"] = company
    result["tokens_used"] = response.usage.total_tokens
    return result

#### 提取所有公司数据
all_financials = []
for company, md_content in results.items():
    print(f"提取 {company} 财务数据...")
    data = extract_financials(md_content, company)
    all_financials.append(data)

Step 3:对比原生方案的 Token 消耗

python
import base64
from pathlib import Path

def encode_pdf_as_images(pdf_path: str) -> list:
"""将 PDF 每页转为 base64 图片(用于原生多模态对比)"""
import fitz  # PyMuPDF
doc = fitz.open(pdf_path)
images = []
for page_num in range(len(doc)):
    page = doc.load_page(page_num)
    mat = fitz.Matrix(2, 2)  # 2x 缩放,约 1080p
    pix = page.get_pixmap(matrix=mat)
    img_bytes = pix.tobytes("png")
    b64 = base64.b64encode(img_bytes).decode()
    images.append(b64)
return images

def extract_financials_native(pdf_path: str) -> dict:
"""原生多模态方案:直接上传图片"""
images = encode_pdf_as_images(pdf_path)
content = [{"type": "text", "text": EXTRACT_PROMPT.format(content="请从以下财报页面图片中提取数据:")}]
for img_b64 in images:
    content.append({
        "type": "image_url",
        "image_url": {"url": f"data:image/png;base64,{img_b64}"}
    })
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": content}],
    response_format={"type": "json_object"},
    temperature=0
)
import json
result = json.loads(response.choices[0].message.content)
result["tokens_used"] = response.usage.total_tokens
return result
对同一份文档跑两种方案,对比 Token 消耗
test_pdf = "./annual_reports/某公司2024年报.pdf"

print("=== MinerU + GPT-4o ===")
md = parse_financial_pdf(test_pdf, "./test_output")
result_mineru = extract_financials(md, "test")
print(f"Token 消耗: {result_mineru['tokens_used']}")

print("\n=== GPT-4o 原生 ===")
result_native = extract_financials_native(test_pdf)
print(f"Token 消耗: {result_native['tokens_used']}")

print(f"\n成本比: {result_native['tokens_used'] / result_mineru['tokens_used']:.1f}x")
---

原生多模态什么时候更好?

公平讲,GPT-4o 原生方案也有它的优势场景:

场景 推荐方案 原因
单份文档、临时查询 GPT-4o 原生 不需要部署额外工具,够用
包含大量自定义图表 GPT-4o 原生 MinerU 对图表语义理解偏弱
批量处理、成本敏感 MinerU + LLM 成本差距太大,无悬念
复杂表格、精度要求高 MinerU + LLM 结构化解析明显更准
离线/私有化部署 MinerU 本地 GPT-4o 没有本地方案

总结

"通用文档解析"听起来平庸,但放到财报场景里,它在解决一个实实在在的工程问题:

把非结构化的 PDF 数据,低成本、高准确率地变成 LLM 能可靠处理的文本。

原生多模态不是不好,而是贵、且在结构化信息提取上存在天花板。MinerU 不替代 LLM,它让 LLM 能做更好的事。

如果你们团队有财报/研报/招股书的处理需求,这条技术路线值得认真评估。代码已整理,可直接拿去跑。


开源地址: https://github.com/opendatalab/MinerU

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐