# PDF 表格提取引擎设计方案 > **文档版本**: v1.0 > **创建日期**: 2026-02-23 > **最后更新**: 2026-02-23 > **文档目的**: 定义 PDF 表格提取引擎的统一架构,为系统综述/Meta 分析等场景提供精确的结构化表格数据 > **核心原则**: 引擎对使用者透明 — 提交 PDF,返回结构化表格,无需关心底层实现 > **当前状态**: MinerU Cloud API (VLM) 已接入并完成测试,其他引擎待逐步评测 --- ## 1. 业务背景 ### 1.1 核心需求 ASL 智能文献模块的**全文复筛**环节,需要从医学 PDF 文献中精确提取数据表格: - **系统综述 (Systematic Review)**: 基线特征表、结局指标表、不良事件表 - **Meta 分析**: 效应值、置信区间、样本量等关键数值 - **数据核验**: 数值必须与原文 100% 一致,不容许任何精度损失 ### 1.2 为什么独立建设 当前文档处理引擎基于 `pymupdf4llm`,定位是 **PDF → Markdown 全文文本转换**,在表格提取场景中存在严重缺陷: | 问题 | 实测数据 | |------|----------| | 8 篇 PDF 仅 1 篇输出结构化表格 | 表格检出率 12.5% | | 其余 7 篇表格退化为纯文本 | 行列结构完全丢失 | | 不支持合并单元格 | 医学表格大量使用 rowspan/colspan | **结论:全文文本提取和结构化表格提取是两个不同的能力,需要分别建设。** --- ## 2. 引擎架构设计 ### 2.1 核心理念 > **使用者不需要关心底层用了什么技术,只需要:提交 PDF → 获取结构化表格。** 底层引擎可以是 MinerU、Qwen-VL、PaddleOCR、Docling 或任意其他方案,通过统一接口抽象,实现热切换和渐进升级。 ### 2.2 统一架构 ``` ┌─────────────────────────────────────────────────────────────┐ │ 业务层 (使用者) │ │ ASL 全文复筛 / 系统综述数据提取 / Meta 分析 │ │ │ │ const tables = await tableEngine.extract(pdfBuffer); │ │ // 只关心输入 PDF 和输出 tables,不关心底层引擎 │ └───────────────────────────┬─────────────────────────────────┘ │ ┌───────────────────────────▼─────────────────────────────────┐ │ PDF 表格提取引擎 (统一抽象层) │ │ │ │ interface TableExtractionEngine { │ │ extract(pdf: Buffer): Promise │ │ extractFromUrl(url: string): Promise │ │ } │ │ │ │ 统一输出:ExtractedTable[] │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ { title, headers, rows, mergedCells, footnotes, │ │ │ │ pageNumber, confidence, rawHtml } │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ ├─────────────────────────────────────────────────────────────┤ │ 引擎适配器 (可插拔) │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ MinerU │ │ Qwen3-VL │ │ PaddleOCR-VL │ │ │ │ Cloud API │ │ 多模态 LLM │ │ 百度 OCR │ │ │ │ (VLM) │ │ │ │ │ │ │ │ ✅ 已接入 │ │ 📋 待评测 │ │ 📋 待评测 │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Qwen-OCR + │ │ Docling │ │ DeepSeek │ │ │ │ Qwen-Long │ │ (IBM) │ │ LLM │ │ │ │ │ │ │ │ │ │ │ │ 📋 待评测 │ │ 📋 待评测 │ │ ✅ 已测试 │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### 2.3 统一输出格式 无论底层使用哪个引擎,输出都遵循统一的 `ExtractedTable` 结构: ```typescript interface ExtractedTable { /** 表格标题 (如 "Table 1 Baseline characteristics") */ title: string; /** 表头行 */ headers: string[]; /** 数据行 (二维数组) */ rows: string[][]; /** 合并单元格信息 */ mergedCells?: MergedCell[]; /** 脚注 */ footnotes?: string[]; /** 所在 PDF 页码 */ pageNumber?: number; /** 引擎自信度 (0-1) */ confidence?: number; /** 原始 HTML (供前端渲染或调试) */ rawHtml?: string; /** 原始 Markdown (备选格式) */ rawMarkdown?: string; } interface MergedCell { row: number; col: number; rowSpan: number; colSpan: number; } ``` --- ## 3. 候选引擎全景 ### 3.1 引擎候选清单 | 引擎 | 类型 | 特点 | 成本 | 状态 | |------|------|------|------|------| | **MinerU Cloud API** | VLM 云端 | 表格结构最完整,rowspan/colspan 支持 | 2000 页/天免费 | ✅ 已接入 | | **Qwen3-VL** | 多模态 LLM | 多模态理解最强,复杂表格语义识别好 | 按 token 计费 | 📋 待评测 | | **Qwen-OCR + Qwen-Long** | OCR + LLM 组合 | 成本最低、功能最全的组合方案 | 极低 | 📋 待评测 | | **百度 PaddleOCR-VL 1.5** | VL OCR | 医学场景案例多,准确率高,免费额度最多 | 官方免费额度多 | 📋 待评测 | | **Docling (IBM)** | 本地部署 | MIT 开源,TableFormer 模型,可完全离线 | 免费 (本地部署) | 📋 待评测 | | **DeepSeek LLM** | 文本 LLM | 从原始文本重构表格,Markdown 输出 | ~0.14 元/万 token | ✅ 已测试 | ### 3.2 推荐分类 **最佳性价比组合:** 1. **Qwen-OCR + Qwen-Long** — 成本最低,功能最全 2. **百度 PaddleOCR-VL** — 官方免费额度最多,技术最成熟 **医学文献表格提取最佳选择:** 1. **Qwen3-VL** — 多模态理解最强,支持复杂表格 2. **百度 PaddleOCR-VL 1.5** — 医学场景案例多,准确率高 **数据合规 / 离线场景:** 1. **Docling (IBM)** — MIT 开源,完全本地部署 ### 3.3 评测计划 按优先级逐步评测,使用同一组 8 篇医学 PDF 文献作为基准: | 阶段 | 引擎 | 优先级 | 评测重点 | |------|------|--------|----------| | ✅ 已完成 | MinerU Cloud API | — | 作为 baseline | | ✅ 已完成 | DeepSeek LLM | — | 文本 LLM 方案的上限 | | P1 待测 | Qwen3-VL | 高 | 多模态 vs MinerU VLM 的表格精度 | | P1 待测 | PaddleOCR-VL 1.5 | 高 | 免费额度 + 医学场景准确率 | | P2 待测 | Qwen-OCR + Qwen-Long | 中 | 验证最低成本方案的可行性 | | P2 待测 | Docling | 中 | 离线方案,评估部署成本 | --- ## 4. 已完成测试:MinerU vs pymupdf4llm vs DeepSeek ### 4.1 测试概要 - **测试对象**: 8 篇真实医学 PDF 文献(含 1 篇中文),涵盖 RCT、队列研究 - **测试方法**: pymupdf4llm (本地) / MinerU Cloud API (VLM) / DeepSeek LLM (deepseek-chat) ### 4.2 核心结果 | 指标 | pymupdf4llm | MinerU API (VLM) | DeepSeek LLM | |------|-------------|------------------|--------------| | 结构化表格检出 | 3 个 (12.5%) | **28 个 (100%)** | 24 个 (85%) | | 输出格式 | 纯文本 | **HTML ``** | Markdown `\|..\|` | | 合并单元格 | ❌ | **✅ rowspan/colspan** | ⚠️ 文字描述 | | 数值精度 | ✅ 原始 | **✅ 100% 保真** | ⚠️ 可能翻译 | | 总耗时 (8 篇) | 16.1s | ~50s | 234.6s | | 综合评分 | 2.7/5 | **4.6/5** | 3.4/5 | ### 4.3 逐文件对比 | # | 文件 | pymupdf4llm | MinerU API | DeepSeek LLM | |---|------|-------------|------------|--------------| | 1 | S2589537025 (EClinMed) | 0 表格 | **1 HTML** | 1 MD | | 2 | Dongen 2003 | 0 结构化 | **4 HTML** | 3 MD | | 3 | Ginkgo+Donepezil | 0 结构化 | **3 HTML** | 3 MD | | 4 | Ginkgo Community | 0 结构化 | **6 HTML** | 6 MD | | 5 | Ginkgo NPS | 3 MD | **3 HTML** | 3 MD | | 6 | Herrschaft 2012 | 0 结构化 | **3 HTML** | 3 MD | | 7 | Ihl 2011 | 0 结构化 | **3 HTML** | 3 MD | | 8 | NIRS 队列研究 (中文) | 0 结构化 | **5 HTML** | 2 MD | ### 4.4 质量深度分析 (Herrschaft 2012 — Table 1) 原始表格: 5 列、18 行,"Type of dementia" 合并 3 行。 | 特征 | pymupdf4llm | MinerU API | DeepSeek LLM | |------|-------------|------------|--------------| | 列数正确 | ❌ 无结构 | **✅ 5 列** | ✅ 4 列 | | 行数完整 | ✅ 数据在 | **✅ 18 行** | ✅ 18 行 | | 合并单元格 | ❌ | **✅ rowspan=3** | ⚠️ 加粗标注 | | 数值保真 | ✅ | **✅ 含 ±** | ⚠️ 翻译行名 | ### 4.5 综合评分 | 维度 | pymupdf4llm | MinerU API | DeepSeek LLM | |------|:-----------:|:----------:|:------------:| | 表格检测率 | 1/5 | **5/5** | 4/5 | | 结构保真度 | 1/5 | **5/5** | 4/5 | | 数值精度 | 5/5 | **5/5** | 4/5 | | 速度 | 5/5 | 3/5 | 2/5 | | 合并单元格 | 1/5 | **5/5** | 3/5 | | 中文支持 | 3/5 | **5/5** | 4/5 | | 成本 | 5/5 | 4/5 | 3/5 | | **综合** | **2.7** | **4.6** | **3.4** | --- ## 5. 技术实现设计 ### 5.1 接口抽象 ```typescript // common/document/tableExtraction/types.ts /** 统一引擎接口 — 所有适配器必须实现 */ interface ITableExtractionEngine { readonly name: string; extract(pdf: Buffer, options?: ExtractionOptions): Promise; } interface ExtractionOptions { language?: 'ch' | 'en' | 'auto'; /** 指定页码范围,如 "1-5,8" */ pageRanges?: string; /** 是否启用公式识别 */ enableFormula?: boolean; } interface ExtractionResult { tables: ExtractedTable[]; /** 引擎名称 */ engine: string; /** 处理耗时 (ms) */ duration: number; /** PDF 总页数 */ pageCount: number; /** 原始 Markdown 全文 (可选) */ fullMarkdown?: string; } ``` ### 5.2 引擎管理器 ```typescript // common/document/tableExtraction/engineManager.ts class TableExtractionEngineManager { private engines: Map = new Map(); private defaultEngine: string = 'mineru'; /** 注册引擎适配器 */ register(engine: ITableExtractionEngine): void { this.engines.set(engine.name, engine); } /** 设置默认引擎 */ setDefault(name: string): void { this.defaultEngine = name; } /** 提取表格 — 使用者唯一入口 */ async extract( pdf: Buffer, options?: ExtractionOptions & { engine?: string } ): Promise { const engineName = options?.engine || this.defaultEngine; const engine = this.engines.get(engineName); if (!engine) throw new Error(`Engine not found: ${engineName}`); return engine.extract(pdf, options); } } ``` ### 5.3 MinerU 适配器 (第一个实现) ```typescript // common/document/tableExtraction/engines/mineruEngine.ts class MinerUEngine implements ITableExtractionEngine { readonly name = 'mineru'; async extract(pdf: Buffer, options?: ExtractionOptions): Promise { // 1. 请求上传 URL // 2. 上传 PDF // 3. 轮询等待解析完成 // 4. 下载结果 ZIP // 5. 解析 HTML 表格 → ExtractedTable[] // ... } } ``` ### 5.4 未来适配器 (预留接口) ```typescript // 后续逐步实现 class Qwen3VLEngine implements ITableExtractionEngine { ... } class PaddleOCRVLEngine implements ITableExtractionEngine { ... } class QwenOCRLongEngine implements ITableExtractionEngine { ... } class DoclingEngine implements ITableExtractionEngine { ... } ``` ### 5.5 文件规划 ``` backend/src/common/document/tableExtraction/ ├── types.ts # 统一类型定义 ├── engineManager.ts # 引擎管理器 (统一入口) ├── htmlTableParser.ts # HTML
→ ExtractedTable 转换 ├── engines/ │ ├── mineruEngine.ts # MinerU Cloud API 适配器 ✅ 首个实现 │ ├── qwen3vlEngine.ts # Qwen3-VL 适配器 (待实现) │ ├── paddleOcrEngine.ts # PaddleOCR-VL 适配器 (待实现) │ ├── qwenOcrLongEngine.ts # Qwen-OCR + Qwen-Long 适配器 (待实现) │ ├── doclingEngine.ts # Docling 适配器 (待实现) │ └── deepseekEngine.ts # DeepSeek LLM 适配器 (已测试,可选) └── index.ts # 导出统一入口 ``` --- ## 6. 使用方式 ### 6.1 业务层调用 (使用者视角) ```typescript import { getTableExtractionEngine } from '@/common/document/tableExtraction'; // 使用者不需要知道底层是 MinerU 还是 Qwen-VL const engine = getTableExtractionEngine(); const result = await engine.extract(pdfBuffer, { language: 'auto' }); for (const table of result.tables) { console.log(`${table.title}: ${table.rows.length} 行 × ${table.headers.length} 列`); // 直接使用结构化数据 } ``` ### 6.2 管理员切换引擎 ```bash # backend/.env — 切换默认引擎 TABLE_EXTRACTION_ENGINE=mineru # 当前默认 # TABLE_EXTRACTION_ENGINE=qwen3vl # 未来切换 # TABLE_EXTRACTION_ENGINE=paddle # 未来切换 # MinerU 配置 MINERU_API_TOKEN=your_token MINERU_API_BASE=https://mineru.net/api/v4 MINERU_MODEL_VERSION=vlm ``` ### 6.3 场景决策矩阵 | 场景 | 推荐引擎 | 说明 | |------|----------|------| | ASL 标题摘要初筛 | pymupdf4llm (文本引擎) | 不需要表格,只需全文文本 | | ASL 全文复筛 — 表格提取 | **PDF 表格提取引擎** | 自动选择最优引擎 | | 系统综述数据提取 | **PDF 表格提取引擎** | 需要精确数值表格 | | Meta 分析效应值识别 | 表格引擎 + LLM 语义理解 | 提取 → 理解两步走 | | PKB 知识库入库 | pymupdf4llm (文本引擎) | 只需 Markdown 文本 | --- ## 7. MinerU Cloud API 接入指南 (当前默认引擎) ### 7.1 API 概览 | 项目 | 说明 | |------|------| | 服务商 | OpenDataLab (上海人工智能实验室) | | API 地址 | `https://mineru.net/api/v4` | | 认证方式 | Bearer Token | | 模型版本 | `vlm` (视觉语言模型,推荐) | | 免费额度 | 2000 页/天 | | 文件限制 | 单文件 ≤ 200MB,≤ 600 页 | ### 7.2 核心流程 ``` PDF 文件 │ ▼ Step 1: POST /file-urls/batch → 获取预签名上传 URL + batch_id │ ▼ Step 2: PUT {pre-signed URL} → 上传 PDF 文件 │ ▼ Step 3: 云端 VLM 模型自动解析 → 识别表格/文本/图片 │ ▼ Step 4: GET /extract-results/batch/{batch_id} → 轮询状态 │ ▼ Step 5: 下载结果 ZIP → 含 .md (内嵌 HTML 表格) + .json + images ``` ### 7.3 代码示例 ```python import requests, time, zipfile, io TOKEN = "your_token" API = "https://mineru.net/api/v4" headers = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"} # Step 1: 请求上传 URL resp = requests.post(f"{API}/file-urls/batch", headers=headers, json={ "files": [{"name": "paper.pdf", "data_id": "paper1"}], "enable_table": True, "model_version": "vlm", }) batch_id = resp.json()["data"]["batch_id"] upload_url = resp.json()["data"]["file_urls"][0] # Step 2: 上传文件 with open("paper.pdf", "rb") as f: requests.put(upload_url, data=f) # Step 3-4: 轮询等待 while True: time.sleep(10) r = requests.get(f"{API}/extract-results/batch/{batch_id}", headers=headers) results = r.json()["data"]["extract_result"] if all(x["state"] in ("done", "failed") for x in results): break # Step 5: 下载解析 for result in results: if result["state"] == "done": zr = requests.get(result["full_zip_url"]) with zipfile.ZipFile(io.BytesIO(zr.content)) as zf: for name in zf.namelist(): if name.endswith('.md'): md = zf.read(name).decode('utf-8') # md 中包含 HTML
格式的表格 ``` ### 7.4 输出格式 MinerU 的表格以 HTML `
` 嵌入 Markdown 中,完整保留合并单元格: ```html
Type of dementiaProbable AD107 (54)
Possible AD with CVD73 (36)
Probable VaD20 (10)
``` --- ## 8. 成本估算 ### 8.1 MinerU (当前) | 场景 | 文献数 | 平均页数 | 总页数 | 天数 | 费用 | |------|--------|----------|--------|------|------| | 小型综述 | 20 篇 | 10 页 | 200 页 | 1 天 | 免费 | | 中型综述 | 100 篇 | 10 页 | 1000 页 | 1 天 | 免费 | | 大型综述 | 500 篇 | 10 页 | 5000 页 | 3 天 | 免费 | ### 8.2 各引擎预估成本对比 | 引擎 | 免费额度 | 超出后单价 | 500 篇 (5000 页) 预估 | |------|----------|-----------|----------------------| | MinerU | 2000 页/天 | 待确认 | 免费 (分 3 天) | | Qwen-OCR + Qwen-Long | 按 token | ~0.004 元/千 token | 约 10-20 元 | | PaddleOCR-VL | 官方免费额度多 | 极低 | 接近免费 | | Qwen3-VL | 按 token | ~0.02 元/千 token | 约 50-100 元 | | Docling | 本地部署 | 仅算力成本 | 免费 | | DeepSeek LLM | 按 token | ~0.14 元/万 token | 约 30-50 元 | --- ## 9. 测试脚本 ### 9.1 已有脚本 | 脚本 | 路径 | 功能 | |------|------|------| | 三方对比测试 | `extraction_service/test_pdf_table_extraction.py` | pymupdf4llm / MinerU / DeepSeek 完整对比 | | 结果分析 | `extraction_service/analyze_table_results.py` | 从提取结果生成对比报告 | ### 9.2 运行方法 ```bash cd AIclinicalresearch # 运行全部三个方法 python extraction_service/test_pdf_table_extraction.py # 单独运行某个方法 python extraction_service/test_pdf_table_extraction.py pymupdf python extraction_service/test_pdf_table_extraction.py mineru python extraction_service/test_pdf_table_extraction.py deepseek # 生成对比报告 python extraction_service/analyze_table_results.py ``` ### 9.3 测试输出 ``` extraction_service/test_output/pdf_table_extraction/ ├── pymupdf4llm/ # pymupdf4llm 提取结果 ├── mineru/ # MinerU 提取结果 ├── deepseek/ # DeepSeek 提取结果 ├── raw_results.json # 原始测试数据 └── comparison_report.md # 综合对比报告 ``` ### 9.4 后续评测扩展 新引擎的评测脚本将遵循同样的结构,添加到 `test_pdf_table_extraction.py` 中: ```bash python extraction_service/test_pdf_table_extraction.py qwen3vl python extraction_service/test_pdf_table_extraction.py paddle python extraction_service/test_pdf_table_extraction.py qwenocr ``` --- ## 10. 路线图 ### Phase 1: 基础框架 + MinerU (当前) - [x] MinerU Cloud API 对比测试 - [x] DeepSeek LLM 对比测试 - [ ] 实现统一接口 `ITableExtractionEngine` - [ ] 实现 `MinerUEngine` 适配器 - [ ] 实现 `engineManager` 引擎管理器 - [ ] ASL 全文复筛集成 ### Phase 2: 多引擎评测 - [ ] Qwen3-VL 评测 + 适配器 - [ ] PaddleOCR-VL 1.5 评测 + 适配器 - [ ] 同一基准集横向对比报告 - [ ] 确定最优引擎组合策略 ### Phase 3: 性价比优化 - [ ] Qwen-OCR + Qwen-Long 评测 (最低成本方案) - [ ] Docling 本地部署评测 (离线方案) - [ ] 引擎路由策略 (按文档复杂度自动选择引擎) ### Phase 4: 生产加固 - [ ] 提取结果缓存 (避免重复解析) - [ ] 批量提取队列 (pg-boss 异步任务) - [ ] 质量监控 (空表格/异常值检测) - [ ] 引擎降级策略 (主引擎不可用时自动切换) --- ## 11. 相关文档 - [文档处理引擎 README](./README.md) — 引擎总览 (含全文文本提取) - [文档处理引擎设计方案 V1](./01-文档处理引擎设计方案.md) — pymupdf4llm 全文文本架构 - [文档处理引擎使用指南](./02-文档处理引擎使用指南.md) — 现有 API 调用指南 - [MinerU 官方文档](https://mineru.net/doc/docs/index_en/) — MinerU Cloud API 在线文档 - [对比测试报告](../../../extraction_service/test_output/pdf_table_extraction/comparison_report.md) — 完整测试数据 --- **维护人**: 技术架构师 **设计原则**: 引擎对使用者透明,底层可热切换,以测试数据驱动选型