Python 操作 PDF 文件:深度解析与实践
PDF (Portable Document Format) 是一种独立于应用程序、硬件、操作系统和打印设备的文档格式。它将文档的文本、字体、图像、图形、链接等所有元素固定在一个单一的文件中,确保文档在任何地方都能以相同的格式显示。由于其跨平台性和格式固定性,PDF 在文档交换、存档等方面被广泛使用。
虽然 PDF 格式的稳定性带来了便利,但其内部结构相对复杂,包含页面、对象(文本、图像、矢量图)、字体、资源字典、交叉引用表等多种元素。直接手动解析和修改 PDF 字节流几乎是不可能的。幸运的是,Python 生态系统提供了多个功能强大且成熟的库来帮助我们处理 PDF 文件。
我们将深入探讨几个最常用和功能互补的 Python PDF 库:
- PyPDF2: 用于常见的 PDF 操作,如分割、合并、旋转、加密、解密、提取文本等。它是一个纯 Python 库,易于安装和使用,但对某些复杂 PDF 的处理可能有限。
- PyMuPDF (fitz): 基于 MuPDF 库,一个轻量级、高性能的 C 库。PyMuPDF 提供了非常快的 PDF 解析、渲染、文本和图像提取功能,并且支持更底层的访问。它功能强大,速度快,是处理大量 PDF 或需要高级提取/渲染功能的首选。
- reportlab: 主要用于创建 PDF 文件。它提供了一个强大的工具集来在 PDF 页面上绘制文本、图形、表格、图像等,可以用来生成报告、发票、图表等动态内容的 PDF。
- pdfminer.six: 主要用于解析 PDF 文件和提取文本、布局信息等。它能够深入 PDF 的内部结构,提取更详细的文本和对象信息,适合进行 PDF 内容分析。
本文将重点深入 PyPDF2, PyMuPDF 和 reportlab,并通过大量代码示例展示它们的用法。
1. PyPDF2:PDF 的瑞士军刀(基础操作篇)
PyPDF2 是一个用于处理 PDF 文件的纯 Python 库。它提供了一系列高级功能,无需关心 PDF 内部复杂的结构细节,就能完成许多常见的操作。
安装 PyPDF2:
pip install pypdf2
注意:新版本的 PyPDF2 库名称是 pypdf
,旧版本是 PyPDF2
。为了兼容性和推荐使用最新功能,建议安装 pypdf
。本文示例将使用 pypdf
。
安装最新版本:
pip install pypdf
如果你安装的是旧版本的 PyPDF2
,示例代码中的导入语句可能需要相应修改。
PyPDF2(或 pypdf)的核心类是 PdfReader
和 PdfWriter
。PdfReader
用于读取现有的 PDF 文件,而 PdfWriter
用于创建新的 PDF 文件或修改现有文件的内容(通过复制页面等方式)。
1.1. 读取 PDF 文件与获取信息
# pypdf_read_info.py
from pypdf import PdfReader # 从 pypdf 库导入 PdfReader 类
import os # 导入 os 模块,用于文件路径操作
# 创建一个示例 PDF 文件用于测试
# 如果你没有现成的 PDF 文件,可以使用 reportlab 或其他工具先创建一个
# 假设你有一个名为 'example.pdf' 的文件在当前目录下
# 如果没有,可以手动创建一个简单的文本文件,然后另存为 PDF (虽然这样创建的 PDF 结构简单)
# 或者使用以下代码先创建一个简单的 PDF
try:
from reportlab.pdfgen import canvas
def create_simple_pdf(filename="example.pdf"):
c = canvas.Canvas(filename) # 创建一个 PDF canvas 对象
c.drawString(100, 750, "Hello, this is a sample PDF.") # 在指定位置绘制字符串
c.drawString(100, 735, "This is page 1.") # 绘制另一行文本
c.showPage() # 添加一个新页面
c.drawString(100, 750, "This is page 2.") # 在新页面绘制文本
c.save() # 保存 PDF 文件
print(f"Created sample PDF: {
filename}") # 打印创建成功信息
if not os.path.exists("example.pdf"):
create_simple_pdf("example.pdf") # 如果 example.pdf 不存在,则创建
except ImportError:
print("警告: 未安装 reportlab 库。无法自动创建示例 PDF。请手动创建一个 'example.pdf' 文件。") # 如果未安装 reportlab,打印警告
except Exception as e:
print(f"创建示例 PDF 时发生错误: {
e}") # 打印创建示例 PDF 时的错误信息
pdf_path = 'example.pdf' # 要读取的 PDF 文件路径
try:
# 使用 with 语句打开 PDF 文件 (推荐,确保文件自动关闭)
with open(pdf_path, 'rb') as file: # 以二进制只读模式 ('rb') 打开 PDF 文件
reader = PdfReader(file) # 创建一个 PdfReader 对象来读取文件
# 获取 PDF 的页数
num_pages = len(reader.pages) # reader.pages 是一个列表,包含所有页面的对象
print(f"PDF 文件 '{
pdf_path}' 总页数: {
num_pages}") # 打印 PDF 总页数
# 获取 PDF 的元数据 (Metadata)
# 元数据包含作者、标题、主题、关键词等信息
metadata = reader.metadata # reader.metadata 返回一个 Metadata 对象
if metadata:
print("\nPDF 元数据:") # 打印元数据标题
print(f" 作者: {
metadata.author}") # 打印作者信息
print(f" 标题: {
metadata.title}") # 打印标题信息
print(f" 主题: {
metadata.subject}") # 打印主题信息
print(f" 关键词: {
metadata.keywords}") # 打印关键词信息
print(f" 创建者: {
metadata.creator}") # 打印创建者信息
print(f" 生产者: {
metadata.producer}") # 打印生产者信息
print(f" 创建日期: {
metadata.creation_date}") # 打印创建日期
print(f" 修改日期: {
metadata.modification_date}") # 打印修改日期
else:
print("\nPDF 文件没有元数据。") # 如果没有元数据,打印提示
# 访问第一页
first_page = reader.pages[0] # reader.pages[0] 获取第一页的 PageObject
# 获取页面大小 (裁剪框 CropBox 或媒体框 MediaBox)
# MediaBox 定义了页面的物理尺寸
# CropBox 定义了页面的可见区域,默认与 MediaBox 相同
media_box = first_page.mediabox # 获取第一页的 MediaBox
crop_box = first_page.cropbox # 获取第一页的 CropBox
print(f"\n第一页 MediaBox: {
media_box} (左下角 x, y, 右上角 x, y)") # 打印第一页 MediaBox
print(f"第一页 CropBox: {
crop_box} (左下角 x, y, 右上角 x, y)") # 打印第一页 CropBox
# 坐标单位是 PDF 自己的单位,通常 72 单位等于 1 英寸
# 获取页面的旋转角度
rotation = first_page.get('/Rotate') # 通过内部字典键获取旋转角度
if rotation is None:
rotation = 0 # 如果没有 Rotate 键,默认为 0 度
print(f"第一页旋转角度: {
rotation} 度") # 打印第一页旋转角度
except FileNotFoundError:
print(f"错误: 文件 '{
pdf_path}' 未找到。请确保文件存在。") # 打印文件未找到错误
except Exception as e:
print(f"读取 PDF 文件时发生错误: {
e}") # 打印读取 PDF 文件时的其他错误
代码解释:
from pypdf import PdfReader
: 从pypdf
库导入PdfReader
类。import os
: 导入os
模块用于检查文件是否存在和路径操作。create_simple_pdf()
: 一个辅助函数,使用reportlab
库创建一个简单的 PDF 文件,以便后续示例使用。如果未安装reportlab
,会打印警告。with open(pdf_path, 'rb') as file:
: 使用with open()
以二进制只读模式 ('rb'
) 打开 PDF 文件。PDF 文件是二进制的,所以必须使用'rb'
。with
语句确保文件在操作完成后自动关闭。reader = PdfReader(file)
: 创建一个PdfReader
对象,将打开的文件对象作为参数传递。len(reader.pages)
:reader.pages
是一个列表,其中每个元素代表 PDF 中的一页。len()
函数获取列表长度,即 PDF 的总页数。reader.metadata
: 返回一个Metadata
对象,该对象包含了 PDF 文件的元数据信息,如作者、标题等。可以通过属性(如.author
,.title
)访问这些信息。reader.pages[0]
: 访问reader.pages
列表中的第一个元素,即 PDF 的第一页。返回一个PageObject
对象。first_page.mediabox
: 获取页面的媒体框,它定义了页面的物理尺寸。返回一个RectangleObject
对象,可以通过索引访问其坐标,如mediabox[0]
,mediabox[1]
(左下角 x, y),mediabox[2]
,mediabox[3]
(右上角 x, y)。first_page.cropbox
: 获取页面的裁剪框,定义了页面的可见区域。通常与 MediaBox 相同,除非特殊设置。first_page.get('/Rotate')
: PDF 内部使用字典结构存储页面属性,旋转角度存储在/Rotate
键下。get()
方法安全地获取这个值,如果不存在则返回None
。
1.2. 从 PDF 中提取文本
从 PDF 中提取文本是 PyPDF2 的常见用途之一。每页的 PageObject
都有一个 extract_text()
方法。
# pypdf_extract_text.py
from pypdf import PdfReader # 从 pypdf 库导入 PdfReader 类
import os # 导入 os 模块,用于文件路径操作
# 假设 'example.pdf' 文件已存在
pdf_path = 'example.pdf' # 要读取的 PDF 文件路径
try:
# 使用 with 语句打开 PDF 文件
with open(pdf_path, 'rb') as file: # 以二进制只读模式 ('rb') 打开 PDF 文件
reader = PdfReader(file) # 创建一个 PdfReader 对象
# 循环遍历每一页并提取文本
print(f"正在从文件 '{
pdf_path}' 提取文本...") # 打印开始提取文本的提示
for page_num in range(len(reader.pages)): # 遍历所有页码 (从 0 开始)
page = reader.pages[page_num] # 获取当前页的 PageObject
print(f"\n--- 页面 {
page_num + 1} 文本 ---") # 打印当前页码标题
try:
text = page.extract_text() # 提取当前页的文本内容
if text:
print(text) # 打印提取到的文本
else:
print("该页面未提取到文本。") # 如果未提取到文本,打印提示
except Exception as page_extract_error:
print(f"从页面 {
page_num + 1} 提取文本时发生错误: {
page_extract_error}") # 打印页面提取文本时的错误信息
except FileNotFoundError:
print(f"错误: 文件 '{
pdf_path}' 未找到。请确保文件存在。") # 打印文件未找到错误
except Exception as e:
print(f"读取 PDF 文件或提取文本时发生错误: {
e}") # 打印读取文件或提取文本时的其他错误
代码解释:
for page_num in range(len(reader.pages)):
: 循环遍历从 0 到总页数减一的页码。page = reader.pages[page_num]
: 获取当前页码对应的PageObject
。text = page.extract_text()
: 调用PageObject
的extract_text()
方法来提取该页的文本内容。提取结果是一个字符串。- 文本提取的质量取决于 PDF 的创建方式、字体嵌入情况以及文本的布局。对于复杂的布局或扫描件,
extract_text()
可能无法提取出完整或正确的文本。对于更复杂的文本提取需求,可能需要使用pdfminer.six
或 PyMuPDF。
1.3. 合并 PDF 文件
PyPDF2 可以轻松地将多个 PDF 文件合并成一个。使用 PdfWriter
对象,将源 PDF 的页面逐一添加到 Writer 中,最后写入新的文件。
# pypdf_merge.py
from pypdf import PdfReader, PdfWriter # 导入 PdfReader 和 PdfWriter 类
import os # 导入 os 模块
# 假设有两个示例 PDF 文件:'part1.pdf' 和 'part2.pdf'
# 如果没有,先创建它们
try:
from reportlab.pdfgen import canvas
def create_part_pdf(filename, content):
c = canvas.Canvas(filename) # 创建 PDF canvas 对象
c.drawString(100, 750, content) # 绘制文本内容
c.save() # 保存 PDF 文件
print(f"Created sample PDF: {
filename}") # 打印创建成功信息
if not os.path.exists("part1.pdf"):
create_part_pdf("part1.pdf", "This is the first part.") # 如果 part1.pdf 不存在,则创建
if not os.path.exists("part2.pdf"):
create_part_pdf("part2.pdf", "This is the second part.") # 如果 part2.pdf 不存在,则创建
except ImportError:
print("警告: 未安装 reportlab 库。无法自动创建示例 PDF。请手动创建 'part1.pdf' 和 'part2.pdf' 文件。") # 如果未安装 reportlab,打印警告
except Exception as e:
print(f"创建示例 PDF 时发生错误: {
e}") # 打印创建示例 PDF 时的错误信息
pdf_files_to_merge = ['part1.pdf', 'part2.pdf'] # 要合并的 PDF 文件列表
output_pdf_path = 'merged.pdf' # 合并后的输出文件路径
# 创建一个 PdfWriter 对象
writer = PdfWriter() # 创建一个 PdfWriter 对象,用于构建新的 PDF
print(f"正在合并文件: {
pdf_files_to_merge} 到 {
output_pdf_path}") # 打印正在合并的文件信息
try:
# 遍历要合并的每个 PDF 文件
for pdf_file in pdf_files_to_merge: # 遍历文件列表
if not os.path.exists(pdf_file):
print(f"警告: 文件 '{
pdf_file}' 未找到,跳过。") # 如果文件不存在,打印警告并跳过
continue # 跳过当前文件
try:
# 使用 with 语句打开当前 PDF 文件
with open(pdf_file, 'rb') as file: # 以二进制只读模式打开要合并的 PDF 文件
reader = PdfReader(file) # 创建 PdfReader 对象来读取当前文件
# 遍历当前 PDF 文件的每一页,并将其添加到 writer 中
for page in reader.pages: # 遍历当前 PDF 文件的所有页面
writer.add_page(page) # 将当前页面的 PageObject 添加到 writer 中
except Exception as read_error:
print(f"读取文件 '{
pdf_file}' 时发生错误: {
read_error}") # 打印读取文件时的错误信息
# 发生错误时,可以根据需要选择是跳过文件还是停止合并
# 将 writer 中的内容写入新的 PDF 文件
if len(writer.pages) > 0: # 只有当 writer 中有页面时才写入文件
with open(output_pdf_path, 'wb') as output_file: # 以二进制写入模式 ('wb') 打开输出文件
writer.write(output_file) # 将 writer 对象中的所有页面写入输出文件
print(f"成功合并文件到 '{
output_pdf_path}'") # 打印合并成功信息
else:
print("没有要合并的有效页面,未生成输出文件。") # 如果没有有效页面,打印提示
except Exception as e:
print(f"合并 PDF 文件时发生错误: {
e}") # 打印合并 PDF 文件时的其他错误
代码解释:
from pypdf import PdfReader, PdfWriter
: 导入PdfReader
和PdfWriter
类。writer = PdfWriter()
: 创建一个PdfWriter
对象。这个对象就像一个空白的 PDF 文档,我们可以向其中添加页面。for pdf_file in pdf_files_to_merge:
: 遍历需要合并的 PDF 文件列表。with open(pdf_file, 'rb') as file:
: 打开源 PDF 文件进行读取。reader = PdfReader(file)
: 创建源文件的PdfReader
对象。for page in reader.pages:
: 遍历源 PDF 文件中的所有页面。writer.add_page(page)
: 将从源文件中读取的PageObject
添加到writer
对象中。with open(output_pdf_path, 'wb') as output_file:
: 打开或创建一个新的文件用于写入合并后的 PDF。必须使用二进制写入模式 ('wb'
)。writer.write(output_file)
: 将writer
对象中所有已添加的页面写入到指定的输出文件中。
1.4. 分割 PDF 文件
分割 PDF 文件是将一个 PDF 拆分成多个较小的 PDF 文件,例如按页分割,或者提取特定的页范围。同样使用 PdfReader
读取源文件,然后创建多个 PdfWriter
对象,将源文件的页面分配到不同的 Writer 中。
# pypdf_split.py
from pypdf import PdfReader, PdfWriter # 导入 PdfReader 和 PdfWriter 类
import os # 导入 os 模块
# 假设有一个包含多页的示例 PDF 文件 'multi_page.pdf'
# 如果没有,先创建一个
try:
from reportlab.pdfgen import canvas
def create_multi_page_pdf(filename="multi_page.pdf", num_pages=5):
c = canvas.Canvas(filename) # 创建 PDF canvas 对象
for i in range(num_pages): # 循环创建多个页面
c.drawString(100, 750, f"This is Page {
i + 1}") # 绘制当前页码文本
if i < num_pages - 1:
c.showPage() # 添加一个新页面 (除了最后一页)
c.save() # 保存 PDF 文件
print(f"Created sample multi-page PDF: {
filename}") # 打印创建成功信息
if not os.path.exists("multi_page.pdf"):
create_multi_page_pdf("multi_page.pdf", 5) # 如果文件不存在,则创建包含 5 页的示例 PDF
except ImportError:
print("警告: 未安装 reportlab 库。无法自动创建示例 PDF。请手动创建一个包含多页的 'multi_page.pdf' 文件。") # 如果未安装 reportlab,打印警告
except Exception as e:
print(f"创建示例 PDF 时发生错误: {
e}") # 打印创建示例 PDF 时的错误信息
pdf_path = 'multi_page.pdf' # 要分割的 PDF 文件路径
output_dir = 'split_pages' # 分割后的文件存放目录
# 确保输出目录存在
os.makedirs(output_dir, exist_ok=True) # 创建输出目录,如果已存在则不报错
print(f"分割后的文件将保存到目录: {
output_dir}") # 打印输出目录信息
try:
# 使用 with 语句打开 PDF 文件
with open(pdf_path, 'rb') as file: # 以二进制只读模式打开要分割的 PDF 文件
reader = PdfReader(file) # 创建 PdfReader 对象来读取文件
# 按页分割:将每一页保存为一个单独的 PDF 文件
print(f"正在按页分割文件 '{
pdf_path}'...") # 打印开始按页分割的提示
for page_num in range(len(reader.pages)): # 遍历所有页码
writer = PdfWriter() # 为每一页创建一个新的 PdfWriter 对象
writer.add_page(reader.pages[page_num]) # 将当前页添加到 writer 中
# 构建输出文件名
output_filename = os.path.join(output_dir, f'page_{
page_num + 1}.pdf') # 构建输出文件的完整路径名
with open(output_filename, 'wb') as output_file: # 打开输出文件进行写入
writer.write(output_file) # 写入文件
print(f"已保存页面 {
page_num + 1} 到 '{
output_filename}'") # 打印保存成功信息
# 示例:提取特定页范围 (例如提取第 2 页到第 4 页,注意页码从 0 开始计数)
print("\n正在提取页范围 (第 2 到 4 页)...") # 打印开始提取页范围的提示
start_page_index = 1 # 要提取的起始页索引 (第 2 页是索引 1)
end_page_index = 4 # 要提取的结束页索引 (第 4 页是索引 3,这里范围是包含结束的)
# 或者更常用的范围表示法:提取第 2 页到第 4 页(共 3 页)
pages_to_extract = [1, 2, 3] # 要提取的页索引列表 (对应原 PDF 的第 2, 3, 4 页)
if len(reader.pages) > max(pages_to_extract) if pages_to_extract else -1 >= 0: # 检查要提取的页码是否有效
range_writer = PdfWriter() # 创建一个新的 PdfWriter 对象用于提取范围
# 将指定页范围添加到 writer 中
for page_index in pages_to_extract: # 遍历要提取的页索引列表
if 0 <= page_index < len(reader.pages): # 确保页索引在有效范围内
range_writer.add_page(reader.pages[page_index]) # 将指定页添加到 writer 中
else:
print(f"警告: 页索引 {
page_index} 超出范围,跳过。") # 如果页索引无效,打印警告
if len(range_writer.pages) > 0: # 只有当 range_writer 中有页面时才写入文件
range_output_filename = os.path.join(output_dir, 'pages_2_to_4.pdf') # 构建范围提取的输出文件名
with open(range_output_filename, 'wb') as output_file: # 打开输出文件进行写入
range_writer.write(output_file) # 写入文件
print(f"已提取页范围到 '{
range_output_filename}'") # 打印提取成功信息
else:
print("没有有效的页面可供范围提取,未生成输出文件。") # 如果没有有效页面,打印提示
else:
print("指定的页范围超出原 PDF 的总页数。") # 如果指定的页范围无效,打印提示
except FileNotFoundError:
print(f"错误: 文件 '{
pdf_path}' 未找到。请确保文件存在。") # 打印文件未找到错误
except Exception as e:
print(f"分割 PDF 文件时发生错误: {
e}") # 打印分割 PDF 文件时的其他错误
代码解释:
os.makedirs(output_dir, exist_ok=True)
: 创建用于存放分割后文件的目录。exist_ok=True
参数表示如果目录已经存在则不抛出错误。for page_num in range(len(reader.pages)):
: 遍历源 PDF 的每一个页码。writer = PdfWriter()
: 在按页分割时,每处理一页就创建一个新的PdfWriter
对象,这样每页都会写入一个独立的文件。writer.add_page(reader.pages[page_num])
: 将当前页添加到当前的writer
对象中。os.path.join(output_dir, f'page_{page_num + 1}.pdf')
: 构建输出文件的完整路径名,包括目录和文件名。with open(output_filename, 'wb') as output_file:
: 打开输出文件进行写入。- 提取页范围示例:创建一个
pages_to_extract
列表,包含需要提取的页面的索引(注意索引从 0 开始)。然后遍历这个列表,将对应的页面添加到新的PdfWriter
对象中。
1.5. 旋转和裁剪页面
PyPDF2 允许对 PDF 页面进行旋转和裁剪。页面的 PageObject
提供了相应的方法。
# pypdf_rotate_crop.py
from pypdf import PdfReader, PdfWriter # 导入 PdfReader 和 PdfWriter 类
import os # 导入 os 模块
# 假设有一个示例 PDF 文件 'example.pdf'
pdf_path = 'example.pdf' # 要操作的 PDF 文件路径
output_rotate_path = 'rotated.pdf' # 旋转后的输出文件路径
output_cropped_path = 'cropped.pdf' # 裁剪后的输出文件路径
# 确保示例文件存在
try:
from reportlab.pdfgen import canvas
if not os.path.exists("example.pdf"):
def create_simple_pdf(filename="example.pdf"):
c = canvas.Canvas(filename)
c.drawString(100, 750, "Hello, this is page 1.")
c.drawString(100, 100, "This is the bottom left.")
c.save()
print(f"Created sample PDF: {
filename}")
create_simple_pdf("example.pdf")
except ImportError:
print("警告: 未安装 reportlab 库。无法自动创建示例 PDF。请手动创建一个 'example.pdf' 文件。")
except Exception as e:
print(f"创建示例 PDF 时发生错误: {
e}")
try:
# 使用 with 语句打开 PDF 文件进行读取
with open(pdf_path, 'rb') as file: # 以二进制只读模式打开 PDF 文件
reader = PdfReader(file) # 创建 PdfReader 对象
writer = PdfWriter() # 创建 PdfWriter 对象用于保存修改后的页面
# 复制第一页到 writer
page_to_rotate = reader.pages[0] # 获取要旋转的页面对象
writer.add_page(page_to_rotate) # 将页面添加到 writer 中 (重要:直接操作原始页面可能影响 reader,通过 add_page 复制一份)
# ------------------ 旋转页面 ------------------
print(f"正在旋转 '{
pdf_path}' 的第一页并保存到 '{
output_rotate_path}'") # 打印旋转操作信息
rotate_writer = PdfWriter() # 创建一个新的 writer 用于旋转操作
# 获取要旋转的页面 (通常需要复制一份,以免影响 reader)
original_page_for_rotate = reader.pages[0] # 获取原始页面对象
rotated_page = original_page_for_rotate # 直接操作 PageObject 会修改它
# 如果不想修改 original_page_for_rotate,可以先复制 PageObject
# rotated_page = reader.pages[0].copy() # 复制页面对象 (如果需要)
# 旋转页面 90 度 (顺时针)
# rotate() 方法接受一个角度,必须是 90 的倍数
rotated_page.rotate(90) # 旋转页面 90 度顺时针
# 将旋转后的页面添加到 writer 中
rotate_writer.add_page(rotated_page) # 将旋转后的页面添加到 writer 中
# 写入旋转后的 PDF 文件
with open(output_rotate_path, 'wb') as output_file: # 打开输出文件进行写入
rotate_writer.write(output_file) # 写入文件
print(f"成功旋转第一页并保存到 '{
output_rotate_path}'") # 打印旋转成功信息
# ------------------ 裁剪页面 ------------------
print(f"\n正在裁剪 '{
pdf_path}' 的第一页并保存到 '{
output_cropped_path}'") # 打印裁剪操作信息
crop_writer = PdfWriter() # 创建一个新的 writer 用于裁剪操作
# 获取要裁剪的页面 (同样通常需要复制一份)
original_page_for_crop = reader.pages[0] # 获取原始页面对象
cropped_page = original_page_for_crop # 直接操作 PageObject 会修改它
# 如果不想修改 original_page_for_crop,可以先复制 PageObject
# cropped_page = reader.pages[0].copy() # 复制页面对象 (如果需要)
# 获取当前页面的 MediaBox (原始尺寸)
media_box = cropped_page.mediabox # 获取页面的 MediaBox
# 定义裁剪区域 (例如,只保留页面的左上角四分之一)
# 裁剪框的坐标是相对于页面的左下角 (0, 0)
left = media_box.left # 原始左边界
bottom = media_box.bottom # 原始下边界
right = media_box.right / 2 # 新的右边界 (原宽度的一半)
top = media_box.top / 2 # 新的上边界 (原高度的一半)
# 设置页面的裁剪框 (CropBox)
# set_cropbox(left, bottom, right, top)
cropped_page.cropbox.lower_left = (left, bottom) # 设置裁剪框的左下角坐标
cropped_page.cropbox.upper_right = (right, top) # 设置裁剪框的右上角坐标
print(f"已将第一页裁剪框设置为: {
cropped_page.cropbox}") # 打印设置后的裁剪框
# 将裁剪后的页面添加到 writer 中
crop_writer.add_page(cropped_page) # 将裁剪后的页面添加到 writer 中
# 写入裁剪后的 PDF 文件
with open(output_cropped_path, 'wb') as output_file: # 打开输出文件进行写入
crop_writer.write(output_file) # 写入文件
print(f"成功裁剪第一页并保存到 '{
output_cropped_path}'") # 打印裁剪成功信息
except FileNotFoundError:
print(f"错误: 文件 '{
pdf_path}' 未找到。请确保文件存在。") # 打印文件未找到错误
except Exception as e:
print(f"操作 PDF 文件时发生错误: {
e}") # 打印操作 PDF 文件时的其他错误
代码解释:
page.rotate(angle)
:PageObject
的rotate()
方法用于旋转页面。angle
必须是 90 的倍数(90, 180, 270)。旋转是相对于页面的中心点进行的。page.cropbox
: 获取或设置页面的裁剪框 (CropBox
)。它是一个RectangleObject
,可以通过.lower_left
(左下角坐标) 和.upper_right
(右上角坐标) 属性来设置裁剪区域。page.mediabox
: 获取页面的媒体框 (MediaBox
),通常是页面的原始物理尺寸。同样是RectangleObject
。RectangleObject
的坐标:坐标是相对于页面的左下角 (0, 0) 的。可以通过索引或属性访问坐标:rect[0], rect[1]
是左下角(x, y)
,rect[2], rect[3]
是右上角(x, y)
。也可以使用属性如rect.left
,rect.bottom
,rect.right
,rect.top
。- 裁剪:通过修改
page.cropbox
的.lower_left
和.upper_right
属性来定义页面的可见区域。
1.6. 加密与解密 PDF 文件
PyPDF2 支持对 PDF 文件进行密码加密和解密。
# pypdf_encrypt_decrypt.py
from pypdf import PdfReader, PdfWriter # 导入 PdfReader 和 PdfWriter 类
import os # 导入 os 模块
# 假设有一个示例 PDF 文件 'example.pdf'
pdf_path = 'example.pdf' # 要操作的 PDF 文件路径
encrypted_pdf_path = 'encrypted.pdf' # 加密后的输出文件路径
decrypted_pdf_path = 'decrypted.pdf' # 解密后的输出文件路径
password = 'mysecretpassword' # 用于加密和解密的密码
# 确保示例文件存在
try:
from reportlab.pdfgen import canvas
if not os.path.exists("example.pdf"):
def create_simple_pdf(filename="example.pdf"):
c = canvas.Canvas(filename)
c.drawString(100, 750, "This PDF will be encrypted.")
c.save()
print(f"Created sample PDF: {
filename}")
create_simple_pdf("example.pdf")
except ImportError:
print("警告: 未安装 reportlab 库。无法自动创建示例 PDF。请手动创建一个 'example.pdf' 文件。")
except Exception as e:
print(f"创建示例 PDF 时发生错误: {
e}")
# ------------------ 加密 PDF ------------------
print(f"正在加密文件 '{
pdf_path}' 并保存到 '{
encrypted_pdf_path}'") # 打印加密操作信息
try:
# 使用 with 语句打开 PDF 文件进行读取
with open(pdf_path, 'rb') as file: # 以二进制只读模式打开 PDF 文件
reader = PdfReader(file) # 创建 PdfReader 对象
writer = PdfWriter() # 创建 PdfWriter 对象用于保存加密后的内容
# 将原始 PDF 的所有页面添加到 writer 中
for page in reader.pages: # 遍历原始 PDF 的所有页面
writer.add_page(page) # 将页面添加到 writer 中
# 对 writer 对象进行加密
# encrypt() 方法接受密码作为参数
# strength='128' (默认值) 使用 128 位 RC4 加密
# strength='256' 使用 256 位 AES 加密 (需要 PdfReader 在读取时也支持)
writer.encrypt(password) # 使用指定的密码加密 writer 中的内容
# 将加密后的内容写入文件
with open(encrypted_pdf_path, 'wb') as output_file: # 打开输出文件进行写入
writer.write(output_file) # 写入加密后的内容到文件
print(f"成功加密文件到 '{
encrypted_pdf_path}' 使用密码 '{
password}'") # 打印加密成功信息
except FileNotFoundError:
print(f"错误: 文件 '{
pdf_path}' 未找到。") # 打印文件未找到错误
except Exception as e:
print(f"加密 PDF 文件时发生错误: {
e}") # 打印加密 PDF 文件时的其他错误
# ------------------ 解密 PDF ------------------
print(f"\n正在解密文件 '{
encrypted_pdf_path}' 并保存到 '{
decrypted_pdf_path}'") # 打印解密操作信息
try:
# 使用 with 语句打开加密后的 PDF 文件进行读取
with open(encrypted_pdf_path, 'rb') as file: # 以二进制只读模式打开加密后的 PDF 文件
reader = PdfReader(file) # 创建 PdfRead