# 基于 PyPDF2 的 PDF 目录添加方法

> 本文公开全文。

原文：https://utgd.net/article/20875

注：本文方案依赖于Python3、PyPDF2、LaunchBar。前两者是必要项，其安装方法很容易搜索到。LaunchBar严格地说并非必要项，只是起到代码容器和启动器的作用，个人认为LaunchBar是启动此类动作的最优方案，但如果你有不同的偏好，下载本文提供的LaunchBar动作，获取源代码后稍加改造即可。

不知是专业特色，还是国内电子书市场尚处草莽时代的缩影，想找我专业领域内的书籍，经常只能找到不带目录的扫描版。这里包括了两个问题，其一是扫描版文字默认不可复制或搜索，其二是默认不带可跳转的目录，扫描版PDF因其照片出身也无法基于原本的排版信息自动化生成目录。在OCR技术普及的今天，前者已经可以缓解；而后者依然没有一个便捷方案。

早在两年前，我和Minja讨论过如何为PDF添加目录，当时的一致结论是：找淘宝代劳，无奈要处理的书太多，全都求诸淘宝成本太高。后者近期发布的基于 Keyboard Maestro 的半自动 PDF 目录做法介绍了一种模拟键鼠操作的低技术方案，具有相当强的通用性。本文的方案则借助PyPDF2，在自动化程度和生成层级目录方面有更好的表现。

本文将介绍： \- 一套经过检验的，从OCR获取文字目录后整理修正形成目录文件，并将目录导入PDF文件的操作流程； \- 一个基于Python、以LaunchBar整合的目录添加脚本。

本文涉及LaunchBar脚本的下载链接。

## 第一步：获取并整理文字版目录

文字版目录，理论上可以尝试从豆瓣、电商网站、出版商网站获取，也可以自己手动OCR书籍文件目录页后复制。但实践中我已经不再尝试前一种方法，失败的次数太多，宁愿采用效率看似更低但确定性更强的后一种方式。需提醒的是，不同OCR软件的识别效果差距较大，值得在这一步挑一个更趁手的工具。比如从我个人实操经历来看，使用DEVONthink内置的OCR引擎得到的结果，与直接用ABBYY识别目录页得到的结果差距甚大，后者的后续工作量相对前者起码减半。

首先要做的是将文字版目录逐页复制，粘贴进文本编辑器中。最终目的是保存为一个以\.bm为扩展名的纯文本目录文件\(以下称bm文件\)，该文件将用于向PDF添加目录条目。bm文件内部的文本结构示例如下：

```
第一层级的标题1 1
    第二层级的标题1 1
        第三层级的标题1 1
        第三层级的标题2 3
        ……
    第二层级的标题2 5
    ……
```

每行可分为两个部分：第一部分是缩进与标题文本，缩进的一个层级用一个制表符表示，第一层级的标题无需缩进，第二层级的标题用一个制表符缩进，以此类推；第二部分是该标题文本对应的页码。两部分间以空格间隔。

对全部粘贴至文本编辑器的目录内容，我通常先进行一次繁体转简体的替换（使用LaunchBar内置的"Convert Text to Simplified Chinese"动作），原因是OCR识别出的中文可能繁简体混杂，有些甚至肉眼无法一眼分辨，无法排查替换，进而在后续搜索中造成问题。

处理文字版目录的文本编辑器并无过多要求，支持正则表达式查找与替换的即可。正则表达式可以用于匹配和处理文本的特定模式（pattern），可以视为通配符的超级进阶版，是一种强大而灵活的工具，可以用于文本搜索、替换、验证、提取等操作。本文末尾的小结部分会提供目录制作场景下几个基础的正则表达式，但并一定不能适用于所有目录，也不一定是效率最高的表达式，因此强烈建议有需要的读者学习一下正则表达式的基础语法，否则很难说不是欠下的又一笔技术债。

使用正则表达式构造出符合规则的文本结构后，肉眼核对一遍是否仍存在文字错误。我曾经想用AI来替代这一步，甚至为此写了一段Prompt。无奈AI不争气，勤劳的时候提出一堆并不存在的问题，懒惰的时候甚至发现不了我草草一眼就看出的错误。不过在某种程度上，核对目录也可看作《如何阅读一本书》所称检视阅读的一部分，自己浏览一遍目录，似乎也不是太大的损失。

再下一步是把经过OCR得到的书籍目录页中的图书页码转换为PDF页码，即调整二者间的偏移量。这一步，Minja的文章用Numbers表格中的函数实现，本文则制作了一个LaunchBar动作来调节页码的偏移量，同时检测OCR得到的页码是否存在问题。其原因是，就我自己的经验来说，人眼对文字比较敏感，对数字大小不敏感，检查目录的同时检查数字页码是否存在问题有些强人所难。因此数字的部分让机器来核对更加适合，也更能胜任。该动作可以检查页码是否存在以下三类问题：1\.内容行不以数字结尾（可能是由于错误的换行、OCR没有正确识别出数字等）；2\.页码倒挂，即下一行的页码数值小于上一行页码数值（可能是由于重复粘贴、OCR数字识别错误等）；3\.页码差值过大（提示粘贴的时候漏了一页目录的可能性），我将提醒阈值设为下一行的页码与上一行页码的差值大于等于20页，你也可以在动作内部自行修改。

将bm文件发送到"Page Offset Adjustment for bm"，即可运行该动作。交互页面采用唤出一个新终端窗口的方案，你需要先在终端窗口中按提示输入页码偏移量，随后动作会运行，调整页码并提示页码数值的以上三项异常，便于逐项排查和解决问题。动作运行完毕后，会在原bm文件的目录下生成一个新的，已调整页码偏移量的bm文件。

最后，视需要在新的bm文件中增加封面、版权、前言、目录等不在目录页中的条目，保存为以\.bm结尾的纯文本文件。

## 第二步：将文字版目录注入PDF文件

这一步实现起来相对复杂，操作起来却非常简单，在LaunchBar中先选中bm文件，再选中PDF文件，发送到LaunchBar动作“Bookmark to PDFdir via pyPDF2”即可。

如果在这一步报错，你可能需要检查： \- 你的设备上是否安装了Python3； \- 你的Python3解释器的路径是不是/usr/local/bin/python3，如果不是，需要在LaunchBar脚本开头的Shebang行中把对应路径替换为你的Python3解释器路径； \- 你的设备上是否安装了PyPDF2，以及是否安装在你的Python3解释器的对应路径下。

若动作成功运行，会在原PDF所在的文件夹下生成一个新的带目录的PDF文件，并自动打开，你可以检验生成的目录是否存在问题，即点击对应的目录，看是否能够跳转到对应的内容。如果不能，除了可能是bm文件的页码偏移量未设置正确外，还有可能是书籍PDF文件本身的问题。

制作目录的过程也是检验书籍内容完整性的过程。我曾遇到过有100页重复页的PDF、大量漏页的PDF、奇偶数页面顺序错乱的PDF、前手好事之徒删除了空白页导致目录页码对不上的PDF。在按书籍目录页生成目录并注入文件后，这些问题马上原形毕露，很容易被发现。这些问题如果不是在书籍收录至数据库前（制作目录时）发现，而是在入库后甚至是阅读了一半时才发现，可能导致所有（更糟糕的情况是只有部分）引用至该书的链接都要修改，解决起来非常麻烦。

## 小结：书籍PDF目录制作指引

- 逐页复制目录页文字进空白文档；

- 繁体转简体；

- 删除所有行内空白字符，正则表达式\\s或\[^\\n\\S\]（“或”之前的表达式适用于VSCode，“或”之后的表达式适用于BBEdit，如没有“或”则二者都适用，下同）；

- 浏览，手动修正OCR错误，重复错误内容可批量替换；

- 补充篇章页码（若篇章无页码）；

- 页码前加空格，正则搜索\(\\d\+\)$，替换为$1或\\1；

- 调整层级结构，注意低层级用制表符表示；同时给编章节后加空格。正则搜索\(第\.\{1,3\}\[编章节\]\)，替换为$1或\\t\\1；注意有时手动更快（目录跳层级的情况）；

- 核对目录文字；

- 调整页码偏移量：运行LaunchBar动作“Page Offset Adjustment for bm”；

- 增加不在目录页中的条目（封面、版权、前言、目录等），保存bm文件；

- 依次选中bm、PDF文件，运行LaunchBar动作“Bookmark to PDFdir via pyPDF2”；

- 检查生成的PDF目录是否对应。

提醒： 1\. 在进行所有的全局替换前，都建议先确认一遍，这时BBEdit的Findall视图就显示出其优越性； 2\. 上述顺序和每一步的操作并不绝对，只是一个较为通用的流程，视书籍特点可以进行调整。

---

[前往标准阅读页](https://utgd.net/article/20875?audience=web)
