批量拆分 PDF:一键拆成单页 / 按页码区间抽取,70 行 pypdf 脚本

2026-10-05 · 喵窝资源铺

两个高频场景:① 一个 5 页的合同只要发第 2-4 页给别人;② 一批 PDF 要全部拆成单页分发盖章。Adobe Acrobat 收费、WPS 得一页页另存。这篇给一个 pypdf 的批量脚本:递归整个目录,默认整本拆单页,加 --pages 2-4,7 就只抽出指定页;坏文件不会让脚本崩,全部记进报告。文中代码实跑验证,真实输出贴在下面。

它做的三件事

需求脚本的处理
整本拆单页每页输出 文件名_p1.pdf、_p2.pdf……方便逐页分发
按区间抽取--pages 2-4,7 支持区间和单页混写,输出 文件名_p2-p4.pdf
烂文件不崩损坏 PDF 自动跳过并写进报告;页码超界明确报错拒绝,不静默给错结果

两个实跑才抓到的坑

坑1:在 PDF 里画中文,Helvetica 会变成一排 ■。写测试文件时用 drawString(100, 700, "合同第1页"),生成的 PDF 里文字全是 ■■■■——Helvetica 是标准 14 字体之一,只嵌了西文字形,没有 CJK 字体。要么用 reportlab 注册 TTF 中文字体,要么造测试文件时干脆用 ASCII 文字(本文的测试就用了文件名拼音)。真实拆分不受影响(拆分是复制整页,不重新画字),但只要你想往 PDF 里写中文,就会撞上这个。
坑2:页码区间必须校验上限,不能静默截断。用户传 --pages 2-99 而文件只有 5 页,如果悄悄输出 2-5 页,用户可能以为拿到了全部——发出去少页是要出事的。正确做法是 b > total 直接 raise ValueError,让这条文件进「[跳过]」报告,用户一眼看到哪个文件页数对不上。宁可明确失败,不要静默出错。

脚本全文(pdf_split.py)

# -*- coding: utf-8 -*-
"""批量 PDF 拆分:每个文件一键拆成单页 PDF,或按页码区间抽出来另存。
用法:
  python3 pdf_split.py <输入目录> <输出目录>                 # 每页一个 PDF
  python3 pdf_split.py <输入目录> <输出目录> --pages 2-4,7   # 只抽出指定页
只依赖 pypdf。坏文件不崩,记进报告。
"""
import os, sys, re
from pypdf import PdfReader, PdfWriter

def parse_ranges(spec, total):
    """把 '2-4,7' 解析成页码列表(1-based)。"""
    pages = []
    for part in spec.split(","):
        m = re.match(r"^(\d+)-(\d+)$", part.strip())
        if m:
            a, b = int(m.group(1)), int(m.group(2))
            if a > b or b > total:
                raise ValueError(f"区间 {part} 超出总页数 {total}")
            pages.extend(range(a, b + 1))
        else:
            p = int(part)
            if p > total:
                raise ValueError(f"页码 {p} 超出总页数 {total}")
            pages.append(p)
    return pages

def main():
    args = [a for a in sys.argv[1:] if not a.startswith("--")]
    spec = None
    if "--pages" in sys.argv:
        spec = sys.argv[sys.argv.index("--pages") + 1]
    src_dir, out_dir = args[0], args[1]
    os.makedirs(out_dir, exist_ok=True)
    report, out_total = [], 0

    for root, _dirs, files in os.walk(src_dir):
        for fn in sorted(files):
            if not fn.lower().endswith(".pdf"):
                continue
            path = os.path.join(root, fn)
            base = os.path.splitext(fn)[0]
            try:
                reader = PdfReader(path)
            except Exception as e:
                report.append(f"[跳过] {fn}: 无法解析 ({type(e).__name__})")
                continue
            if reader.is_encrypted:
                try:
                    reader.decrypt("")
                except Exception:
                    report.append(f"[跳过] {fn}: 加密 PDF")
                    continue
            total = len(reader.pages)
            if spec:
                try:
                    pages = parse_ranges(spec, total)
                except ValueError as e:
                    report.append(f"[跳过] {fn}: {e}")
                    continue
                w = PdfWriter()
                for p in pages:
                    w.add_page(reader.pages[p - 1])
                out_name = f"{base}_p{pages[0]}-p{pages[-1]}.pdf" if len(pages) > 1 else f"{base}_p{pages[0]}.pdf"
                with open(os.path.join(out_dir, out_name), "wb") as f:
                    w.write(f)
                report.append(f"[OK] {fn}: 抽出 {pages} -> {out_name}")
                out_total += len(pages)
            else:
                for i, page in enumerate(reader.pages, 1):
                    w = PdfWriter()
                    w.add_page(page)
                    with open(os.path.join(out_dir, f"{base}_p{i}.pdf"), "wb") as f:
                        w.write(f)
                report.append(f"[OK] {fn}: 拆成 {total} 个单页 PDF")
                out_total += total

    print("=== PDF 批量拆分报告 ===")
    for line in report:
        print(line)
    print(f"合计输出: {out_total} 个 PDF")

if __name__ == "__main__":
    main()

实测输出

造了 3 个测试文件:5 页的「合同多页」、1 页的「单页便签」、还有一个截断损坏的假 PDF。

=== PDF 批量拆分报告 ===
[OK] 单页便签.pdf: 拆成 1 个单页 PDF
[OK] 合同多页.pdf: 拆成 5 个单页 PDF
[跳过] 坏文件.pdf: 无法解析 (PdfStreamError)
合计输出: 6 个 PDF

输出目录里 合同多页_p1.pdf 到 _p5.pdf 共 5 个单页,逐页打开 extract_text() 核对,内容分别是 page 1 到 page 5,没有串页。

按区间抽取 --pages 2-4:

=== PDF 批量拆分报告 ===
[跳过] 单页便签.pdf: 区间 2-4 超出总页数 1
[OK] 合同多页.pdf: 抽出 [2, 3, 4] -> 合同多页_p2-p4.pdf
[跳过] 坏文件.pdf: 无法解析 (PdfStreamError)
合计输出: 3 个 PDF

抽出的 合同多页_p2-p4.pdf 用 PdfReader 复核正好 3 页,内容为 page 2/3/4。超界区间 --pages 2-99 被明确拒绝:[跳过] 合同多页.pdf: 区间 2-99 超出总页数 5——不是悄悄给你 5 页了事。

不想自己跑脚本?喵窝资源铺提供人工代跑+交付服务(miaowoo.900890.xyz):把你的 PDF 打包发来,说明要拆单页还是抽哪几页即可。由 AI 完成脚本与初验、人工复核后交付,9.9 元起。虚拟物品,交付后不退款,介意勿拍。

← 返回喵窝资源铺 · 相关:批量 PDF 去空白页+加水印 · 相关:批量合并 PDF