我是怎么从国标电子书库抓取 PDF 并整理书签的
国标电子书库(ebook.chinabuilding.com.cn)仅提供在线阅读 PDF 文档部分页面,未开放全文下载。std.py 通过爬取网站获取 PDF 文件,本质上是一个 WebCrawler。
抓取的 PDF 自带网站提供的书签,但格式不够规整,也没有章节分组层级。std.py bookmark 使用格式化规则处理书签,是常规工作流中的必要步骤。扫描版 PDF 需借助 std-ocr.py 半自动创建书签。std.py copy-bookmark 与 std-copy-bookmark.ps1 针对另一类场景:当用户已完成书签整理,但需通过 Acrobat 等 PDF 编辑软件进行删除隐藏信息、统一图片分辨率等清理(Clean Up)操作时,原有书签可能会一并丢失,这两个工具用于将书签从备份恢复至处理后的文件。
三个脚本相互独立,按场景选用。
准备
需要 Python ≥ 3.13,建议使用 uv。两个 Python 脚本均通过文件头部的 PEP 723 元信息声明依赖,uv run 会自动建立临时环境完成安装,无需配置 pip 或 venv。
uv run std.py <command> [args]
uv run std-ocr.py
如不使用 uv,亦可手动执行 pip install httpx parsel pymupdf keyboard pyperclip 安装相应依赖。
ps1 脚本需要 PowerShell 7+。
选择指引
| 场景 | 工具 |
|---|---|
| 下载书籍/整理书签 | std.py download / std.py bookmark |
| 为扫描版 PDF 半自动创建书签 | std-ocr.py |
| Acrobat 清理操作后恢复已整理的书签(单文件) | std.py copy-bookmark |
| Acrobat 清理操作后恢复已整理的书签(批量) | std-copy-bookmark.ps1("发送到"菜单) |
下文按脚本逐一展开。
std.py
提供三个子命令:download、bookmark、copy-bookmark。
download 负责抓取 PDF 文件;bookmark 实现抓取国标电子书库网站对应目录经格式化处理后覆盖 PDF 已有书签;copy-bookmark 在分页一致的两个 PDF 之间迁移书签。
Book ID 的获取
打开任意书籍详情页,URL 中 bookID= 参数对应的数字即为 Book ID:
https://ebook.chinabuilding.com.cn/zbooklib/book/detail/show?SiteID=1&bookID=12345
下文命令中的 --book-id 均指该值。
用法
# 抓取 PDF(保存为 12345.pdf)
uv run std.py download --book-id 12345
# 将当前目录下 12345.pdf 自带的书签覆盖为国标电子书库网站对应目录经格式化处理后的版本
uv run std.py bookmark --book-id 12345
# 在两个 PDF 之间迁移书签
uv run std.py copy-bookmark --source 12345.pdf --target 12345-cleaned.pdf
bookmark 子命令按约定处理当前目录下的 {book_id}.pdf,因此无需 --input 参数——download 完成后可直接接续执行 bookmark。
实现要点
- PDF 下载链接的获取方式较为隐晦:试读页中
<link rel="stylesheet">引用的 css 文件,将扩展名替换为.pdf即为 PDF 真实下载地址。该逻辑位于get_pdf_url。 - 目录从详情页 DOM 提取:
get_toc中div.m-t-md.font14 div.clearfix div::text这一 CSS 选择器对应文本节点按"标题、页码、标题、页码……"两两排列。 - 网站改版会导致脚本失效:上述两个抓取点是脚本失效的最主要可能。出现问题时优先检查这两处。
bookmark直接覆盖 PDF 自带书签:PyMuPDF 的set_toc为替换语义而非追加。若需保留此前手动整理的版本,应先通过copy-bookmark备份至他处。- 目录条目均按一级书签写入,不区分章/节层级。国标"第 X.Y.Z 条"扁平展开通常更便于查阅,如需分级,应修改
get_toc中toc.append([1, title, page])的第一个参数。 - 写入采用 PyMuPDF 的
saveIncr,原地修改,不重写整个 PDF。bookmark与copy-bookmark均采用此方式。如需保留原文件,应自行预先备份。 format_title包含若干清理逻辑:将半角括号、冒号转为全角,在章节编号后补充空格等。如遇某书目录排版异常,通常是TOC_FORMAT_RULES未覆盖该模式,添加相应正则即可。
std-ocr.py
国标电子书库中部分文档为扫描版,详情页缺少结构化目录,std.py bookmark 对此类书籍无效。本脚本提供半自动方案:用户对照 PDF 目录页逐条操作,按 F11 触发截屏 OCR 并自动建立书签。
该办法可广泛应用到国标电子书库外用户可收集到的各种 PDF 文档。
前置条件
- Umi-OCR 已安装并启动,且开启 HTTP 服务(设置 → 软件设置 → 启用 HTTP 服务,默认端口 1224)。
- Adobe Acrobat 打开目标 PDF,保持书签面板可见,并选中拟作为父级的书签条目(新建书签将创建于其下)。
- Windows 平台。
keyboard库在 Linux 下需要 root 权限,macOS 不予支持。
用法
uv run std-ocr.py
启动后常驻后台监听以下热键:
| 热键 | 作用 |
|---|---|
| F11 | 截屏 OCR → 自动新建书签 |
| F12 | 切换文本格式化开关 |
| Ctrl + C | 退出 |
工作流程:
- 在 Acrobat 中翻至目录条目对应的实际页面(非目录页本身——书签的目标位置即为按下 F11 时当前显示的页)。
- 按 F11 进入 Umi-OCR 截图模式。
- 框选目录页中对应条目的文字,松开鼠标。
- 脚本依次发送
Ctrl+B新建书签 → 将 OCR 结果粘贴至标题栏 → 回车确认。 - 翻至下一条对应页面,重复上述步骤。
每次建立书签均需同时满足两个条件:Acrobat 当前页停留于书签目标位置,屏幕上可见对应的目录条目文字。常用的操作姿势是 Acrobat 双窗口或分屏,一侧显示目录页,另一侧翻至目标页。
关于格式化
OCR 结果常包含多余空格与换行(同一行文字识别后字与字之间常被插入空格)。默认开启的格式化逻辑包括:
- 删除全部空白字符;
- 在 "第 X 章/节/条/款/项"、"附录/附件/附表/附图/表/图 + 编号"、
1.2.3形式的数字编号、A.1.2形式的字母编号后补一个空格; - 当识别结果为单独的"条文说明"或"起草说明"时,替换为"附:条文说明""附:起草说明"——此为国标中作为附件标题的常见写法。
如需对某条目录跳过格式化(例如较长的说明性文字),可按 F12 临时关闭,处理完毕后再次按 F12 恢复。
实现要点
- OCR 通过 Umi-OCR 的 HTTP API 调用:
POST /argv+["--screenshot"]触发其截图模式。如 Umi-OCR 监听端口非默认值 1224,应修改UMI_OCR_SERVICE常量。 - 键盘自动化由
keyboard库实现:Ctrl+B后插入PASTE_DELAY = 0.25秒的等待,用于等待 Acrobat 弹出"新建书签"对话框并将焦点定位至标题栏。在性能较低的机器上可能需要增大此值。 - 依赖 Acrobat 的
Ctrl+B快捷键。如使用其他 PDF 编辑器,需修改add_bookmark中的按键序列。 OCR_FORMAT_RULES第一条规则与 std.py 的TOC_FORMAT_RULES故意保持差异——本处为删除全部空白(OCR 噪声较多),后者为压缩为单空格(DOM 文本本身已较为干净)。修改任一侧时不应想当然地将两处合并。
std-copy-bookmark.ps1
std.py copy-bookmark 单次仅处理一对文件。当需要对一批已整理书签的 PDF 统一执行 Acrobat 清理操作时,逐一调用命令的方式效率较低。本 ps1 脚本配合 Windows "发送到"菜单,可在一次操作中处理多个文件。
用法
将 ps1 置于固定位置(例如 D:\tools\std\std-copy-bookmark.ps1),修改文件顶部的 $StdScript 常量使其指向 std.py 的绝对路径,然后注册至"发送到"菜单(步骤见下节)。
注册完成后的日常工作流:
- 复制原 PDF:在资源管理器中执行 Ctrl+C / Ctrl+V,Windows 默认将副本命名为
xxx - 副本.pdf。该副本作为书签源文件保留备用,不应做任何修改。 - 在 Acrobat 中清理原文件(不带"- 副本"后缀的文件),执行删除隐藏信息、降低图片分辨率等操作并保存。此步骤将导致原文件已整理的书签丢失。
- 选中一个或多个清理过的 PDF(不带"- 副本"后缀的),右键 → 发送到 → ps1 快捷方式。
- 脚本对每个目标文件自动定位对应的
xxx - 副本.pdf作为书签源,调用std.py copy-bookmark,成功后删除副本。
最终结果:原文件名保持不变,书签从副本迁移过来,副本被清理。
注册至"发送到"菜单
将
std-copy-bookmark.ps1置于固定位置,例如D:\tools\std\std-copy-bookmark.ps1。编辑 ps1 顶部的
$StdScript常量,使其指向实际的std.py绝对路径。在 ps1 文件上右键 → 发送到 → 桌面快捷方式。
右键新建的快捷方式 → 属性 → "目标"栏改为:
pwsh.exe -NoProfile -ExecutionPolicy Bypass -File "D:\tools\std\std-copy-bookmark.ps1"
重命名为合适的名称,例如"复制 PDF 书签"。
Win+R 打开运行对话框,输入
shell:sendto回车,将该快捷方式剪切至打开的文件夹中。
完成后右键任意 PDF 文件即可在"发送到"菜单中看到该项。
实现要点
- 源文件名后缀硬编码为
- 副本(Windows 简体中文系统默认行为)。如系统语言为其他(例如英文系统中副本命名为xxx - Copy.pdf),应修改脚本中构造$sourceFile的字符串。 - 失败的源文件得以保留,成功的会被立即删除。脚本结束时 Summary 部分将列出全部失败的文件名。
- 退出码检测依赖
$LASTEXITCODE:std.py 中copy_bookmark未使用 try/except 包裹,PyMuPDF 异常会向上传播 → Python 以非零状态退出 → ps1 检测到失败并保留副本。如后续为copy_bookmark增加异常捕获,应确保失败时调用sys.exit(1),否则 ps1 将误判为成功并删除副本。
配合使用
下文列出几种典型工作流的具体步骤。
工作流一:抓取 PDF 和整理书签(详情页有目录)
最常见的情况,两条命令完成:
uv run std.py download --book-id 12345
uv run std.py bookmark --book-id 12345
完成后当前目录下的 12345.pdf 即包含格式化处理后的书签。如需进一步进行章/节分组等人工调整,可直接在 Acrobat 中操作。
工作流二:扫描版 PDF
详情页缺少结构化目录,bookmark 子命令对此类书籍无效,改用 OCR 流程:
# 1. 抓取 PDF
uv run std.py download --book-id 67890
# 2. 启动 OCR 工具(常驻后台)
uv run std-ocr.py
随后在 Acrobat 中打开 67890.pdf,按 std-ocr.py 章节描述的流程逐条建立书签。完成后在 Acrobat 中按 Ctrl+S 保存。
工作流三:批量清理与书签恢复
当一批已整理书签的 PDF 需要在 Acrobat 中执行清理操作时:
- 在资源管理器中全选这批 PDF,Ctrl+C / Ctrl+V,得到一组
xxx - 副本.pdf。 - 在 Acrobat 中依次打开原文件(非副本),执行清理操作并保存。此步骤将导致原文件的书签丢失。
- 全部清理完成后,全选所有原文件(不要选中副本),右键 → 发送到 → 复制 PDF 书签。
- 等待脚本执行完毕,副本自动删除,原文件书签恢复。
期间任何文件清理失败或书签复制失败,对应的副本将被保留,脚本结束时会列出,可手动处理。
故障排查
std.py download 失败或抓不到 PDF 链接。 通常是网站改版所致。检查 get_pdf_url 中的 CSS 选择器是否仍能匹配——打开试读页源码,搜索 link rel="stylesheet",对比路径结构是否变化。
std.py bookmark 抓取的目录为空或错乱。 同样可能是网站改版,应检查 get_toc 中 div.m-t-md.font14 div.clearfix div::text 选择器。如仅为部分条目错位或编号识别异常,则可能是 TOC_FORMAT_RULES 覆盖不全,应补充相应正则。
std-ocr.py 按 F11 无反应。 三种可能原因:(1) Umi-OCR 未启动;(2) HTTP 服务未启用;(3) 端口非默认 1224——应修改 UMI_OCR_SERVICE 常量。控制台日志中出现 "OCR request failed" 对应 (2),出现 httpx 连接错误对应 (1) 或 (3)。
std-ocr.py 建立的书签标题正确,但目标页错误。 按下 F11 时 Acrobat 当前显示的页面即为书签指向的页面——此为 Acrobat Ctrl+B 的行为,非脚本可控。每次按 F11 前应先翻至目标页。
std-copy-bookmark.ps1 报 "std.py not found"。 修改 ps1 顶部的 $StdScript 常量为正确的路径。
std-copy-bookmark.ps1 报 "Source not found"。 检查目标文件所在目录是否存在对应的 xxx - 副本.pdf。如系统语言非简体中文,副本可能采用其他命名(例如 xxx - Copy.pdf),应修改 ps1 中构造 $sourceFile 的字符串。
下载
三个脚本可通过以下链接获取:
百度网盘:https://pan.baidu.com/s/13nvO_EvPq83FUauo96fINg(提取码 uy8v)
使用前请参照"准备"一节安装 uv,并按各脚本"前置条件"小节确认 Umi-OCR、Adobe Acrobat 等依赖项已就位。
本文采用 CC BY-NC-SA 4.0 协议发布,可自由转载、修改,但需保留作者署名、不可用于商业用途、衍生作品需以相同协议发布。