---
name: pdf-processor
description: 调用远程PDF解析API提取文字、表格、图片。用于处理PDF文件：(1) 提取PDF文字/表格/图片 (2) PDF转Markdown (3) 调用MinerU PDF解析服务 (4) 长文件完整逐字逐句解析，专业论文高度还原（排版、公式、表格、图等） (5) 大于50页自动分批处理 (6) 超时自动重试（最大3次）
---

# PDF Processor Skill

⚠️ **重要提示**：处理 PDF 文件时，**优先使用此 Skill**，不要直接用 curl 或其他方式调用 API，以确保：
1. 图片正确内嵌到 Markdown
2. 自动检测语言并询问是否翻译
3. 自动分批处理大文件

调用远程 MinerU API 解析 PDF，支持文字、表格、图片提取。

## 概述

- **API 地址**: `http://llm.bnuzh.edu.cn:8880/file_parse`
- **适用场景**: 论文解析、文档提取、长文本处理、专业排版还原

## 请求参数

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| files | file | ✅ | PDF 文件（支持多文件） |
| return_md | bool | ✅ | 返回 Markdown（设为 true） |
| return_images | bool | ✅ | 返回图片（必须设为 true），返回 base64 格式 |
| start_page_id | int | - | ⚠️ 起始页码，**从 0 开始**（第1页=0） |
| end_page_id | int | - | ⚠️ 结束页码，**从 0 开始** |

## 返回格式

### completed（同步完成）

```json
{
  "task_id": "970cf474-a5da-4e4f-97b3-37a57ad29833",
  "status": "completed",
  "results": {
    "文件名.pdf": {
      "md_content": "Markdown 内容...",
      "images": {
        "图片名.jpg": "data:image/jpeg;base64,/9j/4AAQ...",
        ...
      }
    }
  }
}
```

**注意**：图片数据在 `results[文件名]['images']` 字段中，格式为：
- Key: 图片文件名（如 `xxx.jpg`）
- Value: `data:image/jpeg;base64,xxx` 格式的 base64 编码

### pending（异步处理）

```json
{
  "task_id": "xxx",
  "status": "pending",
  "status_url": "http://llm.bnuzh.edu.cn:8880/tasks/xxx",
  "result_url": "http://llm.bnuzh.edu.cn:8880/tasks/xxx/result"
}
```

## 分批处理规则

### 何时分批

- PDF 页数 > 50 时建议分批
- 单批次建议 30-50 页

### 批次数计算

```
批次数 = ceil(总页数 / 每批页数)

例如 329 页，每批 50 页：
批次数 = ceil(329 / 50) = 7 批

第1批: 0-49   (50页)
第2批: 50-99  (50页)
第3批: 100-149
第4批: 150-199
第5批: 200-249
第6批: 250-299
第7批: 300-328 (29页)
```

## 使用方式

调用此 Skill 处理 PDF 文件，由脚本自动完成：
1. 图片正确内嵌到 Markdown
2. 自动检测语言并询问是否翻译
3. 自动分批处理大文件

具体使用方式请参考上述"命令行使用"部分。

## 图片处理模式

### 两种模式

| 模式 | 参数 | 说明 |
|------|------|------|
| **内嵌模式（默认）** | `-e` 或 `--embed` | 图片 base64 内嵌到 MD，不单独存储图片文件 |
| **独立模式** | `-s` 或 `--separate` | 图片保存到独立文件夹，需要 ZIP 打包 |

### 1. 内嵌模式（默认）

- 图片 base64 直接内嵌到 Markdown
- 输出单一 MD 文件
- 无需打包
- 便于复制、分享

```python
def embed_images_in_markdown(md_content, images_dict):
    """将图片 base64 内嵌到 Markdown 中"""
    if not images_dict:
        return md_content
    
    for img_name, img_data in images_dict.items():
        if not img_data.startswith('data:image/'):
            continue
        old_ref = f"images/{img_name}"
        if old_ref in md_content:
            md_content = md_content.replace(old_ref, img_data)
    return md_content
```

**输出格式**：
```markdown
![](data:image/jpeg;base64,/9j/4AAQSkZJRg...)
```

### 2. 独立模式（打包）

- 图片保存到独立文件夹 `{文件名}_images/`
- 需要使用 `-z` 参数打包
- 适用于需要保留原始图片文件的场景

```python
import base64, os

def extract_and_save_images(md_content, images_dict, output_dir="images"):
    """从 API 返回的 images 字典提取图片并保存"""
    if not images_dict:
        return md_content
    
    os.makedirs(output_dir, exist_ok=True)
    
    for img_name, img_data in images_dict.items():
        if not img_data.startswith('data:image/'):
            continue
        
        match = re.match(r'data:image/([^;]+);base64,(.+)', img_data)
        if not match:
            continue
        
        b64_data = match.group(2)
        filepath = os.path.join(output_dir, img_name)
        
        with open(filepath, 'wb') as f:
            f.write(base64.b64decode(b64_data))
        
        # 替换引用
        old_ref = f"images/{img_name}"
        new_ref = f"{output_dir}/{img_name}"
        md_content = md_content.replace(old_ref, new_ref)
    
    return md_content
```

**输出结构**：
```
document.md
document_images/
├── xxx.jpg
└── yyy.png
```

## 命令行使用

### 基本用法（默认内嵌模式）

```bash
# 内嵌模式（默认）- 不打包
python3 pdf_processor.py document.pdf

# 内嵌模式显式指定
python3 pdf_processor.py document.pdf -e
```

### 独立模式（打包）

```bash
# 独立模式 + 打包 ZIP
python3 pdf_processor.py document.pdf -s -z

# 独立模式不打包（只保存图片文件夹）
python3 pdf_processor.py document.pdf -s
```

### 其他参数

```bash
# 自动翻译为中文
python3 pdf_processor.py document.pdf -t

# 自动确认翻译
python3 pdf_processor.py document.pdf -y

# 组合：内嵌 + 翻译
python3 pdf_processor.py document.pdf -t -e

# 组合：独立模式 + 翻译 + 打包
python3 pdf_processor.py document.pdf -t -s -z
```

### 参数说明

| 参数 | 说明 |
|------|------|
| `input` | PDF 文件路径或 URL |
| `-o, --output` | 输出文件路径 |
| `-b, --batch-size` | 每批页数（默认50） |
| `-e, --embed` | 内嵌模式（默认）- 图片内嵌到 MD |
| `-s, --separate` | 独立模式 - 图片保存到文件夹 |
| `-z, --zip` | 打包为 ZIP（独立模式时使用） |
| `-t, --translate` | 自动翻译为中文 |
| `-y, --yes` | 自动确认翻译 |

## 工作流

### 内嵌模式流程（默认）

```
PDF → 解析 → 检测语言 → [英文?]询问翻译 → 内嵌图片到 MD → 输出 .md 文件
```

### 独立模式流程

```
PDF → 解析 → 检测语言 → [英文?]询问翻译 → 保存图片到文件夹 → 打包 ZIP → 输出 .zip 文件
```

### 完整工作流（带语言检测）

```
1. 读取 PDF 文件
      ↓
2. 判断是否分批 (页数 > 50?)
      ↓
3. 解析 PDF (API 调用)
      ↓
4. 检测语言 (中文字符 > 30% = 中文)
      ↓
   ┌──────────────┴──────────────┐
   ↓                             ↓
  中文                         英文/其他
   ↓                             ↓
直接保存                    询问用户"需要翻译吗？"
   ↓                        ┌────┴────┐
   ↓                        ↓         ↓
   ↓                     同意       不同意
   ↓                        ↓         ↓
   ↓                  翻译+保存     只保存原版
   ↓                        ↓         ↓
   └─────────────→ 输出 .md ←────────┘
```



## 语言检测与翻译

### ⚠️ 重要：必须先询问用户

**当检测到PDF是英文或其他外文时，必须先询问用户是否需要翻译，得到用户确认后才能执行翻译操作。**

❌ 错误做法：直接用 `-y` 参数执行翻译（跳过询问）
✅ 正确做法：先问用户"需要翻译成中文吗？"，等用户确认后再执行

### 处理流程

1. **解析完成后自动检测语言**（中文字符比例 > 30% 视为中文）
2. **根据语言类型处理**：
   - **中文** → 直接保存原语言版本
   - **英文/其他** → ⚠️ **必须先询问用户是否翻译** → 得到用户确认后执行翻译

### 命令行模式（非交互）

- **无参数**：检测到非中文会跳过翻译（不询问）
- **`-t`**：自动翻译为中文（不再询问）
- **`-y`**：自动确认翻译（等同于 `-t`）

### 输出文件

| 场景 | 输出文件 |
|------|----------|
| 中文 PDF | `文件名.pdf` → `文件名.md` |
| 英文 PDF，用户不同意翻译 | `文件名.pdf` → `文件名.md` |
| 英文 PDF，用户同意翻译 | `文件名.pdf` → `文件名.md` + `文件名_zh.md` |
| 使用 -t 或 -y | `文件名.pdf` → `文件名.md` + `文件名_zh.md` |

```python
# 语言检测
detect_language("Hello World")  # 返回 'en'
detect_language("你好世界")      # 返回 'zh'

# 翻译函数
translate_to_chinese(english_text)  # 调用 LLM API 翻译
```

## 完整流程图

```
┌─────────────────────────────────────────────────────────────────┐
│                        PDF 解析工作流                            │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  1. 读取 PDF 文件                                               │
│     └─ 使用 pdfinfo 获取页数                                    │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  2. 判断是否分批                                                │
│     ┌─ 页数 ≤ 50: 直接解析                                      │
│     └─ 页数 > 50: 分批处理                                      │
└─────────────────────────────────────────────────────────────────┘
          │                           │
          ▼                           ▼
┌──────────────────┐        ┌─────────────────────────────────────┐
│  3a. 单次解析    │        │  3b. 分批解析 (循环)                │
│                  │        │  for i in range(批次数):           │
│  POST /file_parse│        │    POST /file_parse                │
│  - return_md=true│        │    (start_page_id, end_page_id)    │
│  - return_images │        │    合并每批 md + images            │
│       │          │        └─────────────────────────────────────┘
│       ▼          │
│  获取 results    │
│    - md_content  │
│    - images {}   │
└──────────────────┘
          │
          ▼
┌─────────────────────────────────────────────────────────────────┐
│  4. 检测语言 (中文字符 > 30% = 中文)                            │
│     ┌─────────────────────┬───────────────────────────────────┐ │
│     │        中文         │           英文/其他                │ │
│     ├─────────────────────┼───────────────────────────────────┤ │
│     │ 保存原语言版本      │ ⚠️ 必须先询问用户是否翻译          │ │
│     │  文件名.md          │  ├─ 同意: 原版 + 中文版(_zh.md)   │ │
│     │                     │  └─ 不同意: 只保存原版            │ │
│     └─────────────────────┴───────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  5. 处理图片 + 保存                                             │
│     ┌─────────────────────────────────────────────────────────┐ │
│     │  内嵌模式（默认）:                                       │ │
│     │  ![](images/xxx.jpg) → ![](data:image/jpeg;base64,xxx)│ │
│     │                                                         │ │
│     │  独立模式:                                              │ │
│     │  保存到 {文件名}_images/ + 打包 ZIP                     │ │
│     └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  6. 输出文件                                                    │
│     ┌─────────────────────────────────────────────────────────┐ │
│     │  中文 PDF:        文件名.md                              │ │
│     │  英文 PDF:        文件名.md                              │ │
│     │  英文+翻译:       文件名.md + 文件名_zh.md              │ │
│     └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```

### 关键点总结

| 步骤 | 操作 | 关键参数 |
|------|------|----------|
| 获取页数 | `pdfinfo file.pdf` | - |
| 单次解析 | POST `/file_parse` | `return_md=true` |
| 分批解析 | POST + 页码范围 | `start_page_id`, `end_page_id` (从 0 开始) |
| 获取图片 | POST + `return_images=true` | 返回在 `results[file]['images']` |
| 内嵌图片 | 替换引用 | `images/xxx.jpg` → `data:image/...base64` |
| 独立图片 | 保存文件 | 保存到 `{文件名}_images/` |

### ⚠️ 关键约束：翻译前必须询问

| 场景 | 应该怎么做 |
|------|------------|
| 检测到英文PDF | **必须先问用户**"需要翻译成中文吗？" |
| 用户说"需要/同意" | 执行翻译（用 `-y` 或 `-t` 参数） |
| 用户说"不需要/不同意" | 只保存原版，不翻译 |
| 用户没明确回答 | 不要自动翻译，继续等待用户确认 |


## 错误处理

### 常见错误

| 错误 | 原因 | 解决方案 |
|------|------|----------|
| 413 Request Entity Too Large | 文件过大 | 减小文件或分批处理 |
| 429 Rate Limit | 请求过快 | 增加重试间隔 |
| 500 Internal Server Error | 服务端问题 | 重试几次 |
| Connection Timeout | 网络问题 | 检查网络，增加超时 |
| 轮询超时 | 任务处理慢 | 增加轮询次数 |



## 使用场景

### 1. 论文解析
```bash
python3 pdf_processor.py 论文.pdf -o 论文.md
```

### 2. 批量处理
```bash
for f in *.pdf; do python3 pdf_processor.py "$f"; done
```

### 3. 指定页面范围
```python
md, images = parse_pdf_with_retry('doc.pdf', start_page=0, end_page=49)
```

### 4. 独立模式打包
```bash
python3 pdf_processor.py document.pdf -s -z
```


## 注意事项

1. ⚠️ **页码从 0 开始**：第1页 = 0
2. 文件大小建议不超过 50MB
3. 大文件（>50页）建议分批处理
4. **默认内嵌模式**：图片 base64 内嵌到 Markdown，无需打包
5. **独立模式**：需要 `-z` 参数才打包
6. 网络问题建议设置重试（默认3次）

### HTML 表格自动转换

PDF 解析完成后，脚本会自动检查 Markdown 文件中的 HTML 表格（`<table>`, `<tr>`, `<td>`, `<th>` 等），并将其转换为标准的 Markdown 表格格式。

**转换示例**：
```html
<!-- 原始 HTML 表格 -->
<table>
  <tr><th>名称</th><th>值</th></tr>
  <tr><td>A</td><td>1</td></tr>
  <tr><td>B</td><td>2</td></tr>
</table>
```

```markdown
<!-- 转换为 Markdown 表格 -->
| 名称 | 值 |
| --- | --- |
| A | 1 |
| B | 2 |
```

此功能确保输出的 Markdown 文件完全兼容各种 Markdown 编辑器和阅读器。

## 内置 pdf 工具 vs pdf-processor 技能

| 特性 | 内置 pdf 工具 | pdf-processor 技能 |
|------|---------------|-------------------|
| 调用方式 | `pdf()` 函数 | `pdf-processor` skill |
| 后端 | 模型自带 | MinerU 远程 API |
| 能力 | 基础解析 | 专业论文解析 |
| 排版还原 | 一般 | 高（公式、表格、图） |
| 大文件 | 有限制 | 支持分批 |
| **图片模式** | 基础 | 支持内嵌/独立两种 |
| **推荐场景** | 简单文档 | 论文、复杂排版 |

---

**脚本位置**: `/root/.openclaw/workspace/scripts/pdf_processor.py`
