由我和我的大肥鱼一起制作
把整本 PDF 按章节切开,再一键导入 OneNote —— 一章(或一节)一个页面,可以拿触控笔在书上直接写。
扫描版教材、电子版教材、文件汇编都能处理 —— 扫描件没有文本层、书签只是页码,电子版又常常书签和目录页码对不上,而 OneNote 只认物理页。
- 自动识别章节:书签 → 文本层 → OCR 目录页 → 固定页数,四级降级,扫描件也能认出目录
- 按章 / 按节切分:输出一堆小 PDF,文件名直接就是章节名
- 一键导入 OneNote:每个 PDF 一个页面,页面标题自动用文件名,导入后可手写批注、可被 OCR 搜索
git clone <your-repo> pdf2onenote
cd pdf2onenote
python -m venv .venv
.venv\Scripts\pip install -r requirements.txtWindows 上也可以直接双击 install.bat(会装上核心依赖,再尝试装可选的 OCR 依赖)。
rapidocr-onnxruntime 是扫描件识别目录用的(约 100MB,含模型),已拆到可选的 requirements-ocr.txt:
.venv\Scripts\pip install -r requirements-ocr.txt # 只处理有书签/文本层的 PDF 时不用装装不上也不影响拆分与导入——扫描件会退化为「固定页数」兜底切分。
python -m pdf2onenote split 书.pdf --level 1 # 按章
python -m pdf2onenote split 书.pdf --level 2 # 按章 + 按节(更细)
python -m pdf2onenote split 书.pdf --dry-run # 先看识别结果,不切文件输出:<PDF名>_章节/,里面的文件名形如 01_集合与映射.pdf、05-2_洛必达法则.pdf,另有 00_前置页(封面·版权·目录).pdf(--no-front 可关)。章号取标题里的「第X章」;英文教材没有这个字样就按出现顺序编号(标题里自带的章号会去掉,避免 01_1 Introduction 这种重复),附录 / 索引 / 答案这类正文之后的内容排到 91 起。重名会自动加 _2 后缀,不会互相覆盖。
--level 2 在四个通道都生效(书签、文本层、OCR、兜底页数不产生节);只有和章标题同页的那一节不单独成文件,内容包含在章文件里。
python -m pdf2onenote import ./书_章节 --limit 1 # 先试一个
python -m pdf2onenote import ./书_章节 # 全量python -m pdf2onenote all 书.pdf --level 1把 PDF 拖到 run.bat 上,自动拆分 + 导入。
依次采用预设的方法,识别到就停:
| 顺序 | 策略 | 适用场景 | 说明 |
|---|---|---|---|
| 1 | PDF 书签 | 有目录书签的 PDF | 读 outline,按层级取章/节。会剔除「每页一个的页码书签」和封面/书名/版权/前言这类条目,但附录 / 索引 / 参考文献会保留;Part > 章 > 节 三层书签会自动把章层下移到第二层(顶层是篇);每章重复的 Exercises / Summary / Key Terms 不当章 |
| 2 | 文本层目录页 | 有文本层的 PDF(电子版、文件汇编) | 见下 |
| 3 | OCR 目录页 | 扫描版教材 | 同上,只是目录页要先 OCR 出来 |
| 4 | 固定页数 | 以上全失败 | 每 N 页切一份(--every 30) |
有文本层就不会去跑 OCR
目录页路径的流程(两种来源共用):
-
扫描前 N 页(
--toc-scan,默认 30),用「行尾数字密度」找出目录页,再向前向后扩展成连续段 -
把目录页拆成行,每行记下左边界 x 坐标,解析出
标题 … 页码;行尾数字若比全书页数还大(如标题里的年份2024),不当页码 -
按左缩进聚类分章/节,只有两条互补规则:
情形 判定 例子 多缩进层级(章比节更靠左) 取最左的「含章样条目」簇为章;章号与之不重叠的右栏簇视为双栏续章;整簇都是附页(索引/参考文献)的也保留 Chapter 1–4左栏 +Chapter 5–8右栏单缩进层级(整本左对齐) 只留章样条目( 第X章/Chapter N/附录/索引…)与书末附页,N.M小节与重复小节剔除文件汇编、纯文本目录 两种分栏都会先做「含章样条目的孤立小簇」保护:只有 ≥2 条章样条目的孤立簇才算真实续章/独立栏,单条噪声行不会劫持章层。
进这一步之前还有两种排版要先归一化,不然标题会被拆错:
- 页码是独立的文本框(排版软件常这么放),跟标题不在同一个行对象里 —— 按 y 邻近并回一行,只并「一边是纯页码、另一边不是」的组合,免得双栏目录左右两列并到一起
Chapter 1/第一章这种章号独占一行 —— 并到紧随其后的标题行上当前缀,否则会被当成「标题 Chapter、页码 1」,整章信息全错
中文「第X章」特殊路由:目录里出现 ≥3 个
第X章就认定是显式章编号,直接按「第X章 + 书末附页」筛选,不再依赖缩进——扫描件 OCR 后章标题左边界会在 40~85 之间跳变,靠缩进会把大半章判成小节。按节切分(
--level 2):在上面结果之外,把§1.1/一、/(一)/1.2这类小节行收成 L2(文件名01-1_…)。和章标题同页的第一节不单独成文件(会切出 0 页),它的内容自然落在章文件里。 -
抽样几页正文,读页眉/页脚上的页码(扫描件是 OCR 页脚),投票算出「印刷页码 → PDF 页序」的偏移量;页脚页码居中或读不到时,退化为「目录末页 + 1」推算
-
用偏移量把目录页码换算成 PDF 页序,切分;第一个条目如果不是第 1 页,最前面会补一个
00_前置页(封面·版权·目录),保证整本书不丢页(--no-front可关)
第 3 步是关键。靠正则去认「第X章 / § / 一、」是认不全的——文件汇编类的文档根本没有「章」,只有文章标题和小节。改成看缩进后,教材和文件汇编都能认。
第 4 步同理:扫描件的目录印的是印刷页码,而 PDF 前面还夹着封面、版权、目录等页,两者差一个固定偏移。硬猜会错,所以直接去正文页眉页脚把页码读出来;读不到才用目录位置推算。
| 通道 | 条件 | 特点 |
|---|---|---|
| COM | OneNote 桌面版自动化接口正常 | 快,能精确指定笔记本和分区(不存在会自动建) |
| UI 驱动 | COM 被锁(默认会落到这条) | 只用 Ctrl+N / Ctrl+V,不依赖 ribbon 和打印机,导入到当前打开的分区 |
很多 Office 2024 / 365 机器上 COM 是坏的:Dispatch("OneNote.Application") 能成功,但 GetHierarchy 等方法一律返回 0x80004005,而且 ONENOTE.EXE 里没有可加载的类型库。工具会先探测 COM,不通就自动切到 UI 驱动。
UI 驱动通道的注意事项:
- 运行前手动把 OneNote 切到目标分区,脚本在当前分区建页
- 新版 OneNote 建页是异步的:脚本建页后等 1.5 s 再粘标题,粘完还会用 UI Automation 读回页面第一行校验,不一致会自动重试(最多 3 次);剪贴板被别的程序占用时也会重试
- 运行期间别动鼠标键盘;想紧急停止就把鼠标甩到屏幕左上角
- 卡的话把
--delay调大(默认 0.6s/页)
| 参数 | 默认 | 说明 |
|---|---|---|
--level |
1 | 1 = 按章,2 = 章 + 节(和章同页的第一节并进章文件,内容不丢) |
--out |
<PDF名>_章节 |
输出目录 |
--toc-scan |
30 | 扫描前 N 页找目录页 |
--ocr-workers |
2 | OCR 并行线程数,调大更快也更吃内存 |
--every |
30 | 兜底切分的页数 |
--no-ocr |
关 | 禁用 OCR |
--no-front |
关 | 不单独产出 00_前置页(封面/版权/目录) |
--dry-run |
关 | 只打印识别结果 |
| 参数 | 默认 | 说明 |
|---|---|---|
--dpi |
150 | 渲染清晰度,越高越清楚也越占空间 |
--color |
关 | 默认灰度,扫描教材没必要彩色 |
--delay |
0.6 | 每张图粘贴后的等待秒数 |
--section |
PDF 导入 | COM 通道下的目标分区 |
--ui |
关 | 强制走 UI 驱动通道 |
--limit |
0 | 只导入前 N 个文件 |
识别出来的章节不对
先用 --dry-run 看。扫描件常见问题是目录页没被找到,把 --toc-scan 调大(比如 40)。
还不行就加 --level 2 看小节是否被认出来(能看出缩进聚类对不对),或者直接改识别:_章节对照.txt 只是结果记录,改它不会影响切分。
导入顺序不对 / 节排到了章前面
导入按文件名里的「章号, 节号」数字排序(章永远在自己节前面),不再按字典序,所以 01_… 一定在 01-1_… 之前。
不想让封面和目录单独成一个文件
加 --no-front。
页面标题是「未命名」
最近版 OneNote 新建页面是异步的,粘标题有时会被吞掉。工具已经会等 1.5 s 并用 UI Automation 读回校验、失败重试;如果仍然出现,把 --delay 调大(比如 1.2)再试一次。
导入很慢
362 页大约 6–10 分钟,主要是 OneNote 自己粘贴渲染慢。分几次导入,每次 --limit。
页面里图片顺序乱了
别在导入过程中碰键盘鼠标。中断了的话,删掉当前页面,用 --limit 重来。
OCR 认不出目录 RapidOCR 对竖排、花式字体、密集点线目录效果一般。认不出时会落到固定页数切分。
识别太慢
扫 25 页目录大约 2–5 分钟(CPU 上跑 onnx)。把 --ocr-workers 调到 3–4 能快一倍,代价是多占几百 MB 内存;目录靠前的书也可以把 --toc-scan 调小。
第一章节页码认不出来 目录里第一章后面往往跟着最长的点线,OCR 容易把它连页码一起丢掉。工具发现首个条目的印刷页码不是 1 时,会自动按「正文首页 = 印刷页 1」补上第一章。
pdf2onenote/
├── pdf2onenote/
│ ├── toc.py # 章节识别(书签 / 文本 / OCR / 兜底)
│ ├── splitter.py # 按条目切 PDF
│ ├── onenote.py # 导入(COM + UI 驱动双通道)
│ └── cli.py # 命令行入口
├── selfcheck.py # 自检(20 项,合成数据,不需要教材 PDF)
├── install.bat # 建 venv + 装依赖(含可选 OCR)
├── run.bat # PDF 拖上去,拆分 + 导入
├── requirements.txt # 核心依赖
├── requirements-ocr.txt # 可选:扫描件 OCR
├── CHANGES.md # 改动记录(含真实教材 + OneNote 实机验证)
├── HANDOFF.md # 交接说明(设计取舍、已知限制)
└── README.md
装好依赖后可以先自检,再处理自己的书:
python selfcheck.py # 应输出「通过 20 / 失败 0 / 共 20」
python -m pdf2onenote split --dry-run "书.pdf" # 先看识别结果文本层目录页的解析靠「左缩进聚类 + 显式标记」两条互补规则,覆盖绝大多数教材与文件汇编。下面是实测中踩过并已在代码里修掉的坑,以及仍需注意的边界:
| 边界情况 | 例子 | 处理 |
|---|---|---|
| 章号独占一行 | CHAPTER 1 / 第一章 独占一行,标题在下一行 |
并把「远右独立页码框」一并抓回,章号绝不当页码抠掉 |
| 章标题折行 | Chapter 12 / Chapter 13 的长标题折成两行 |
贪心吞掉折行标题,直到碰到页码 / 下一个章头 / 小节才停 |
| 纯缩进分级目录 | 章 x=148.5、节 x=163.4,章号复用 | 取最左含章样条目的簇为章;剔除每章重复的 Introduction 之类小节 |
| 罗马数字 Part + 阿拉伯章 | Part 在 x=65、Chapter 在 x=88.3 | 同时存在阿拉伯章时丢弃纯罗马 Part 簇,避免重复切分 |
| 双栏目录 | 章 1–4 在左栏 x=72、章 5–8 在右栏 x=143.7 | 章号集合与左栏不重叠的右栏视为续章保留;章号重叠的缩进小节丢弃 |
| 双栏 + 孤立小簇吞章 | 章 11/12 挤在右栏 x≈52.9 的 2 条小簇 | 左缩进聚类时,「含 ≥2 条章样条目的孤立小簇」不再当噪声并掉,保留为独立层级交续章逻辑处理;单条噪声行不再享有这个豁免 |
| 中文「第X章」+ OCR 缩进抖动 | 扫描件 OCR 后章标题左边界 x 在 42~82 双峰跳变 | 第X章 是无歧义章标记:候选里出现 ≥3 个即认定为显式章编号,直接按「第X章 + 书末附页」筛选全部保留为 L1,不再依赖会因 OCR 抖动误判的缩进聚类(否则 8 章只认出 4 章) |
| 超长英文章标题 | 82 字符的英文标题 | 标题长度上限放宽到 120(ASCII)/ 50(中文),噪声过滤靠「必须命中章样模式」兜底 |
双栏里写 Chapter N |
两栏都写 Chapter 3 … / Chapter 7 … |
续章判据取章号时带词边界,不会把 Chapter 的 C、Index 的 I 当成罗马数字(否则两栏章号都算成 {C},右栏整栏被丢) |
| 页脚页码居中 | 中文教材常见把页码排在页脚正中 | 角落页码投票采不到时,偏移退化为「目录末页 + 1」,不会整体错一页 |
仍需注意:少数书第一章是 Introduction / Preface 这类没有 Chapter N 字样的无名章(如全书只有 Introduction 没有 Chapter N 的书),文本通道可能不把它单独成章——这类书通常有完整书签,会优先走书签通道;纯文本通道下可加 --level 2 或先用 --dry-run 确认。
「第X章」少于 3 个的中文书:explicit_cn 路由要求目录里至少有 3 个 第X章 才触发;只有 1–2 章的短书仍走缩进聚类,若同时有扫描抖动可能丢章(可先 --dry-run 确认)。
和章同页的第一节:§1.1 常常就印在章首页上,切出来会是 0 页文件,所以这一节不单独成文件,内容包含在章文件里(--level 2 下的正常行为)。
扫描件 / 无文本层 PDF:既无书签又无文本层的纯扫描书(如部分 z-library 扫描版),四级降级会一路走到 OCR 目录页通道。该通道需 pip install -r requirements-ocr.txt(复用 onnxruntime),会渲染并 OCR 前 --toc-scan(默认 30)页定位目录,CPU 上约 5–10 秒/页,整本定位耗时较久;首章常因页码缺失按印刷页 1 补。未装 OCR 时退化为「固定页数」兜底切分。
导入链路只做过一次实机验证:UI 驱动通道在一台 OneNote 桌面(6 页一章)上验证通过——页面标题正确、6 张图片都在。COM 通道仍未实测(多数 Office 365/2024 机器上它本来就是坏的)。COM 中途失败会自动回退到 UI 驱动;如果你机器上 COM 半通不通,直接加 --ui 更稳。
MIT