PDF → EPUB(扫描书转换工作流)
何时使用
- 输入是一本 PDF 书籍,内页全是扫描图片、没有文字层(用 PyMuPDF 提取不出文本,或全是空白)。
- 目标产物是一本干净、带完整导航目录、目录可点击跳转的 EPUB。
- 中间产物可以是 Markdown。
- 本 skill 面向通用流程:任何页数的纯文字中文书籍,不针对某一本书特化。
本 skill 不适用于:有文字层的数字原生 PDF(直接用提取工具即可);漫画书(图为主,本流程去除插图)。
核心原则:职责边界
| 环节 | 承担方 | 理由 |
|---|---|---|
| 拆页渲染、标记检查、批次合并、分章、打包、校验 | CLI 脚本(scripts/) |
确定性、可复现、可重跑 |
| 识图、错别字校对、版权页判定、锚点转标题 | Agent(Claude 子代理 / 主代理) | 需要视觉与语义判断,不写脚本 |
| 全流程编排与规范 | SKILL.md | 承载流程与严谨规则 |
模型约定:识图子代理必须使用 Sonnet 模型;不要使用外部 API。主代理校对由当前会话模型完成。
不要为识图或校对环节编写脚本——那部分是 Agent 的灵活任务,脚本只覆盖五个确定性命令:extract、check-markers、merge-batches、md-to-epub、verify。
工作流总览
1. extract (CLI) PDF → 每页一张图 page_0001.png...
2. 子代理识图 (Agent) 每子代理一批 → batch_001.md(纯文本行 + 锚点)
3. 批次标记检查 (CLI) check-markers 检查各 batch 标记 → 有误重跑、无误通过
4. 批次合并 (CLI) merge-batches 按序合并 → draft.md
5. 草稿标记复查 (CLI) check-markers 复查 draft.md 标记
6. 主代理校对 (Agent) 修正错别字(不改原文)→ 锚点转 h 标题 → book.md
7. md-to-epub (CLI) book.md → 按 h 标题切分章节 → 生成导航目录 → book.epub
8. verify (CLI) 校验 EPUB 结构、目录跳转、无残留
依赖
- Python 3.10+,
pymupdf(渲染 PDF 页为图,import fitz或import pymupdf) - pandoc(Markdown → EPUB,含导航目录)。安装顺序:
- 系统 CLI pandoc(
winget install JohnMacFarlane.Pandoc/brew install pandoc) - 或
pip install pypandoc-binary(捆绑 pandoc 二进制,无需单独安装) - 兜底:
pip install ebooklib(纯 Python 打包,目录同样生成)
- 系统 CLI pandoc(
- 脚本自动探测上述任一可用路径。
启动前先检查:python scripts/extract.py --check 会报告缺哪些依赖。
工作目录布局
在临时目录内新建一个项目文件夹 book/,全部产物放其中:
book/
├── pages/ # 1. 拆页图片 page_0001.png...
├── raws/ # 2. 子代理识图输出 batch_001.md...
├── draft.md # 3-5. 批次合并 + 标记检查后的草稿(仍含 PAGE/CH/__ 标记)
├── book.md # 6. 校对优化后的全文(干净层级化 Markdown)
└── book.epub # 7. 最终产物
1. extract:拆页渲染(CLI)
python scripts/extract.py <input.pdf> -o book/pages [--dpi 300]
- 每页渲染为一张 PNG,文件名按原书页码顺序
page_0001.png、page_0002.png……(从 1 起连续编号,忽略 PDF 内部无关页面偏移)。 - 默认 300 DPI,足以支撑中文小字识别;输出时打印总页数,供调度子代理参考。
- 结束后核对
pages/图片数量 == PDF 页数。
2. 子代理识图(Sonnet 子代理):逐页转纯文本行 + 锚点
分派规则
- 模型:本阶段识图一律使用 Sonnet 模型子代理(Sonnet 具备本环境识图能力),严禁使用外部 API 或其他模型;主代理校对由当前会话模型完成。
- 每个子代理负责连续 10 张图片:子代理 #1 负责
page_0001–page_0010,#2 负责page_0011–page_0020……批次严格按页码顺序分派。 - 批次边界:第 k 个子代理负责
page_{(k-1)*10+1:04d}~page_{k*10:04d};最后一批不足 10 页则只处理实际存在的页。 - 并发由主代理自行决定:一次可同时调度多个子代理,各自负责不同批次,批次间不重叠、顺序不乱。并发数与批次划分由主代理按页数规模、成本与运行环境权衡,不设固定上限。
- 每子代理一个文件:子代理把自己负责的整批页内容写入同一个文件
batch_{k:03d}.md(命名规范见references/subagent_prompt.md),文件内**每页(含空白页、无正文页)**均以<!-- PAGE XXXX -->注释定位(空页只写标记不写内容)。失败重跑以批次为单位。
子代理提示词模板(引用 references/subagent_prompt.md)
调度每个子代理时,直接使用 references/subagent_prompt.md 中的提示词模板:把 {N}、{起始页}、{结束页}、{批次序号}、{pages_dir}、{raws_dir} 替换为实际值后作为子代理任务传入(识别必须用 Sonnet 模型)。该文件是提示词的唯一正式来源,本小节不再内嵌模板正文。
调度子代理时,仅可替换模板中 {占位符} 标记的内容,模板其余部分必须原样保留,禁止增删或修改。
子代理严格输出约束(要点)
子代理的唯一输出规范以 references/subagent_prompt.md 的提示词模板为准,要点归纳如下;完整条款见模板与下方「锚点规范」「内容保留 / 去除规则」:
- 输出纯文本行:原文一个自然段 = Markdown 一行,段落间空行;不使用列表、表格、代码块等结构标记。
- 跨页段落:一个自然段被分页截断时,上一页行尾与下一页行首各写
__连接标记(两部分间不放空行),校对时合并为一段。 - 禁止:任何解释、说明、思考过程、结论、"这是第 X 页"之类元评论;禁止页眉页脚文字;禁止对图片的描述性旁白。
- 每子代理一个文件
batch_{k:03d}.md:严格按page_XXXX.png文件名的数字顺序逐页读取,整批页内容顺序写入同一文件;每页(含空页)以<!-- PAGE XXXX -->定位(XXXX 严格等于该图片文件名的页码:读page_0005.png写<!-- PAGE 0005 -->)。 - 遇到章节标题时,插入层级前缀式锚点(见下),正文段落里不重复标题文字;层级如实反映原书结构,不主观合并或拆分。
- 保留:前言、后记(标题用锚点标注层级)、各章节章号/大标题、正文段落;原书目录页照常识别。
- 去除:页眉、页脚、插图、二维码/条形码/水印等无关标识。
- 遇到疑似版权页(整页版权声明模式:ISBN / 版权年份 / 出版信息 / 印次号)时,整页内容以单个锚点
<!-- COPYRIGHT_CANDIDATE -->起始,交由主代理终审。 - 完成后汇报:顺利则只返回一句话说明已写入的文件名;遇阻断或不确定点(图片无法读取、内容严重残缺、规则无法判断)如实反馈主代理,不自行臆断、跳过或编造内容。
锚点规范(层级前缀式)
每遇到一个标题,插入一行注释,CH 后的数字表示层级:
<!-- CH1: 第二部 江湖 -->
<!-- CH2: 第1章 初入师门 -->
<!-- CH3: 第一节 拜师 -->
CH1= 全书最高级标题:有部/卷则部/卷为 CH1、章为 CH2、节为 CH3;无部/卷的书,章直接为 CH1(保证顶级章节转成 h1,pandoc 才能按章切分,否则全书挤成单一文件)。- 标题文本只出现在锚点中,正文段落不重复标题。
- 锚点为独立注释行,前后以空行与正文隔开;锚点之间是正文(段落,一个自然段 = 一行)。
- 规则:遇到一个新标题就按其层级插入对应锚点;层级如实反映原书结构,不要主观合并或拆分。
内容保留 / 去除规则
| 内容 | 处理 | |
|---|---|---|
| ✅ 保留 | 前言、后记 | 作为正文保留(标题用锚点标注层级) |
| ✅ 保留 | 各章节章号/大标题 | 插入锚点,作为分章依据 |
| ✅ 保留 | 正文段落 | 每段一行,段落间空行 |
| ❌ 去除 | 页眉、页脚 | 直接丢弃,不写入 |
| ❌ 去除 | 插图(含示意图、照片) | 直接丢弃,不写入(本 skill 面向纯文字书籍) |
| ❌ 去除 | 版权页(整页版权声明) | 用 <!-- COPYRIGHT_CANDIDATE --> 标记,主代理终审 |
| ❌ 去除 | 无关标识(二维码、条形码、水印、页码) | 直接丢弃 |
| ⚠️ 特殊 | 原书目录页 | 子代理照常识别正文,但主代理校对阶段会据此提取章节结构并整体丢弃(不进入最终 EPUB,避免与导航目录重复) |
3. 批次标记检查:check-markers(CLI)
全部批次子代理完成后,用脚本逐个检查 raws/ 下的 batch_XXX.md:
python scripts/check_markers.py book/raws/*.md
脚本输出每个文件的标记清单与问题:
- PAGE:列出全部页码;检查顺序递增、无缺号、无重复。
- CH:列出全部
<!-- CHn: 标题 -->及行号;检查层级是否跳级(作警告)。 - __:列出全部行尾/行首
__及行号;检查跨页切口是否成对。
判定:无问题 → 通过;有问题(PAGE 缺失/乱序、__ 不成对)→ 单独重派该批次子代理,重跑后复检,直至通过;不重跑全书。若同一批次多次失败,可临时缩小该批次页数(如拆为 5 页)再试,或暂停交由用户处理。
4. 批次合并:merge-batches(CLI)
python scripts/merge_batches.py book/raws -o book/draft.md
按批次序号顺序合并所有 batch_XXX.md 为 draft.md:保留各批内的 PAGE/CH/__ 标记(供校对定位、跨批次 __ 切口衔接);合并时校验批次序号连续(1..K 无缺)。合并后规范化行距:任意两个相邻非空行之间恰好 1 个空行(跨页 __ 切口连接的行除外,保持切口两部分紧密相连),无连续空行堆积、无缺失空行。
5. 草稿标记复查:check-markers(CLI)
python scripts/check_markers.py book/draft.md
合并后对整本草稿复查一次标记:全书画页码连续(无跨批次缺页)、__ 切口在批次边界正确成对、CH 层级全本连续。复查通过后再进入校对。
6. 主代理校对:错别字修正 + 转标题(Agent)
这是 Agent 的灵活任务,不写脚本。基于 draft.md 执行:
- 分块通读:按批次分批通读
draft.md,每块在上下文内完整读完。页数多也不爆上下文。主要修正识别出来的错别字,不改变原文、不增删句子。 - 标记转换:
- 删除
<!-- PAGE XXXX -->页定位注释——空白页/无意义页仅含该注释,删除后自然无痕。 - 去掉跨页行尾/行首的
__连接标记并把两部分合成为一段。 - 将
<!-- CH1 -->/CH2/CH3/CH4分别转为#/##/###/####Markdown 标题(以此类推);删除已无用的锚点注释与COPYRIGHT_CANDIDATE标记。
- 删除
- 目录页处理:原书目录页仅用于核对章节标题与层级,核对完毕后从正文中移除。
- 版权页终审:逐个确认
COPYRIGHT_CANDIDATE标记的页——确为版权声明则删除;误标(如书名页、献辞页)则保留为正文。 - 最终检查:全文无空锚点、无连续空行堆积、无未清除的页眉页脚特征、无
<!-- PAGE残留、无__残留;h 标题层级连续无跳级。此时产出book.md,是干净的层级化 Markdown,h1=全书最高级标题(有部/卷则为部/卷,无则为章),h2、h3 依层级递减。
7. md-to-epub:分章与打包(CLI)
python scripts/md_to_epub.py book/book.md -o book/book.epub \
--title "书名" --author "作者" --lang zh [--cover book/pages/page_0001.png]
脚本行为:
- 切分:以 h1 为顶级章节切分正文;h2/h3 作为子级保留在章节内。
- 导航目录:生成 EPUB 导航(NCX 与 nav.xhtml),目录条目与正文 h1/h2 标题一一对应(h3 及以下为章内层级,不进目录),支持点击跳转。
- 封面(可选):
--cover <图片>指定封面图(PNG/JPG)。实际流程通常用拆页得到的page_0001.png(原书封面页),也可由用户提供其他图片;pandoc 路径用--epub-cover-image,ebooklib 路径用EpubImage设cover-image属性,两条路径均生成封面。 - 打包:优先调用
pandoc(Markdown → EPUB);若 pandoc 缺失,回退用ebooklib手工组包(目录同样生成)。两种路径都保证阅读顺序与原书一致、已去除的内容不出现。 - 输出
book.epub。
8. verify:校验(CLI)
python scripts/verify.py book/book.epub
脚本检查并完整打印校验报告到终端:
- EPUB 结构合法:
content.opf/manifest/spine完整一致。 - 导航目录存在,且每条目录条目在正文中都有对应标题(条目数 ≈ 正文 h1/h2 标题数;封面等非正文页除外)。
- 目录锚点目标可解析(点击可跳转)。
- 正文字段抽查:无
<!-- CH锚点注释残留、无COPYRIGHT_CANDIDATE、无<!-- PAGE页定位注释残留、无__连接标记残留。
校验失败时脚本给出具体条目,修复后重跑 md-to-epub 与 verify 直至通过。
常见问题
- 某批次识别失败 / 子代理超时:用
check-markers检查确认问题,单独重跑该批次子代理(batch_XXX),不重跑全书。 - 正文出现公式/表格(罕见):本 skill 面向纯文字书籍(如小说),子代理不使用结构标记;若某页含公式表格等非常规内容,如实转录为文字段落,主代理校对时人工核对。
- 某本书需要保留插图:偏离通用默认,需用户单独确认,并放弃"去除插图"规则。