# 用 Python 将 PDF 转为 Markdown：提交任务、恢复与读取结果

运行 PDF Craft 的 Python 示例，理解异步转换和任务恢复，读取 Markdown ZIP，并了解接入自己 PDF 文件时需要调整的部分。

用 Python 将扫描 PDF 转为 Markdown，可以把识别、下载和后续文本处理接进同一套程序。PDF Craft 的转换在云端执行：Python 创建任务并上传 PDF，等待处理完成，再下载包含 Markdown 和图片资源的 ZIP。

适合先跑通的入口是[官方 Python 示例](https://pdfcraft.ai/examples/api-quickstart.py)。它使用固定的单页 PDF，方便验证连接、鉴权和结果下载；处理自己的文件需要调整上传与结果校验。若只想手动转换一份文件，可以直接按[网页操作教程](https://pdfcraft.ai/zh-CN/blog/pdf-to-markdown-guide/)完成。

## 先运行固定样例

准备 Python 3.9 或更高版本，无需安装额外依赖。在 [OOMOL Console](https://console.oomol.com/api-key) 创建生产环境 API Key，确认账户有足够点数；运行转换会产生用量，费率见[充值页面](https://pdfcraft.ai/zh-CN/recharge/)。

以下命令适用于 Bash 和 Zsh。下载脚本后，通过不回显的输入设置密钥：

```bash
curl --fail --show-error --location \
  https://pdfcraft.ai/examples/api-quickstart.py \
  --output api-quickstart.py

export PDF_CRAFT_API_KEY="$(
  python3 -c 'import getpass; print(getpass.getpass("API key: "))'
)"
python3 api-quickstart.py
```

脚本会保存 `pdf-craft-task.json`，其中的 `uuid` 用来找回同一次转换。成功后，结果写入 `pdf-craft-result.zip`。脚本还会在 Markdown 中检查样例里的固定句子，确认拿到了预期文件。

当前脚本只处理固定样例，没有 `--input` 参数。接入自己的 PDF 时，需要调整上传来源和结果校验，具体改动见文末。

## 保存任务 ID，按状态继续处理

PDF 转换包含创建、上传、启动、查询和下载几个阶段。启动请求被接受，只表示服务接下了任务，结果需要在后续查询中取得。

示例的主循环根据任务状态决定下一步：

| 状态                     | 程序接下来做什么              |
| ---------------------- | --------------------- |
| `waiting_for_upload`   | 获取上传地址，按需上传文件，再提交转换参数 |
| `running`              | 等待后再次查询，逐渐延长间隔        |
| `succeeded`            | 获取下载链接，读取 ZIP 并保存     |
| `failed` 或 `cancelled` | 停止，保留错误信息             |

上传已完成时，重新获取上传地址会返回文件的读取地址，不必再次上传。启动时使用 `{"output_format":"markdown"}`，不指定目标语言，便保留原文语言。需要脚注时，可在自己的程序中按[转换参数](https://pdfcraft.ai/zh-CN/docs/api/conversion/)设置精修。

API Key 只传给 API 请求。向返回的存储地址上传或下载时，不附带 API Key；上传要使用响应中提供的请求头。完整请求顺序见 [cURL 分步接入](https://pdfcraft.ai/zh-CN/docs/getting-started/quickstart/)。

## 超时后继续同一个任务

本地脚本超时或断网，不意味着云端转换失败。保留状态文件，用创建任务时的账户恢复：

```bash
python3 api-quickstart.py \
  --resume pdf-craft-task.json \
  --output pdf-craft-result.zip \
  --timeout 1200
```

`--timeout` 是本次脚本运行的总等待时间，单位为秒，不是给云端任务重新计时。已有输出文件不会被覆盖，需要时换一个 `--output` 路径。

如果状态文件只有 `creation_unknown`、没有 `uuid`，先通过[任务列表](https://pdfcraft.ai/zh-CN/docs/api/overview/#查询任务列表)确认创建结果，再决定是否重建。已经 `failed` 的任务也不会因为重复启动而重新执行。不同阶段的恢复方法见[任务生命周期](https://pdfcraft.ai/zh-CN/docs/api/lifecycle/)。

## 用 Python 读取 ZIP 里的 Markdown

下载结果后，可以直接查看归档中的文件，不必先把全部内容解压。下面的代码只使用 Python 标准库，列出资源，并预览每份 Markdown 的开头：

```python
from pathlib import Path
from zipfile import ZipFile

result = Path("pdf-craft-result.zip")
with ZipFile(result) as archive:
    for item in archive.infolist():
        if item.is_dir():
            continue
        print(f"{item.filename}: {item.file_size} bytes")
        if item.filename.lower().endswith(".md"):
            with archive.open(item) as source:
                preview = source.read(8192).decode("utf-8", errors="replace")
            print(preview)
```

这里最多读取每份 Markdown 开头的 8 KiB，因此较长文档的预览会截断，末尾也可能出现不完整字符。正式处理正文时，按你的文件大小和用途读取完整内容。

如果要在编辑器中打开结果，解压到独立文件夹并保留目录结构。只搬走 `.md` 文件会让本地图片引用失效，归档和分享时应一起保留对应资源，具体见[Markdown 图片资源指南](https://pdfcraft.ai/zh-CN/blog/pdf-markdown-image-assets/)。

## 接入自己的 PDF 时，改哪些地方

固定样例跑通后，可以沿用任务循环，但要把输入与校验改成自己的业务规则：

* **上传来源**：将示例下载 `SAMPLE_URL` 的步骤改为读取你的 PDF，例如用 `Path("document.pdf").read_bytes()` 取得字节，再发往本次任务返回的上传地址。大文件应按业务需要规划内存与上传方式。
* **结果校验**：示例中的 `EXPECTED_TEXT` 只适用于那份单页 PDF。改为检查 ZIP 中是否有预期的 Markdown，并根据文档需要核对章节或关键内容。
* **任务记录**：把输入文件、`uuid` 和启动参数一并保存。恢复时使用同一份文件和参数；更换已上传文件需要新建任务。
* **大小与时限**：示例把单次读取的响应限制为 10 MiB，默认总等待时间为 600 秒。这是示例代码的设置，不是服务的通用文件上限。

从一份真实文档开始，保存原 PDF、任务记录和输出 ZIP，再把读取正文的代码接入笔记整理或其他处理步骤。这样程序中断时能继续原任务，也能随时回到原件检查提取内容。

