ZenParse 是一个专门面向 中文上市公司财报 PDF 的预处理工具,用来把复杂的 PDF 文档解析成 结构化的「父子分块 + 表格组」JSON 数据,方便后续做向量检索、问答系统、风控分析等。
核心目标:尽量不丢信息,又切成适合模型使用的小块。
- 为中文年报/半年报/季报搭建 向量检索 / RAG 知识库
- 需要从 PDF 中 精准保留表格 + 标题 + 注释 的分析类应用
- 对财报进行 高质量切分和打分,过滤掉噪声内容
-
父子分块架构(SmartChunker)
- 父块(ParentChunk):尽量覆盖完整语境的一大段内容,用于构建上下文与章节结构
- 子块(ChildChunk):在父块内部按滑动窗口细分,适合向量检索和问答召回
-
表格上下文识别(TableGroup)
- 自动识别表格的标题、单位说明、注释等上下文
- 将「标题 + 表格主体 + 注释」组合成一个
TableGroup,保证表格在检索/问答中不丢上下文 - 尝试将表格内容转为标准 Markdown 表格,保留行列结构,便于前端展示或导出审阅
-
多引擎 PDF 解析与与高级模型(可选)检测
- 支持
pdfplumber/unstructured/pymupdf等多种底层引擎,由engine_checker自动检测并按优先级选择 - 在
hybrid_table策略下,对“低质量表格 / 明显应该有表格却没识别出来的页面”按需触发 DocLayout‑YOLO 等高级模型做高级表格检测。 - 自动对比并合并
pdfplumber与高级模型的结果:去重、保留质量更高/更完整的表格 - 支持从 HuggingFace 自动下载 DocLayout‑YOLO 模型,首次运行自动完成配置;详细配置说明和选型建议见
docs/strategy_and_models.md
- 支持
-
财报语义增强与准则适配(含 Standards Adapter,可选)
- 在分块和表格上自动标注会计科目、时间期间、是否为财务数据等信息
- 针对 2017–2021 年新收入 / 金融工具 / 租赁准则落地后,新旧报表之间“科目改名、口径变了、数字不好比”的问题,提供一层自动适配
- 能识别当前报表是按新准则还是旧准则披露,并把“叫法不同但本质相同”的项目对齐(如预收款项 vs 合同负债、应收票据 vs 应收款项融资),提供统一新旧准则口径的“标准化指标”,方便做 5–10 年跨期分析或喂给大模型使用;详情见
docs/standards_adapter.md
-
质量评估与审阅辅助
- 每个分块都有
quality_score、信息密度、连贯性等指标,便于筛选和调参与调试 - 提供整体统计信息(页数、块数、质量分布、处理耗时),用于评估某份年报的抽取效果
- 附带审阅脚本(如
scripts/preview_chunks.py),支持将分块结果导出为 Excel,按页码排序人工检查
示例:
python scripts/preview_chunks.py output/某公司年报_chunks_20251128_194530.json --show-children --limit 0 --xlsx output/review.xlsx
更详细的审阅流程见docs/review_chunks.md
- 每个分块都有
-
工程化与批处理能力
- 使用统一日志系统(默认输出到
logs/),方便排查问题 - 基于
config.yaml的配置化设计,可以按项目需求调整解析/分块策略 - 提供
runner.py命令行工具,支持目录遍历、文件列表、去重处理,适合批量跑整套年报
- 使用统一日志系统(默认输出到
zenparse/pipeline.py:端到端管线(PDF → 元素 → 表格组 → 父子分块)data/pdf_parser.py:PDF 解析(多引擎,中文财报优化)data/context_identifier.py:表格上下文识别,生成TableGroupdata/smart_chunker.py:智能分块器,生成父块/子块data/models.py:数据模型定义(DocumentElement、Chunk、TableGroup等)core/logger.py:统一日志系统core/engine_checker.py:解析引擎检测工具
runner.py:命令行批量处理工具config.yaml:默认配置input/:示例输入目录output/:分块结果 JSON 输出目录
git clone <your-repo-url>
cd ZenParse
python -m venv venv
source venv/bin/activate # macOS/Linux
# venv\Scripts\activate # Windows
pip install -r requirements.txt建议使用 Python 3.10+(最低 3.8),并在有一定内存的环境下运行(处理大体量 PDF 时更稳)。
默认示例目录:
input/pdfs/:放待处理的 PDF 文件input/pdf_file_list/pdf_list_test.txt:示例文件名列表(每行一个)
你也可以使用自己的目录,只要在命令行参数中指定即可。
(1)处理一个目录下所有 PDF
python runner.py -i input/pdfs -o output- 会扫描
input/pdfs目录下的所有.pdf - 对每个 PDF 生成
<原文件名>_chunks.json到output/目录
(2)处理单个 PDF 文件
python runner.py -i input/pdfs/某公司年报.pdf -o output(3)使用文件列表(推荐批量精确控制)
python runner.py \
--file-list input/pdf_file_list/pdf_list_test.txt \
--search-dirs input/pdfs input/pdfs_raw \
--output output--file-list:指定一个文本文件,每行一个文件名(支持#注释)--search-dirs:在这些目录中搜索对应的 PDF 文件
(4)直接指定多个文件名
python runner.py \
--files 2020年年报.pdf,2019年年报.pdf \
--search-dirs input/pdfs input/pdfs_raw \
--output output(5)避免覆盖输出(添加时间戳)
python runner.py -i input/pdfs -o output --timestamp输出文件会变成:xxx_chunks_20251125_130003.json 这种格式。
runner.py 支持丰富的命令行参数:
| 参数 | 简写 | 说明 |
|---|---|---|
--input |
-i |
输入 PDF 文件或目录,可重复使用多次 |
--output |
-o |
输出 JSON 目录(默认 output/) |
--config |
-c |
指定配置文件路径(默认 config.yaml) |
--timestamp |
-t |
在输出文件名后添加时间戳(避免覆盖已有文件) |
--file-list |
-l |
从文本文件读取待处理文件名列表(每行一个) |
--files |
-f |
逗号分隔的文件名列表(如 a.pdf,b.pdf) |
--search-dirs |
-s |
搜索 PDF 的目录,可提供多个目录 |
示例:
pdf_parsing:
# 解析策略:
# - auto 自动按优先级选择可用引擎
# - pdfplumber 只使用 pdfplumber(速度快、对电子版财报效果好)
# - unstructured 使用 unstructured 的高分辨率解析(适合复杂/扫描版,但较慢)
# - hybrid_table 启用高级表格检测与结构化(最慢,仅在特别需要表格结构时建议开启)
strategies:
- pdfplumber
context:
context_before: 10
context_after: 3
chunking:
parent_size: 4000
child_size: 1200
overlap: 200
min_chunk_size: 50
min_quality_score: 0.3关键字段解释:
pdf_parsing.strategies- 支持:
auto/pdfplumber/unstructured/hybrid_table - 建议:一般用
pdfplumber;需要更强表格/扫描版支持时可以尝试unstructured或hybrid_table(更占资源)
- 支持:
context.context_before/context.context_after- 表格上下文搜索范围(前后多少个元素),主要用于找到「表标题」「单位说明」「注释」
chunking.parent_size- 父块目标大小(字符数),越大上下文越完整,但后续召回粒度会变粗
chunking.child_size/chunking.overlap- 子块大小与重叠大小,用于滑动窗口切分
- 一般来说:父块偏大、子块偏小且有适当重叠,是比较稳妥的配置
min_chunk_size/min_quality_score- 过滤过短或质量过低的分块,减少噪声
table_extraction:
advanced_model: doclayout_yolo # doclayout_yolo / detectron2 / none
model_path: null # 自定义权重路径,null 表示从 HuggingFace 自动下载
model_repo: "juliozhao/DocLayout-YOLO-DocStructBench" # HuggingFace 模型仓库
model_filename: "doclayout_yolo_docstructbench_imgsz1024.pt" # 模型文件名
quality_threshold: 0.65 # 表格质量低于此值触发高级检测
detection_conf: 0.30 # YOLO 置信度阈值
detection_iou: 0.50 # YOLO NMS IoU 阈值
merge_iou_threshold: 0.55 # 表格去重合并 IoU 阈值
bbox_padding_ratio: 0.02 # YOLO 框裁剪前的扩张比例
render_dpi: 150 # 检测用页面渲染 DPI
skip_ocr_for_digital: true # 数字 PDF 下跳过 OCR 兜底
ocr_trigger_char_threshold: 10 # bbox 内字符数低于此值时认为"文本过少"建议:
- 大部分数字财报:
advanced_model: doclayout_yolo+ 合理设置quality_threshold,model_path: null自动下载模型。 - 若只追求速度:
advanced_model: none,只用 pdfplumber。 - 若有扫描版/难页,可将
skip_ocr_for_digital设为false,允许文本阶段触发 OCR 兜底。
详细配置说明和选型建议请参考 docs/strategy_and_models.md。
每个 PDF 会对应一个 JSON 文件,例如:
output/2020-01-21__...__年度报告_chunks.json
结构大致如下(简化示意):
{
"source": "xxx.pdf",
"sources": {
"src-xxxxx": {
"id": "src-xxxxx",
"relative_path": "input/pdfs/xxx.pdf",
"display_name": "xxx.pdf",
"absolute_path_hash": "b03d552430c51c146201a6d98b18eb25"
}
},
"overview": {
"source_file": "xxx.pdf",
"counts": {
"parents": 12,
"children": 48,
"table_groups": 5,
"pages": [[1, 20]],
"page_count": 20
},
"quality": {
"high": 30,
"medium": 20,
"low": 10
},
"avg_child_per_parent": 4.0,
"parent_previews": [
{
"chunk_id": "abcd1234",
"chars": 3500,
"preview": "本公司董事会、监事会及全体董事、监事、高级管理人员保证本报告内容真实、准确、完整...",
"page_numbers": [1, 2],
"child_count": 4
}
]
},
"parents": [
{
"chunk_id": "abcd1234",
"content": "……",
"chunk_type": "text_group",
"child_ids": ["efgh5678", "..."],
"metadata": {
"source_ref": "src-xxxxx",
"page_numbers": [1, 2],
"char_count": 3521,
"report_type": "annual_report",
"fiscal_year": 2019
},
"contains_financial_data": true,
"financial_indicators": ["营业收入", "净利润"],
"quality_score": 0.87
}
],
"children": [
{
"chunk_id": "efgh5678",
"parent_id": "abcd1234",
"content": "……",
"start_char": 0,
"end_char": 1200,
"page_number": 1,
"metadata": {
"source_ref": "src-xxxxx",
"page_numbers": [1]
},
"contains_financial_data": true
}
]
}可以直接把 parents 和 children 用于向量化,parent_id / child_ids 则用于做父子联动召回。
表格相关内容会以 chunk_type="table_group" 的形式出现在分块中,同时在 overview.counts.table_groups 中统计表格组数量。
如果你希望在自己的 Python 代码中直接调用:
from zenparse import ZenPipeline
pipeline = ZenPipeline("config.yaml")
parents, children, table_groups = pipeline.process(
"input/pdfs/2020-01-21__某公司__年度报告.pdf"
)
print(len(parents), len(children), len(table_groups))在此基础上,你可以:
- 将
parents/children向量化,构建检索库 - 对
table_groups做进一步结构化处理或可视化 - 使用
metadata中的页码、财报年份、公司代码等进行过滤
- 目前优化重点是 中文 A 股财报类 PDF,对其他类型文档也能工作,但效果可能不如财报场景
- 使用
unstructured/detectron2等高级特性时,对环境和依赖版本有一定要求,推荐先在小规模样本上测试 - PDF 质量本身(扫描清晰度、排版混乱程度)会直接影响解析效果
本项目的开发者并非会计专业出身,所有财务相关理解主要来自个人投资过程中的阅读和自学(包括但不限于上述书籍和公开资料)。
代码和文档难免存在理解偏差或不严谨之处,如你在使用中发现任何错误或有更好的口径建议,非常欢迎指正:
- 邮件:
souflex@163.com - 或在仓库中提交 issue
你的反馈会直接帮助这套工具变得更可靠,在此提前致谢。