Files
AIclinicalresearch/docs/02-通用能力层/02-文档处理引擎/04-PDF表格提取引擎使用指南.md
HaHafeng dc6b292308 docs(asl): Complete Tool 3 extraction workbench V2.0 development plan (v1.5)
ASL Tool 3 Development Plan:
- Architecture blueprint v1.5 (6 rounds of architecture review, 13 red lines)
- M1/M2/M3 sprint checklists (Skeleton Pipeline / HITL Workbench / Dynamic Template Engine)
- Code patterns cookbook (9 chapters: Fan-out, Prompt engineering, ACL, SSE dual-track, etc.)
- Key patterns: Fan-out with Last Child Wins, Optimistic Locking, teamConcurrency throttling
- PKB ACL integration (anti-corruption layer), MinerU Cache-Aside, NOTIFY/LISTEN cross-pod SSE
- Data consistency snapshot for long-running extraction tasks

Platform capability:
- Add distributed Fan-out task pattern development guide (7 patterns + 10 anti-patterns)
- Add system-level async architecture risk analysis blueprint
- Add PDF table extraction engine design and usage guide (MinerU integration)
- Add table extraction source code (TableExtractionManager + MinerU engine)

Documentation updates:
- Update ASL module status with Tool 3 V2.0 plan readiness
- Update system status document (v6.2) with latest milestones
- Add V2.0 product requirements, prototypes, and data dictionary specs
- Add architecture review documents (4 rounds of review feedback)
- Add test PDF files for extraction validation

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 22:49:16 +08:00

472 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PDF 表格提取引擎使用指南
> **文档版本**: v1.0
> **最后更新**: 2026-02-23
> **状态**: ✅ 已测试通过MinerU 引擎)
> **目标读者**: 业务模块开发者ASL 全文复筛、系统综述数据提取等)
> **前置条件**: `backend/.env` 中已配置 `MINERU_API_TOKEN`
---
## 快速开始
### 5 秒上手
```typescript
import { getTableExtractionManager } from '../common/document/tableExtraction/index.js';
const manager = getTableExtractionManager();
const result = await manager.extractTables(pdfBuffer, 'paper.pdf');
for (const table of result.tables) {
console.log(`${table.title}: ${table.rows.length}× ${table.headers.length}`);
}
```
### 完整调用示例
```typescript
import fs from 'fs';
import { getTableExtractionManager } from '../common/document/tableExtraction/index.js';
const manager = getTableExtractionManager();
// 读取 PDF 文件
const pdf = fs.readFileSync('/path/to/medical-paper.pdf');
// 提取表格(自动使用默认引擎 MinerU
const result = await manager.extractTables(pdf, 'medical-paper.pdf', {
keepRaw: true, // 保留原始 Markdown
});
console.log(`引擎: ${result.engine}`); // "mineru"
console.log(`耗时: ${result.duration}ms`); // ~6000-20000ms
console.log(`表格数: ${result.tables.length}`);
// 遍历每个表格
for (const table of result.tables) {
console.log(`\n[${table.title}]`);
console.log(` 列: ${table.headers.join(' | ')}`);
console.log(` 行数: ${table.rows.length}`);
console.log(` 合并单元格: ${table.mergedCells.length}`);
// 访问具体数据
for (const row of table.rows) {
// row 是 string[],与 headers 一一对应
console.log(` ${row.join(' | ')}`);
}
// 原始 HTML可直接渲染到前端
if (table.rawHtml) {
console.log(` [HTML] ${table.rawHtml.substring(0, 100)}...`);
}
}
```
---
## 核心概念
### 架构设计
```
┌────────────────────────────────────────────────────┐
│ 业务代码ASL / 系统综述 / Meta 分析) │
│ │
│ manager.extractTables(pdf, filename) │
│ → 返回 ExtractedTable[] │
└──────────────────────┬─────────────────────────────┘
┌──────────────────────▼─────────────────────────────┐
│ TableExtractionManager (统一入口) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ MinerU (VLM) │ │ Qwen-VL │ │ Paddle │ │
│ │ ✅ 已接入 │ │ 📋 待接入 │ │ 📋 待接入 │ │
│ └──────────────┘ └──────────────┘ └──────────┘ │
└────────────────────────────────────────────────────┘
```
**核心原则:使用者不需要关心底层引擎。** 提交 PDF → 获取结构化表格。
### 数据结构
```typescript
// 提取结果
interface ExtractionResult {
tables: ExtractedTable[]; // 表格列表
engine: string; // 使用的引擎名
duration: number; // 耗时 (ms)
pageCount?: number; // PDF 页数
fullMarkdown?: string; // 完整 Markdown (需 keepRaw: true)
}
// 单个表格
interface ExtractedTable {
title: string; // "Table 1 Baseline characteristics"
headers: string[]; // 表头列名
rows: string[][]; // 数据行(二维数组)
mergedCells: MergedCell[]; // 合并单元格
footnotes: string[]; // 脚注
pageNumber?: number; // 页码
rawHtml?: string; // 原始 HTML
rawMarkdown?: string; // 原始 Markdown
}
// 合并单元格
interface MergedCell {
row: number; // 起始行 (0-based)
col: number; // 起始列 (0-based)
rowSpan: number;
colSpan: number;
}
```
---
## API 参考
### `getTableExtractionManager()`
获取全局管理器单例。首次调用时自动注册 MinerU 引擎。
```typescript
import { getTableExtractionManager } from '../common/document/tableExtraction/index.js';
const manager = getTableExtractionManager();
```
### `manager.extractTables(pdf, filename, options?)`
提取 PDF 中的表格。
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `pdf` | `Buffer` | ✅ | PDF 文件内容 |
| `filename` | `string` | ✅ | 文件名(含 .pdf 后缀) |
| `options.language` | `'zh' \| 'en' \| 'auto'` | ❌ | 语言提示 |
| `options.pages` | `number[]` | ❌ | 指定页码 |
| `options.keepRaw` | `boolean` | ❌ | 保留原始 Markdown |
| `options.engine` | `EngineType` | ❌ | 覆盖默认引擎 |
返回:`Promise<ExtractionResult>`
### `manager.availableEngines()`
返回已注册的引擎名称列表。
```typescript
console.log(manager.availableEngines()); // ['mineru']
```
### `manager.getEngine(name?)`
获取指定引擎实例。
### `manager.setDefault(name)`
切换默认引擎。
---
## 实战场景
### 场景 1ASL 全文复筛 — 提取基线特征表
```typescript
import { getTableExtractionManager } from '../common/document/tableExtraction/index.js';
async function extractBaselineTable(pdfBuffer: Buffer, filename: string) {
const manager = getTableExtractionManager();
const result = await manager.extractTables(pdfBuffer, filename);
// 找到 "Table 1" 或包含 "Baseline" 的表格
const baseline = result.tables.find(
(t) =>
/table\s*1\b/i.test(t.title) ||
/baseline/i.test(t.title),
);
if (baseline) {
return {
title: baseline.title,
columns: baseline.headers,
data: baseline.rows,
hasMergedCells: baseline.mergedCells.length > 0,
};
}
return null;
}
```
### 场景 2系统综述 — 提取所有表格为 JSON
```typescript
async function extractAllTablesAsJson(pdfBuffer: Buffer, filename: string) {
const manager = getTableExtractionManager();
const result = await manager.extractTables(pdfBuffer, filename);
return result.tables.map((table) => ({
title: table.title,
headers: table.headers,
rows: table.rows.map((row) => {
const obj: Record<string, string> = {};
table.headers.forEach((h, i) => {
obj[h] = row[i] || '';
});
return obj;
}),
}));
}
// 输出示例:
// [
// {
// title: "Table 1 Baseline characteristics",
// headers: ["", "", "EGb 761®(N=200)", "Placebo(N=202)", "p-value"],
// rows: [
// { "": "Sex female", "": "", "EGb 761®(N=200)": "139 (69.5)", ... },
// ...
// ]
// }
// ]
```
### 场景 3Meta 分析 — 提取效应值
```typescript
async function extractEffectSizes(pdfBuffer: Buffer, filename: string) {
const manager = getTableExtractionManager();
const result = await manager.extractTables(pdfBuffer, filename);
// 找结局指标表
const outcomeTable = result.tables.find(
(t) => /outcome|result|efficacy|effect/i.test(t.title),
);
if (!outcomeTable) return [];
return outcomeTable.rows.map((row) => ({
measure: row[0],
treatment: row[1],
control: row[2],
pValue: row[3],
}));
}
```
### 场景 4在 API 路由中使用
```typescript
import { getTableExtractionManager } from '../../../common/document/tableExtraction/index.js';
async function handleTableExtraction(request: FastifyRequest, reply: FastifyReply) {
const data = await request.file();
if (!data) return reply.status(400).send({ error: 'No file uploaded' });
const buffer = await data.toBuffer();
const manager = getTableExtractionManager();
const result = await manager.extractTables(buffer, data.filename);
return reply.send({
success: true,
engine: result.engine,
duration: result.duration,
tables: result.tables.map((t) => ({
title: t.title,
headers: t.headers,
rowCount: t.rows.length,
rows: t.rows,
mergedCells: t.mergedCells,
})),
});
}
```
---
## 环境配置
### 必需环境变量
```bash
# backend/.env
# MinerU Cloud API必需
MINERU_API_TOKEN=your_mineru_api_token
MINERU_API_BASE=https://mineru.net/api/v4
MINERU_MODEL_VERSION=vlm
```
### 获取 MinerU Token
1. 注册 [OpenDataLab](https://sso.openxlab.org.cn/login)
2. 登录 [MinerU 控制台](https://mineru.net/)
3. 个人中心 → API Token → 复制
4. 写入 `backend/.env``MINERU_API_TOKEN`
### 免费额度
| 项目 | 限制 |
|------|------|
| 日解析页数 | 2000 页 |
| 单文件大小 | ≤ 200 MB |
| 单文件页数 | ≤ 600 页 |
小型综述 20 篇 (200 页) → 1 天免费完成。大型综述 500 篇 (5000 页) → 分 3 天免费完成。
---
## 运行测试
```bash
cd backend
# 测试指定 PDF推荐
npx tsx src/tests/test-table-extraction.ts "../docs/03-业务模块/ASL-AI智能文献/05-测试文档/PDF/Herrschaft 2012.pdf"
# 自动选取测试目录中的第一个 PDF
npx tsx src/tests/test-table-extraction.ts
```
### 期望输出
```
========================================
PDF 表格提取引擎 — 集成测试
========================================
文件: Herrschaft 2012.pdf
引擎: mineru
耗时: 6.5s
检出表格: 3 个
────────────────────────────────────────
表格 1: Table 1 Baseline characteristics...
列数: 5
行数: 18
合并单元格: 2
表头: ... | EGb 761®(N = 200) | Placebo(N = 202) | p-value
表格 2: Table 2
列数: 4
行数: 10
表格 3: Table 3 Adverse events...
列数: 6
行数: 7
合并单元格: 4
测试通过
```
---
## 文件清单
```
backend/src/common/document/tableExtraction/
├── types.ts # 统一接口 + 类型定义
├── htmlTableParser.ts # HTML <table> → ExtractedTable 解析器
├── TableExtractionManager.ts # 引擎管理器(使用者入口)
├── engines/
│ └── MinerUEngine.ts # MinerU Cloud API 适配器
└── index.ts # 统一导出 + 全局单例
backend/src/tests/
└── test-table-extraction.ts # 集成测试脚本
```
---
## 扩展新引擎
添加新引擎只需 3 步:
### Step 1: 实现接口
```typescript
// engines/Qwen3VLEngine.ts
import type { ITableExtractionEngine, ExtractionOptions, ExtractionResult } from '../types.js';
export class Qwen3VLEngine implements ITableExtractionEngine {
readonly name = 'qwen3vl';
readonly displayName = 'Qwen3-VL 多模态';
async extractTables(
pdf: Buffer,
filename: string,
options?: ExtractionOptions,
): Promise<ExtractionResult> {
// 实现提取逻辑 ...
}
}
```
### Step 2: 注册引擎
```typescript
// index.ts 中添加
import { Qwen3VLEngine } from './engines/Qwen3VLEngine.js';
// 在 getTableExtractionManager() 中
if (process.env.QWEN3VL_API_KEY) {
_instance.register(new Qwen3VLEngine());
}
```
### Step 3: 使用
```typescript
const manager = getTableExtractionManager();
// 显式指定引擎
const result = await manager.extractTables(pdf, 'paper.pdf', {
engine: 'qwen3vl',
});
// 或切换默认引擎
manager.setDefault('qwen3vl');
```
---
## 常见问题
### Q: 提取耗时多久?
MinerU Cloud API 通常 5-20 秒(取决于 PDF 页数和云端负载)。首次请求可能较慢(云端冷启动),后续请求更快。
### Q: 没有检出表格?
1. 确认 PDF 中确实包含表格(扫描件图片中的表格也能识别)
2. 检查 `fullMarkdown` 输出中是否有 `<table>` 标签
3. MinerU 对极端复杂的嵌套表格可能识别不完整
### Q: 合并单元格数据如何处理?
`ExtractedTable.mergedCells` 记录了所有合并单元格的位置和跨度。在 `rows` 中,被合并的单元格只在起始位置有值,其余位置为空字符串。
### Q: 和文档处理引擎 (pymupdf4llm) 的关系?
两者分别负责不同场景:
| 引擎 | 路径 | 场景 |
|------|------|------|
| 文档处理引擎 | `ExtractionClient.ts` | 全文文本提取标题摘要初筛、PKB 入库) |
| **PDF 表格提取引擎** | `tableExtraction/` | 结构化表格提取全文复筛、Meta 分析) |
---
## 相关文档
- [PDF 表格提取引擎设计方案](./03-PDF表格提取引擎设计方案.md) — 架构设计 + 候选引擎 + 对比测试
- [文档处理引擎使用指南](./02-文档处理引擎使用指南.md) — 全文文本提取 (pymupdf4llm)
- [文档处理引擎 README](./README.md) — 引擎总览
---
**维护人**: 技术架构师
**核心依赖**: `adm-zip` (ZIP 解析), `axios` (HTTP 请求)