【Python】操作PDF文件

Python 操作 PDF 文件:深度解析与实践

PDF (Portable Document Format) 是一种独立于应用程序、硬件、操作系统和打印设备的文档格式。它将文档的文本、字体、图像、图形、链接等所有元素固定在一个单一的文件中,确保文档在任何地方都能以相同的格式显示。由于其跨平台性和格式固定性,PDF 在文档交换、存档等方面被广泛使用。

虽然 PDF 格式的稳定性带来了便利,但其内部结构相对复杂,包含页面、对象(文本、图像、矢量图)、字体、资源字典、交叉引用表等多种元素。直接手动解析和修改 PDF 字节流几乎是不可能的。幸运的是,Python 生态系统提供了多个功能强大且成熟的库来帮助我们处理 PDF 文件。

我们将深入探讨几个最常用和功能互补的 Python PDF 库:

  1. PyPDF2: 用于常见的 PDF 操作,如分割、合并、旋转、加密、解密、提取文本等。它是一个纯 Python 库,易于安装和使用,但对某些复杂 PDF 的处理可能有限。
  2. PyMuPDF (fitz): 基于 MuPDF 库,一个轻量级、高性能的 C 库。PyMuPDF 提供了非常快的 PDF 解析、渲染、文本和图像提取功能,并且支持更底层的访问。它功能强大,速度快,是处理大量 PDF 或需要高级提取/渲染功能的首选。
  3. reportlab: 主要用于创建 PDF 文件。它提供了一个强大的工具集来在 PDF 页面上绘制文本、图形、表格、图像等,可以用来生成报告、发票、图表等动态内容的 PDF。
  4. 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)的核心类是 PdfReaderPdfWriterPdfReader 用于读取现有的 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(): 调用 PageObjectextract_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: 导入 PdfReaderPdfWriter 类。
  • 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): PageObjectrotate() 方法用于旋转页面。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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宅男很神经

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值