# PDF 解析双引擎方案：本地 PaddleOCR + 远程 MinerU

## 目标
给 `ocr_extract.py` 增加 `--engine` 参数，支持在本地 PaddleOCR（原方案）和远程 MinerU（高精度云端解析）之间切换，两种引擎输出统一 JSON 格式供下游消费。

---

## 涉及文件（6 个文件变更）

| 文件 | 操作 | 说明 |
|---|---|---|
| `scripts/mineru_parser.py` | **新建** | MinerU 云端解析模块 |
| `scripts/ocr_extract.py` | **修改** | 增加 `--engine` / `--mineru-token` / `--mineru-mode` 参数，路由分发 |
| `scripts/requirements.txt` | **修改** | 增加 `langchain-mineru` + `python-dotenv`（可选依赖） |
| `scripts/deploy.md` | **修改** | 增加 MinerU 部署配置章节 |
| `SKILL.md` | **修改** | 阶段 2 补充 `--engine` 用法说明 |
| `.env.example` | **新建** | MinerU 配置模板 |
| `.gitignore` | **修改** | 增加 `mineru_cache/` |

---

## 详细设计

### 1. `scripts/mineru_parser.py`（新建，约 180 行）

**配置加载**（优先级：CLI arg > 环境变量 > `.env` 文件）：
- `MINERU_TOKEN`：MinerU API 令牌
- `MINERU_MODE`：解析模式，默认 `"precision"`
- `MINERU_MAX_PAGES`：单次 API 最大页数，默认 20
- `MINERU_CACHE_DIR`：缓存目录，默认 `./mineru_cache/`

**核心函数**：
```
get_file_hash(file_path) → md5 hex
_cache_path_for(file_hash) → Path
_read_cache(path) / _write_cache(path, content)
count_pdf_pages(file_path) → int
clean_mineru_text(markdown_text) → str
sanitize_text(text) → str
mineru_pdf(pdf_path, out_dir, pages_spec, token, mode) → (result_dict, json_path)
```

**`mineru_pdf()` 流程**：
1. 检查缓存（基于 PDF 的 MD5 hash）
2. 命中缓存 → 直接返回
3. 未命中 → 分片调 `MinerULoader(source, mode, token, pages="start-end")`
4. 每页 `doc.page_content` 过 `clean_mineru_text()` 清洗
5. 拼接全文 → 写缓存
6. 输出统一 JSON，`type: "mineru"`, `avg_confidence: 0.98`

**输出 JSON 格式**（与 PaddleOCR 兼容）：
```json
{
  "source": "/path/to/file.pdf",
  "engine": "mineru",
  "total_pages": 15,
  "from_cache": false,
  "pages": [
    {
      "page": 1,
      "type": "mineru",
      "text": "## 配风计划表\n| 地点 | 风量 |\n...",
      "avg_confidence": 0.98,
      "item_count": 0,
      "items": []
    }
  ]
}
```

**缓存策略**：
- 缓存 key = PDF 文件 MD5（文件内容不变则命中缓存）
- 缓存内容 = 清洗后的全文 markdown
- 缓存目录 `mineru_cache/`（`.gitignore` 排除）

---

### 2. `scripts/ocr_extract.py`（修改）

**新增 CLI 参数**：
```
--engine {local,mineru}    解析引擎，默认 local
--mineru-token TOKEN       MinerU API Token（可选，优先级最高）
--mineru-mode {precision,fast}  MinerU 模式，默认 precision
```

**`main()` 路由逻辑**：
```python
if args.engine == "mineru":
    from mineru_parser import mineru_pdf
    result, json_path = mineru_pdf(
        args.pdf, args.out, args.pages,
        token=args.mineru_token, mode=args.mineru_mode
    )
else:
    result, json_path = ocr_pdf(args.pdf, args.out, args.dpi, args.pages)
```

**输出时标注 engine 字段**：
- `ocr_pdf()` 结果自动补 `"engine": "local"`

---

### 3. `scripts/requirements.txt`（修改）

新增（注释标为可选）：
```
# === MinerU 云端解析（可选，仅 --engine mineru 时需要）===
# pip install langchain-mineru python-dotenv
# langchain-mineru
# python-dotenv
```

---

### 4. `scripts/deploy.md`（修改）

新增章节"六、MinerU 云端解析（可选）"：
- 申请 Token 地址
- 安装依赖：`pip install langchain-mineru python-dotenv`
- 配置 `.env` 文件
- 用法示例
- 费用/速率限制说明
- 与本地 PaddleOCR 对比表（精度/速度/费用/离线能力）

---

### 5. `SKILL.md`（修改）

阶段 2 第 2 步增加 `--engine` 选项说明：

```markdown
**引擎选择**：
- `--engine local`（默认）：本地 PaddleOCR，无需网络，适合离线环境
- `--engine mineru`：MinerU 云端解析，精度更高，适合扫描质量差的 PDF
```

---

### 6. `.env.example`（新建）

```ini
# MinerU 云端解析配置（可选，仅 --engine mineru 时需要）
MINERU_TOKEN=your_token_here
MINERU_MODE=precision
MINERU_MAX_PAGES=20
MINERU_CACHE_DIR=./mineru_cache
```

---

### 7. `.gitignore`（修改）

新增两行：
```
# MinerU 缓存
mineru_cache/
.env
```

---

## 兼容性保证

| 关注点 | 措施 |
|---|---|
| **向下兼容** | `--engine` 默认 `local`，不传时行为完全不变 |
| **输出格式** | MinerU 输出 JSON 与 PaddleOCR 结构一致，`type: "mineru"` 区分 |
| **依赖隔离** | `langchain-mineru` 仅在 `--engine mineru` 时才 import，不用不装 |
| **容错** | MinerU 网络异常时明确报错，不静默失败；缓存损坏自动清除重建 |

---

## CLI 用法示例

```bash
# 本地 PaddleOCR（默认，与原行为一致）
python scripts/ocr_extract.py --pdf 配风计划.pdf --out output/ --pages 3,8,9

# MinerU 云端解析
python scripts/ocr_extract.py --pdf 配风计划.pdf --out output/ --engine mineru

# MinerU + 指定 Token + 快速模式
python scripts/ocr_extract.py --pdf 配风计划.pdf --out output/ --engine mineru --mineru-token xxx --mineru-mode fast
```