python 处理 PDF 的几个知识点

之前有谈到处理 PDF 文档的操作,今天再来讲讲。
目前,我们可以通过使用 PyPDF2 包在 Python 中处理已先存在的 PDF。

介绍:

PyPDF2 是一个纯 Python 包,可用于许多不同类型的 PDF 操作。

  • 从 Python 中提取 PDF 中的文档信息 

  • 旋转页面 

  • 合并 PDF 

  • 拆分 PDF 

  • 添加水印 

  • 加密 PDF

PyPDF2 的安装
如果使用 Anaconda 而不是常规 Python,可以使用 pip 或 conda 安装 PyPDF2。以下是使用 pip 安装 PyPDF2 的方法:

$ pip install pypdf2

由于 PyPDF2 没有任何依赖,因此安装非常快。

如何从 Python**** 中提取 PDF 文档信息

我们可以使用 PyPDF2 从 PDF 中提取元数据和一些文本,尤其是当在预先存在的 PDF 文件上执行某些类型的自动化时是非常有用的。
以下是当前可以提取的数据类型:

  • Author
  • Creator
  • Producer
  • Subject
  • Title
  • Number of page
    可以在自己的电脑上随便找一个 PDF 文件进行尝试操作。下面是使用该 PDF 编写一些代码,并了解如何访问这些属性:
from PyPDF2 import PdfFileReader

def extract_information(pdf_path):
    with open(pdf_path, 'rb') as f:
        pdf = PdfFileReader(f)
        information = pdf.getDocumentInfo()
        number_of_pages = pdf.getNumPages()

    txt = f"""
    Information about {pdf_path}:

    Author: {information.author}
    Creator: {information.creator}
    Producer: {information.producer}
    Subject: {information.subject}
    Title: {information.title}
    Number of pages: {number_of_pages}
    """

    print(txt)
    return information

if __name__ == '__main__':
    path = 'xxxx.pdf'
    extract_information(path)

首先从 PyPDF2 包导入 PdfFileReader。PdfFileReader 是一个具有多种与 PDF 文件交互的方法的类。在此示例中,我们调用了.getDocumentInfo(),它将返回 DocumentInformation 的实例, 包含了我们感兴趣的大部分信息。我们还可以在 reader 对象上调用.getNumPages(),让它返回文档中的页数。

information 这个变量具有多个实例属性,可以使用这些属性从文档中获取所需的其余元数据。我们可以打印出该信息并将其返回以备将来使用。

虽然 PyPDF2 具有.extractText(),可以在其页面对象上使用提取文本(本例中未显示),但它的效果不是很好。有些 PDF 会返回文本,有些会返回空字符串。如果要从 PDF 中提取文本,建议应该看一下 PDFMiner 项目。PDFMiner 更加强大,专门用于从 PDF 中提取文本。```

如何旋转页面?

有时候 PDF 是横向模式而不是纵向模式,甚至是颠倒的。当有人扫描文档为 PDF 或电子邮件时,很可能会发生这种情况。我们可以打印出文档并阅读纸质版本,也可以使用 Python 的强大功能来旋转有问题的页面。

下面看一下如何使用 PyPDF2 旋转文章的一些页面:


from PyPDF2 import PdfFileReader, PdfFileWriter

def rotate_pages(pdf_path):
    pdf_writer = PdfFileWriter()
    pdf_reader = PdfFileReader(path)
    # 顺时针旋转90度
    page_1 = pdf_reader.getPage(0).rotateClockwise(90)
    pdf_writer.addPage(page_1)
    # 逆时针旋转90度
    page_2 = pdf_reader.getPage(1).rotateCounterClockwise(90)
    pdf_writer.addPage(page_2)
    # 在正常方向上添加一页
    pdf_writer.addPage(pdf_reader.getPage(2))

    with open('rotate_pages.pdf', 'wb') as fh:
        pdf_writer.write(fh)

if __name__ == '__main__':
    path = '新路径.pdf'
    rotate_pages(path)

上面除了 pdfileReader 之外,还导入了 pdfileWriter,因为我们需要编写一个新的 pdf。rotate_pages() 获取要修改的 PDF 的路径。在这个函数中,需要创建一个可以命名为 pdf-writer 的 writer 对象和一个名为 pdf-reader 的 reader 对象。

接下来,可以使用.get page()获取所需的页面。上面开始输入了第 0 页,也就是第一页,调用 page 对象的.rotateClockwise() 顺时针旋转方法并输入 90。然后同样地,对于第二页,调用.rotateCounterLockwise() 逆时针旋转并输入 90。

每次调用 Rotation 旋转方法后,都会调用.addPage(),这将向 writer 对象添加页面的旋转版本。最后一页是第 3 页,没有对其进行任何旋转。最后,使用.write() 把所有新页写入新的 PDF。

如何合并 PDF?

在许多情况下,我们希望将两个或多个 PDF 合并到一个 PDF 中。例如,现在可能有一个标准的封面,需要转到许多类型的报告中。这时候就可以使用 python 来帮助完成这类工作。

下面是实现的代码,完成 PDF 合并的操作:

from PyPDF2 import PdfFileReader, PdfFileWriter
def merge_pdfs(paths, output):
    pdf_writer = PdfFileWriter()

    for path in paths:
        pdf_reader = PdfFileReader(path)
        for page in range(pdf_reader.getNumPages()):
            # 将每页添加到writer对象
            pdf_writer.addPage(pdf_reader.getPage(page))

    # 写入合并的pdf
    with open(output, 'wb') as out:
        pdf_writer.write(out)

if __name__ == '__main__':
    paths = ['document1.pdf', 'document2.pdf']
    merge_pdfs(paths, output='merged.pdf')

假如有一个要合并到一起的 pdf 列表时,可以直接使用 merge_pdf 函数完成。此函数采用了输入路径和输出路径作为参数。

首先遍历输入的 paths,并为每个输入创建一个 PDF 阅读对象。然后遍历 PDF 文件中的所有页面,并使用.addpage() 将这些页面写入 writer 对象。当完成对列表中所有 PDF 的所有页面的写入后,将在末尾写入新的结果中。

如果不想合并每个 PDF 的所有页面,可以通过添加一系列要添加的页面来稍微增强这个脚本。挑战一点的话,也可以使用 Python 的 argparse 模块为这个函数创建一个命令行接口。
如何拆分 PDF?

有时可能需要将 PDF 拆分为多个 PDF,对于包含大量扫描内容的 PDF 来说尤其重要。以下是如何使用 PyPDF2 将 PDF 拆分为多个文件:

from PyPDF2 import PdfFileReader, PdfFileWriter

def split(path, name_of_split):
    pdf = PdfFileReader(path)
    for page in range(pdf.getNumPages()):
        pdf_writer = PdfFileWriter()
        pdf_writer.addPage(pdf.getPage(page))

        output = f'{name_of_split}{page}.pdf'
        with open(output, 'wb') as output_pdf:
            pdf_writer.write(output_pdf)

if __name__ == '__main__':
    path = 'xxx.pdf'
    split(path, 'jupyter_page')

这个函数中再次创建了 PDF 的 reaer 对象,并对其所读取的页面进行遍历。对于 PDF 中的每个页面,创建一个新的 PDF 的 writer 实例并向其添加单个页面。然后,将该页面写入一个唯一命名的文件。脚本运行完毕后,就可以将原始 PDF 的每个页面拆分为单独的 PDF。

如何添加水印?

水印是纸质或者电子文档上的图像或图案,一些水印只能在特殊照明条件下才能看到。水印的重要性在于它可以保护你的知识产权,例如图像或 PDF。

我们可以使用 Python 和 PyPDF2 为文档添加水印,而且是拥有仅包含水印图像或文本的 PDF。下面是向 PDF 添加水印方法:

from PyPDF2 import PdfFileWriter, PdfFileReader

def create_watermark(input_pdf, output, watermark):
    watermark_obj = PdfFileReader(watermark)
    watermark_page = watermark_obj.getPage(0)

    pdf_reader = PdfFileReader(input_pdf)
    pdf_writer = PdfFileWriter()

    # 给所有页面添加水印
    for page in range(pdf_reader.getNumPages()):
        page = pdf_reader.getPage(page)
        page.mergePage(watermark_page)
        pdf_writer.addPage(page)

    with open(output, 'wb') as out:
        pdf_writer.write(out)

if __name__ == '__main__':
    create_watermark(
        input_pdf='Jupyter_Notebook_An_Introduction.pdf', 
          output='watermarked_notebook.pdf',
        watermark='watermark.pdf')

上面 create_watermark 有三个参数:

  • input_pdf:要加水印的 PDF 文件路径

  • output:要保存 PDF 的水印版本的路径

  • watermark:包含水印图像或文本的 PDF

在代码中,打开水印 PDF 并从文档中抓取第一页,因为这是水印应该驻留的位置。然后使用 input_pdf 和通用 pdf_writer 对象创建 PDF 的 writer 对象,以写出带水印的 PDF。

下一步是遍历 input_pdf 中的页面,然后调用.mergePage() 并以用上面读取的水印对象 watermark_page 为参数,这样会将 watermark_page 覆盖在当前页面的顶部,然后再将新合并的页面添加到 pdf_writer 对象中。遍历完成后,最后将新加水印的 PDF 写入磁盘。

如何加密 PDF?

PyPDF2 目前仅支持将用户密码和所有者密码添加到预先存在的 PDF。在 PDF 版本中,所有者密码会提供 PDF 的管理员权限,并允许设置文档的权限,而用户密码只允许打开文档。

实际上,PyPDF2 是不允许设置文档的任何权限的,即使它允许设置所有者密码的情况下。但无论如何,这是可以加密的方式,也将固有地加密 PDF:

from PyPDF2 import PdfFileWriter, PdfFileReader

def add_encryption(input_pdf, output_pdf, password):
    pdf_writer = PdfFileWriter()
    pdf_reader = PdfFileReader(input_pdf)

    for page in range(pdf_reader.getNumPages()):
        pdf_writer.addPage(pdf_reader.getPage(page))

    pdf_writer.encrypt(user_pwd=password, owner_pwd=None, 
                       use_128bit=True)

    with open(output_pdf, 'wb') as fh:
        pdf_writer.write(fh)

if __name__ == '__main__':
    add_encryption(input_pdf='reportlab-sample.pdf',
                 output_pdf='reportlab-encrypted.pdf',
                 password='twofish')

add_encryption 以输入输出 PDF 路径和要添加到 PDF 的密码为参数。由于需要加密整个输入 PDF,因此需要遍历其所有页面并将其添加到 writer 编写器。最后一步是调用.encrypt(),以用户密码,所有者密码以及是否应该添加 128 位加密为参数。默认情况下,要启用 128 位加密。如果将其设置为 False,则将应用 40 位加密。