Python 一键拆分 PDF:按章节建文件夹并导出单页(支持书签与正文识别)
该工具基于 Python 实现 PDF 按章节自动拆分功能。优先通过 PDF 书签识别章节标题,若无书签则扫描正文文本匹配正则表达式。支持按章节创建文件夹,导出整章 PDF 及单页 PDF。适用于电子书处理、AI 文档分析等场景。需安装 pypdf 库,支持 Windows 系统。

该工具基于 Python 实现 PDF 按章节自动拆分功能。优先通过 PDF 书签识别章节标题,若无书签则扫描正文文本匹配正则表达式。支持按章节创建文件夹,导出整章 PDF 及单页 PDF。适用于电子书处理、AI 文档分析等场景。需安装 pypdf 库,支持 Windows 系统。

经常需要将电子书交给 AI 做总结或问答,但很多 PDF 体积大、页数多。如果按章节拆开再喂给 AI,手动操作非常耗时。
本工具实现了以下功能:
拆分后的输出结构如下:
01_第 1 章_xxx/
01_第 1 章_xxx.pdf(整章)p0001.pdf p0002.pdf ...(单页)02_第 2 章_xxx/
02_第 2 章_xxx.pdfp00xx.pdf ...既保留'整章',也能拿到'每一页'。
将脚本直接运行即可。
点击按钮 「1. 请选择你的 PDF 文件」,选中要处理的 PDF。
点击 「2. 请选择输出目录并开始拆分」,选择一个输出文件夹,工具就会开始处理,并在下方日志区域输出进度。


pypdf安装依赖:
pip install pypdf
**注意:**如果 PDF 是扫描版图片(没有可提取的文字),正文识别可能会失败,这种情况需要先 OCR,否则工具无法'读到章节标题'。
为了让工具对不同 PDF 更稳定,设计了两种识别策略:
如果 PDF 自带书签(目录),直接读取书签并找出匹配 '第 X 章' 的标题,然后拿到对应页码作为章节起点。
**优点:**速度快、准确率高。
如果没有书签,逐页提取文本,然后用正则匹配 第 X 章,并做了两层过滤:
标题......页码 的目录行**优点:**即使没书签,只要正文可提取文字,也能拆分。
代码最上面配置区留有两个重点:
CHAPTER_PATTERN:章节匹配正则(默认支持:第 1 章 / 第 1 章 / 第一章 / 第十四章)EXPORT_SINGLE_PAGES:是否导出单页 PDF(默认 True)不想生成单页 PDF,把 EXPORT_SINGLE_PAGES = False 就行。
通常是三类原因:
m.start() > 400)**解决思路:**先确认 PDF 是不是可复制文字;不行就 OCR;标题格式不一致就改正则。
如果 PDF 正文排版很特殊(比如章标题不在页首、页眉重复干扰),可能会误判。这种情况优先找'带书签'的版本,或者适当调整正文识别的阈值。
在 sanitize_filename() 里把 Windows 不允许的字符替换成 _,避免出现'无法创建文件'的问题。
完整代码如下,可直接复制运行:
# -*- coding: utf-8 -*-
import re
from pathlib import Path
from pypdf import PdfReader, PdfWriter
import tkinter as tk
from tkinter import filedialog, messagebox
# ================= 配置区域 =================
# 章节标题匹配规则(适合:第 1 章 / 第 1 章 / 第一章 / 第十四章 等)
CHAPTER_PATTERN = re.compile(r"第\s*[一二三四五六七八九十百千 0-9]+\s*章[^\n\r]*")
# 是否额外按'页'拆成单页 PDF
EXPORT_SINGLE_PAGES = True
# ==========================================
# GUI 全局对象占位
root = None
log_text = None
def sanitize_filename(name: str) -> str:
"""
去掉 Windows 不支持的文件名字符。
"""
return re.sub(r'[\\/:*?"<>|]', "_", name)
# ---------- 方案一:优先从 PDF 书签 (outline) 中找章节 ----------
def find_chapters_from_outline(reader: PdfReader):
"""
从 PDF 书签 (outline) 中找出章节:
- 遍历所有书签
- 标题里匹配 CHAPTER_PATTERN(第 X 章……)
- 获取对应页码
返回:[{title: '第 1 章 xxx', start: 0}, ...]
"""
chapters = []
# 兼容不同版本的 pypdf:有的叫 outline,有的叫 outlines
try:
outlines = reader.outline
except Exception:
try:
outlines = reader.outlines
except Exception:
outlines = None
if not outlines:
return []
():
item items:
(item, ):
walk(item)
:
:
title = item.title
AttributeError:
title = (item)
(title, ):
title = (title)
CHAPTER_PATTERN.search(title):
:
page_num = reader.get_destination_page_number(item)
Exception:
chapters.append({: title.strip(), : page_num})
walk(outlines)
unique = {}
ch chapters:
ch[] unique:
unique[ch[]] = ch
chapters = (unique.values(), key= c: c[])
chapters
():
chapters = []
num_pages = (reader.pages)
i (num_pages):
page = reader.pages[i]
text = page.extract_text()
text.strip():
head = text[:]
head head head:
m CHAPTER_PATTERN.finditer(text):
m.start() > :
lines = text.splitlines()
line_of_match =
char_pos =
line lines:
next_pos = char_pos + (line) +
m.start() < next_pos:
line_of_match = line
char_pos = next_pos
re.search(, line_of_match):
title = m.group().strip()
(ch[] == i ch chapters):
chapters.append({: title, : i})
chapters
():
chapters = find_chapters_from_outline(reader)
chapters:
logger()
chapters
logger()
chapters = find_chapters_from_text(reader)
chapters
():
idx, ch (chapters):
start = ch[]
idx < (chapters) - :
end = chapters[idx + ][] -
:
end = num_pages -
ch[] = end
chapters
():
logger :
logger =
pdf_path = Path(pdf_path)
output_root = Path(output_root)
pdf_path.exists():
msg =
logger(msg)
FileNotFoundError(msg)
reader = PdfReader((pdf_path))
num_pages = (reader.pages)
book_name = pdf_path.name
logger()
logger()
chapters = find_chapters(reader, logger=logger)
chapters:
msg =
logger(msg)
ValueError(msg)
logger()
idx, ch (chapters, start=):
logger()
chapters = fill_chapter_ranges(chapters, num_pages)
output_root.mkdir(parents=, exist_ok=)
logger()
idx, ch (chapters, start=):
title = ch[]
start_page = ch[]
end_page = ch[]
page_count = end_page - start_page +
safe_title = sanitize_filename(title)
chapter_dir = output_root /
chapter_dir.mkdir(parents=, exist_ok=)
logger()
logger()
logger()
logger()
chapter_writer = PdfWriter()
p (start_page, end_page + ):
chapter_writer.add_page(reader.pages[p])
chapter_pdf_path = chapter_dir /
(chapter_pdf_path, ) f:
chapter_writer.write(f)
logger()
EXPORT_SINGLE_PAGES:
p (start_page, end_page + ):
writer = PdfWriter()
writer.add_page(reader.pages[p])
page_label =
single_page_path = chapter_dir / page_label
(single_page_path, ) f:
writer.write(f)
logger()
logger()
logger()
selected_pdf_file =
selected_output_dir =
():
log_text :
(msg)
log_text.config(state=)
log_text.insert(tk.END, msg + )
log_text.see(tk.END)
log_text.config(state=)
root :
root.update_idletasks()
():
selected_pdf_file
path = filedialog.askopenfilename(
title=,
filetypes=[(, ), (, )]
)
path:
selected_pdf_file = path
label_pdf.config(text=)
append_log()
():
selected_output_dir, selected_pdf_file
selected_pdf_file:
messagebox.showwarning(, )
path = filedialog.askdirectory(title=)
path:
selected_output_dir = path
label_output.config(text=)
append_log()
append_log()
append_log()
:
split_pdf_by_chapters(selected_pdf_file, selected_output_dir, logger=append_log)
messagebox.showinfo(, )
Exception e:
append_log()
messagebox.showerror(, )
__name__ == :
root = tk.Tk()
root.title()
root.geometry()
root.resizable(, )
btn_frame = tk.Frame(root)
btn_frame.pack(padx=, pady=, fill=)
btn_pdf = tk.Button(btn_frame, text=, command=choose_pdf)
btn_pdf.pack(fill=)
label_pdf = tk.Label(btn_frame, text=, anchor=)
label_pdf.pack(fill=, pady=(, ))
btn_output = tk.Button(btn_frame, text=, command=choose_output_and_run)
btn_output.pack(fill=)
label_output = tk.Label(btn_frame, text=, anchor=)
label_output.pack(fill=, pady=(, ))
log_frame = tk.Frame(root)
log_frame.pack(padx=, pady=, fill=, expand=)
log_text = tk.Text(log_frame, state=)
log_text.pack(side=, fill=, expand=)
scrollbar = tk.Scrollbar(log_frame, command=log_text.yview)
scrollbar.pack(side=, fill=)
log_text.config(yscrollcommand=scrollbar.)
append_log()
append_log()
root.mainloop()

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online