查看: 2985|回复: 3

Python按页数拆分PDF并提取指定范围内容的实现方法

[复制链接]
发表于 昨天 14:00 | 显示全部楼层 |阅读模式
在日常办公和开发任务中,PDF按页拆分是一个非常常见的需求:从200页的培训教材里抽取15到30页,或者将合同按章节拆成独立文件分发给不同负责人。手动在阅读器里选择页面再导出的方式效率很低,而且每次只能处理一段范围。用Python脚本可以直接完成指定页码提取、逐页拆分、按块大小拆分以及按书签拆分为章节,适合批量处理和自动化流程。

实现思路并不复杂:通过pypdf库的PdfReader读取源PDF页面,再用PdfWriter将选中的页面写入新的PDF文件。整个过程只涉及页面对象的复用和写入操作,不涉及重新渲染,所以速度很快,也不会损失原始PDF内容。

环境准备方面,需要安装pypdf,可以执行以下命令:
  1. pip install pypdf
复制代码
如果还需要解析非标准书签,可以额外安装pdfplumber:
  1. pip install pdfplumber
复制代码

下面给出完整实现代码,包含四种拆分方式:提取指定页码范围、逐页拆分、按固定页数分块、按书签章节拆分。
  1. import os
  2. from pypdf import PdfReader, PdfWriter
  3. from pathlib import Path
  4. # ==================== 方案 1:提取指定页码范围 ====================
  5. def extract_pages(input_pdf, start_page, end_page, output_file):
  6.     """
  7.     从 PDF 中提取指定范围的页面
  8.     参数:
  9.     input_pdf: 源 PDF 文件
  10.     start_page: 起始页码(从 1 开始)
  11.     end_page: 结束页码(包含)
  12.     output_file: 输出文件名
  13.     """
  14.     reader = PdfReader(input_pdf)
  15.     total = len(reader.pages)
  16.     # 验证页码范围
  17.     if start_page < 1 or end_page > total:
  18.         print(f"错误: 文件共 {total} 页,请求范围 {start_page}-{end_page}")
  19.         return
  20.     writer = PdfWriter()
  21.     for i in range(start_page - 1, end_page):  # 转为 0-based 索引
  22.         writer.add_page(reader.pages[i])
  23.     with open(output_file, 'wb') as f:
  24.         writer.write(f)
  25.     print(f"已提取第 {start_page}-{end_page} 页 → {output_file}")
  26. # ==================== 方案 2:逐页拆分 ====================
  27. def split_pdf_by_page(input_pdf, output_dir="逐页拆分"):
  28.     """
  29.     将 PDF 的每一页拆分为独立文件
  30.     """
  31.     reader = PdfReader(input_pdf)
  32.     output_path = Path(output_dir)
  33.     output_path.mkdir(parents=True, exist_ok=True)
  34.     base_name = Path(input_pdf).stem
  35.     for i, page in enumerate(reader.pages):
  36.         writer = PdfWriter()
  37.         writer.add_page(page)
  38.         output_file = output_path / f"{base_name}_第{i+1}页.pdf"
  39.         with open(output_file, 'wb') as f:
  40.             writer.write(f)
  41.     print(f"逐页拆分完成: {len(reader.pages)} 页 → {output_dir}/")
  42. # ==================== 方案 3:按指定块大小拆分 ====================
  43. def split_pdf_by_chunks(input_pdf, chunk_size, output_dir="分块拆分"):
  44.     """
  45.     按指定页数拆分(如每 10 页一个文件)
  46.     参数:
  47.     input_pdf: 源 PDF
  48.     chunk_size: 每个文件的页数
  49.     output_dir: 输出目录
  50.     """
  51.     reader = PdfReader(input_pdf)
  52.     output_path = Path(output_dir)
  53.     output_path.mkdir(parents=True, exist_ok=True)
  54.     total = len(reader.pages)
  55.     base_name = Path(input_pdf).stem
  56.     chunk_count = 0
  57.     for start in range(0, total, chunk_size):
  58.         end = min(start + chunk_size, total)
  59.         writer = PdfWriter()
  60.         for i in range(start, end):
  61.             writer.add_page(reader.pages[i])
  62.         output_file = output_path / f"{base_name}_第{start+1}-{end}页.pdf"
  63.         with open(output_file, 'wb') as f:
  64.             writer.write(f)
  65.         chunk_count += 1
  66.         print(f"已拆分: 第 {start+1}-{end} 页")
  67.     print(f"\n分块拆分完成: {chunk_count} 个文件")
  68. # ==================== 方案 4:按章节/书签拆分 ====================
  69. def split_pdf_by_outline(input_pdf, output_dir="按章节拆分"):
  70.     """
  71.     按 PDF 的书签(大纲)拆分为独立章节文件
  72.     """
  73.     reader = PdfReader(input_pdf)
  74.     output_path = Path(output_dir)
  75.     output_path.mkdir(parents=True, exist_ok=True)
  76.     base_name = Path(input_pdf).stem
  77.     outline = reader.outline
  78.     # 提取书签信息
  79.     bookmarks = []
  80.     for item in outline:
  81.         if isinstance(item, dict) and '/Title' in item:
  82.             title = item['/Title']
  83.             page_num = None
  84.             if '/Dest' in item:
  85.                 dest = item['/Dest']
  86.                 if isinstance(dest, list) and dest[0]:
  87.                     page_num = reader.get_destination_page_number(item)
  88.             bookmarks.append({'title': title, 'page': page_num})
  89.     if not bookmarks:
  90.         print("该 PDF 没有书签/大纲")
  91.         return
  92.     # 按书签拆分
  93.     for idx, bm in enumerate(bookmarks):
  94.         if bm['page'] is None:
  95.             continue
  96.         start = bm['page']
  97.         # 下一个书签的页码 - 1 作为结束
  98.         if idx + 1 < len(bookmarks) and bookmarks[idx + 1]['page'] is not None:
  99.             end = bookmarks[idx + 1]['page'] - 1
  100.         else:
  101.             end = len(reader.pages) - 1
  102.         writer = PdfWriter()
  103.         for i in range(start, end + 1):
  104.             writer.add_page(reader.pages[i])
  105.         safe_title = bm['title'].replace('/', '_').replace('\\', '_')
  106.         output_file = output_path / f"{safe_title}.pdf"
  107.         with open(output_file, 'wb') as f:
  108.             writer.write(f)
  109.         print(f"已拆分章节: {bm['title']} (第 {start+1}-{end+1} 页)")
  110.     print(f"\n按章节拆分完成: {output_dir}/")
  111. # ==================== 使用示例 ====================
  112. if __name__ == "__main__":
  113.     PDF_FILE = "培训教材_200页.pdf"
  114.     # 方案 1:提取第 15-30 页
  115.     extract_pages(PDF_FILE, 15, 30, "培训教材_核心章节.pdf")
  116.     # 方案 2:逐页拆分
  117.     # split_pdf_by_page(PDF_FILE)
  118.     # 方案 3:每 10 页一个文件
  119.     # split_pdf_by_chunks(PDF_FILE, chunk_size=10)
  120.     # 方案 4:按书签拆分
  121.     # split_pdf_by_outline(PDF_FILE)
复制代码

代码说明:extract_pages函数的start_page和end_page均从1开始计数,内部通过start_page-1转换为pypdf的0基索引。输出前会校验页码范围,避免越界。逐页拆分会在指定目录下生成“原文件名_第N页.pdf”的独立文件。分块拆分需要传入chunk_size参数,例如chunk_size=10表示每10页生成一个文件,最后一个文件可能不足10页。按书签拆分则依赖PDF内置大纲,提取出章节标题和对应起始页码,并把每个书签到下一个书签之间的页面归入同一章节;标题中的特殊字符会被替换为下划线,避免生成非法文件名。

实际使用中可能遇到两个典型问题。

问题一:提取后页面空白。这通常出现在扫描件或加密PDF上,pypdf无法直接读取加密内容。可以在创建reader之后先判断并解密:
  1. reader = PdfReader(input_pdf)
  2. if reader.is_encrypted:
  3.     reader.decrypt("")
复制代码
空字符串解密适用于无密码的加密文件;如果PDF本身有打开密码,则需要传入对应密码。

问题二:按书签拆分时没有提取到书签。部分PDF的书签可能存储在非标准位置,pypdf的outline属性无法读取。此时可以改用pdfplumber尝试获取更完整的目录信息:
  1. import pdfplumber
  2. with pdfplumber.open(pdf_file) as pdf:
  3.     toc = pdf.toc
  4.     print(toc)
复制代码
pdfplumber会尽量解析文档结构中的书签数据,拿到目录后可以再结合页码信息自行实现拆分逻辑。

总结一下四种方式的特点:
  1. 拆分方式          函数                     说明
  2. 指定范围          extract_pages()          提取 start-end 页
  3. 逐页拆分          split_pdf_by_page()      每页一个文件
  4. 分块拆分          split_pdf_by_chunks()    每 N 页一个文件
  5. 按书签拆分        split_pdf_by_outline()   按章节拆分
复制代码
如果你的业务场景是“只取中间某一段”,用extract_pages最直接;如果需要将整份文档拆成多个固定页数的小文件,split_pdf_by_chunks更合适;而按书签拆分则适合结构清晰的电子书或技术手册,可以一次性得到按章节组织的独立PDF。这套逻辑同样可以扩展到批量处理多个PDF文件,只需在外层加一个文件遍历循环即可。
回复

使用道具 举报

发表于 昨天 19:00 | 显示全部楼层

Re: Python按页数拆分PDF并提取指定范围内容的实现方法

感谢分享,正好手头有份几百页的PDF需要按章节拆,这个思路很实用。看了下代码,方案一到方案三都明白了,不过标题里提到了按书签拆分,但代码好像只到方案三分块拆分就结束了,是不是没贴完?方便补一下第四种的实现吗?另外问一下,提取指定页码范围时如果起始页大于结束页,目前好像没做这个校验,实际用的时候可能容易出现边界情况。
回复 支持 反对

使用道具 举报

发表于 昨天 19:00 | 显示全部楼层

Re: Python按页数拆分PDF并提取指定范围内容的实现方法

楼主这个拆分思路很实用,正好最近在整理技术文档,每次用阅读器手动抽页面真的痛苦,你这个脚本可以直接搞定。方桉2和方桉3的命名逻辑也很清楚,输出文件名直接带着页码范围,方便后面归档。 另外想请教下,如果源PDF有加密需要密码,pypdf直接读会报错吗?还有方桉4按书签拆分,我看代码好像没贴完,额外装pdfplumber是为了解析那种书签层级不规范的情况吗?期待楼主补充一下,谢谢分享!
回复 支持 反对

使用道具 举报

发表于 昨天 19:10 | 显示全部楼层

Re: Python按页数拆分PDF并提取指定范围内容的实现方法

看到这个帖子真的挺有用的,最近刚好在处理一份几百页的PDF,正愁怎么按章节拆开分发呢。代码思路很清晰,从指定范围到逐页、分块都覆盖了,直接复制就能用。而且不重新渲染这点很关键,速度快还不损伤内容。期待看到方案4按书签拆分的实现,那个对按章节整理材料应该最方便。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-8-28 17:11 , Processed in 0.022405 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部