62 KiB
用 Python 创建和修改 PDF 文件
知道如何在 Python 中创建和修改 PDF 文件真的很有用。 PDF 或 P 或表格 D 文档 F 格式,是在互联网上共享文档最常见的格式之一。pdf可以包含文本、图像、表格、表单以及视频和动画等富媒体,所有这些都在一个文件中。
如此丰富的内容类型会使处理 pdf 变得困难。当打开一个 PDF 文件时,有许多不同种类的数据要解码!幸运的是,Python 生态系统有一些很棒的包,可以用来读取、操作和创建 PDF 文件。
在本教程中,您将学习如何:
- 阅读 PDF 中的文本
- 将一个 PDF 文件分割成多个文件
- 串联和合并 PDF 文件
- 旋转和裁剪 PDF 文件中的页面
- 用密码加密和解密 PDF 文件
- 从头开始创建PDF 文件
**注:**本教程改编自 Python 基础知识:Python 实用入门 3 中“创建和修改 PDF 文件”一章。
这本书使用 Python 内置的 IDLE 编辑器来创建和编辑 Python 文件,并与 Python shell 进行交互,因此在整个教程中你会偶尔看到对 IDLE 的引用。但是,从您选择的编辑器和环境中运行示例代码应该没有问题。
在这个过程中,您将有几次机会通过跟随示例来加深理解。您可以点击下面的链接下载示例中使用的材料:
下载示例材料: 单击此处获取您将在本教程中使用学习创建和修改 PDF 文件的材料。
从 PDF 中提取文本
在本节中,您将学习如何阅读 PDF 文件并使用 PyPDF2 包提取文本。不过,在你这么做之前,你需要用pip 安装:
$ python3 -m pip install PyPDF2
通过在终端中运行以下命令来验证安装:
$ python3 -m pip show PyPDF2
Name: PyPDF2
Version: 1.26.0
Summary: PDF toolkit
Home-page: http://mstamy2.github.com/PyPDF2
Author: Mathieu Fenniak
Author-email: biziqe@mathieu.fenniak.net
License: UNKNOWN
Location: c:\\users\\david\\python38-32\\lib\\site-packages
Requires:
Required-by:
请特别注意版本信息。在撰写本文时,PyPDF2的最新版本是1.26.0。如果你有 IDLE open,那么你需要重启它才能使用PyPDF2包。
打开 PDF 文件
让我们先打开一个 PDF 文件,阅读一些相关信息。您将使用位于配套存储库中的practice_files/文件夹中的Pride_and_Prejudice.pdf文件。
打开 IDLE 的交互窗口,从PyPDF2包中导入类PdfFileReader:
>>> from PyPDF2 import PdfFileReader
要创建一个新的PdfFileReader类的实例,您将需要您想要打开的 PDF 文件的路径。现在让我们使用pathlib模块来获取:
>>> from pathlib import Path
>>> pdf_path = (
... Path.home()
... / "creating-and-modifying-pdfs"
... / "practice_files"
... / "Pride_and_Prejudice.pdf"
... )
pdf_path 变量现在包含了简·奥斯汀的傲慢与偏见的 PDF 版本的路径。
**注意:**您可能需要更改pdf_path,使其对应于您计算机上creating-and-modifying-pdfs/文件夹的位置。
现在创建PdfFileReader实例:
>>> pdf = PdfFileReader(str(pdf_path))
您将pdf_path转换为字符串,因为PdfFileReader不知道如何从pathlib.Path对象中读取。
回想一下第 12 章“文件输入和输出”,在程序终止之前所有打开的文件都应该关闭。对象为你做了所有这些,所以你不需要担心打开或关闭 PDF 文件!
现在您已经创建了一个PdfFileReader实例,您可以使用它来收集关于 PDF 的信息。例如,.getNumPages()返回 PDF 文件中包含的页数:
>>> pdf.getNumPages()
234
注意.getNumPages()是用 mixedCase 写的,而不是像 PEP 8 中推荐的 lower _ case _ with _ 下划线。记住,PEP 8 是一套指导方针,而不是规则。就 Python 而言,mixedCase 是完全可以接受的。
注: PyPDF2改编自pyPdf包。pyPdf写于 2005 年,仅在 PEP 8 出版后四年。
当时,许多 Python 程序员正在从 mixedCase 更常见的语言中迁移。
您还可以使用.documentInfo属性访问一些文档信息:
>>> pdf.documentInfo
{'/Title': 'Pride and Prejudice, by Jane Austen', '/Author': 'Chuck',
'/Creator': 'Microsoft® Office Word 2007',
'/CreationDate': 'D:20110812174208', '/ModDate': 'D:20110812174208',
'/Producer': 'Microsoft® Office Word 2007'}
由.documentInfo返回的对象看起来像一个字典,但它实际上不是同一个东西。您可以将.documentInfo中的每个项目作为属性进行访问。
例如,要获得标题,使用.title属性:
>>> pdf.documentInfo.title
'Pride and Prejudice, by Jane Austen'
.documentInfo对象包含 PDF 元数据,它在创建 PDF 时设置。
PdfFileReader类提供了访问 PDF 文件中的数据所需的所有方法和属性。让我们来探索一下您可以用 PDF 文件做什么,以及如何做!
从页面中提取文本
PDF 页面在PyPDF2中用PageObject类表示。您可以使用PageObject实例与 PDF 文件中的页面进行交互。您不需要直接创建自己的PageObject实例。相反,你可以通过PdfFileReader对象的.getPage()方法来访问它们。
从单个 PDF 页面中提取文本有两个步骤:
- 用
PdfFileReader.getPage()得到一个PageObject。 - 用
PageObject实例的.extractText()方法将文本提取为字符串。
Pride_and_Prejudice.pdf有234页。每一页在0和233之间都有一个索引。通过将页面的索引传递给PdfFileReader.getPage(),可以获得代表特定页面的PageObject:
>>> first_page = pdf.getPage(0)
.getPage()返回一个PageObject:
>>> type(first_page)
<class 'PyPDF2.pdf.PageObject'>
您可以使用PageObject.extractText()提取页面文本:
>>> first_page.extractText()
'\\n \\nThe Project Gutenberg EBook of Pride and Prejudice, by Jane
Austen\\n \\n\\nThis eBook is for the use of anyone anywhere at no cost
and with\\n \\nalmost no restrictions whatsoever. You may copy it,
give it away or\\n \\nre\\n-\\nuse it under the terms of the Project
Gutenberg License included\\n \\nwith this eBook or online at
www.gutenberg.org\\n \\n \\n \\nTitle: Pride and Prejudice\\n \\n
\\nAuthor: Jane Austen\\n \\n \\nRelease Date: August 26, 2008
[EBook #1342]\\n\\n[Last updated: August 11, 2011]\\n \\n \\nLanguage:
Eng\\nlish\\n \\n \\nCharacter set encoding: ASCII\\n \\n \\n***
START OF THIS PROJECT GUTENBERG EBOOK PRIDE AND PREJUDICE ***\\n \\n
\\n \\n \\n \\nProduced by Anonymous Volunteers, and David Widger\\n
\\n \\n \\n \\n \\n \\n \\nPRIDE AND PREJUDICE \\n \\n \\nBy Jane
Austen \\n \\n\\n \\n \\nContents\\n \\n'
请注意,此处显示的输出已经过格式化,以更好地适应此页面。您在电脑上看到的输出格式可能会有所不同。
每个PdfFileReader对象都有一个.pages属性,您可以使用它来按顺序遍历 PDF 中的所有页面。
例如,下面的 for循环打印傲慢与偏见 PDF 中每一页的文本:
>>> for page in pdf.pages:
... print(page.extractText())
...
让我们结合你所学的一切,编写一个程序,从Pride_and_Prejudice.pdf文件中提取所有文本,并保存到.txt文件中。
将所有这些放在一起
在空闲状态下打开一个新的编辑器窗口,并键入以下代码:
from pathlib import Path
from PyPDF2 import PdfFileReader
# Change the path below to the correct path for your computer.
pdf_path = (
Path.home()
/ "creating-and-modifying-pdfs"
/ "practice-files"
/ "Pride_and_Prejudice.pdf"
)
# 1
pdf_reader = PdfFileReader(str(pdf_path))
output_file_path = Path.home() / "Pride_and_Prejudice.txt"
# 2
with output_file_path.open(mode="w") as output_file:
# 3
title = pdf_reader.documentInfo.title
num_pages = pdf_reader.getNumPages()
output_file.write(f"{title}\\nNumber of pages: {num_pages}\\n\\n")
# 4
for page in pdf_reader.pages:
text = page.extractText()
output_file.write(text)
让我们来分解一下:
-
首先,将一个新的
PdfFileReader实例分配给pdf_reader变量。您还创建了一个新的Path对象,它指向您的主目录中的文件Pride_and_Prejudice.txt,并将它赋给变量output_file_path。 -
接下来,以写模式打开
output_file_path,并将.open()返回的文件对象赋给变量output_file。你在第 12 章“文件输入和输出”中了解到的with语句确保当with块退出时文件被关闭。 -
然后,在
with块中,使用output_file.write()将 PDF 标题和页数写入文本文件。 -
最后,使用一个
for循环迭代 PDF 中的所有页面。在循环的每一步,下一个PageObject被分配给page变量。用page.extractText()提取每页的文本,并写入output_file。
当您保存并运行该程序时,它将在您的主目录中创建一个名为Pride_and_Prejudice.txt的新文件,其中包含了Pride_and_Prejudice.pdf文档的全文。打开看看吧!
检查你的理解能力
展开下面的方框,检查您的理解程度:
在本文的配套存储库中的practice_files/文件夹中,有一个名为zen.pdf的文件。创建一个读取 PDF 的PdfFileReader实例,并使用它打印第一页的文本。
您可以展开下面的方框查看解决方案:
设置 PDF 文件的路径:
# First, import the needed classes and libraries
from pathlib import Path
from PyPDF2 import PdfFileReader
# Then create a `Path` object to the PDF file.
# You might need to change this to match the path
# on your computer.
pdf_path = (
Path.home()
/ "creating-and-modifying-pdfs"
/ "practice_files"
/ "zen.pdf"
)
现在您可以创建PdfFileReader实例了:
pdf_reader = PdfFileReader(str(pdf_path))
记住PdfFileReader对象只能用路径字符串实例化,不能用Path对象实例化!
现在使用.getPage()来获得第一页:
first_page = pdf_reader.getPage(0)
记住,页面索引是从 0 开始的!
然后使用.extractText()提取文本:
text = first_page.extractText()
最后,打印文本:
print(text)
当你准备好了,你可以进入下一部分。
从 PDF 中提取页面
在上一节中,您学习了如何从 PDF 文件中提取所有文本并保存到一个.txt文件中。现在,您将了解如何从现有 PDF 中提取一个页面或一系列页面,并将它们保存到新的 PDF 中。
您可以使用PdfFileWriter创建一个新的 PDF 文件。让我们探索这个课程,学习使用PyPDF2创建 PDF 所需的步骤。
使用PdfFileWriter类
PdfFileWriter类创建新的 PDF 文件。在 IDLE 的交互窗口中,导入PdfFileWriter类并创建一个名为pdf_writer的新实例:
>>> from PyPDF2 import PdfFileWriter
>>> pdf_writer = PdfFileWriter()
对象就像空白的 PDF 文件。在将它们保存到文件之前,您需要向它们添加一些页面。
继续给pdf_writer添加一个空白页:
>>> page = pdf_writer.addBlankPage(width=72, height=72)
width和height参数是必需的,它们以称为点的单位确定页面的尺寸。一点等于 1/72 英寸,所以上面的代码为pdf_writer添加了一个一英寸见方的空白页。
.addBlankPage()返回一个新的PageObject实例,代表您添加到PdfFileWriter中的页面:
>>> type(page)
<class 'PyPDF2.pdf.PageObject'>
在这个例子中,您已经将由.addBlankPage()返回的PageObject实例赋给了page变量,但是实际上您通常不需要这样做。也就是说,您通常调用.addBlankPage()而不将返回值赋给任何东西:
>>> pdf_writer.addBlankPage(width=72, height=72)
要将pdf_writer的内容写入 PDF 文件,以二进制写入模式将文件对象传递给pdf_writer.write():
>>> from pathlib import Path
>>> with Path("blank.pdf").open(mode="wb") as output_file:
... pdf_writer.write(output_file)
...
这将在当前工作目录中创建一个名为blank.pdf的新文件。如果你用 PDF 阅读器打开文件,比如 Adobe Acrobat,你会看到一个只有一个一英寸见方的空白页面的文档。
**技术细节:**注意,您保存 PDF 文件的方法是将 file 对象传递给PdfFileWriter对象的.write()方法,将而不是传递给 file 对象的.write()方法。
特别是,下面的代码将不起作用:
>>> with Path("blank.pdf").open(mode="wb") as output_file:
... output_file.write(pdf_writer)
对于许多新程序员来说,这种方法似乎是倒退的,所以确保你避免这个错误!
PdfFileWriter对象可以写入新的 PDF 文件,但是除了空白页之外,不能从头开始创建新的内容。
这似乎是一个大问题,但是在许多情况下,您不需要创建新的内容。通常,您会处理从 PDF 文件中提取的页面,这些文件是用PdfFileReader实例打开的。
**注意:**您将在下面的“从头创建 PDF 文件”一节中学习如何从头创建 PDF 文件
在上面的例子中,使用PyPDF2创建一个新的 PDF 文件有三个步骤:
- 创建一个
PdfFileWriter实例。 - 向
PdfFileWriter实例添加一个或多个页面。 - 使用
PdfFileWriter.write()写入文件。
随着您学习向PdfFileWriter实例添加页面的各种方法,您将会一遍又一遍地看到这种模式。
从 PDF 中提取单个页面
让我们重温一下你在上一节处理过的傲慢与偏见 PDF。您将打开 PDF,提取第一页,并创建一个新的 PDF 文件,其中只包含一个提取的页面。
打开 IDLE 的交互窗口,从PyPDF2导入PdfFileReader和PdfFileWriter,从pathlib模块导入Path类;
>>> from pathlib import Path
>>> from PyPDF2 import PdfFileReader, PdfFileWriter
现在用一个PdfFileReader实例打开Pride_and_Prejudice.pdf文件:
>>> # Change the path to work on your computer if necessary
>>> pdf_path = (
... Path.home()
... / "creating-and-modifying-pdfs"
... / "practice_files"
... / "Pride_and_Prejudice.pdf"
... )
>>> input_pdf = PdfFileReader(str(pdf_path))
将索引0传递给.getPage()以获得代表 PDF 第一页的PageObject:
>>> first_page = input_pdf.getPage(0)
现在创建一个新的PdfFileWriter实例,并用.addPage()将first_page添加到其中:
>>> pdf_writer = PdfFileWriter()
>>> pdf_writer.addPage(first_page)
与.addBlankPage()一样,.addPage()方法将页面添加到pdf_writer对象的页面集中。不同的是,它需要一个已有的PageObject。
现在将pdf_writer的内容写入一个新文件:
>>> with Path("first_page.pdf").open(mode="wb") as output_file:
... pdf_writer.write(output_file)
...
您现在有一个新的 PDF 文件保存在您当前的工作目录中,名为first_page.pdf,它包含了Pride_and_Prejudice.pdf文件的封面。相当整洁!
从 PDF 中提取多个页面
让我们从Pride_and_Prejudice.pdf中提取第一章并保存到一个新的 PDF 中。
如果你用 PDF 浏览器打开Pride_and_Prejudice.pdf,那么你可以看到第一章在 PDF 的第二、第三和第四页。因为页面是从0开始索引的,所以您需要提取索引1、2和3处的页面。
您可以通过导入所需的类并打开 PDF 文件来设置所有内容:
>>> from PyPDF2 import PdfFileReader, PdfFileWriter
>>> from pathlib import Path
>>> pdf_path = (
... Path.home()
... / "creating-and-modifying-pdfs"
... / "practice_files"
... / "Pride_and_Prejudice.pdf"
... )
>>> input_pdf = PdfFileReader(str(pdf_path))
您的目标是提取索引为1、2和3的页面,将它们添加到一个新的PdfFileWriter实例,然后将它们写入一个新的 PDF 文件。
一种方法是在从1开始到3结束的数字范围内循环,在循环的每一步提取页面并将其添加到PdfFileWriter实例:
>>> pdf_writer = PdfFileWriter()
>>> for n in range(1, 4):
... page = input_pdf.getPage(n)
... pdf_writer.addPage(page)
...
因为range(1, 4)不包括右边的端点,所以循环迭代数字1、2和3。在循环的每一步,使用.getPage()提取当前索引处的页面,并使用.addPage()将其添加到pdf_writer。
现在pdf_writer有三页,你可以用.getNumPages()检查:
>>> pdf_writer.getNumPages()
3
最后,您可以将提取的页面写入新的 PDF 文件:
>>> with Path("chapter1.pdf").open(mode="wb") as output_file:
... pdf_writer.write(output_file)
...
现在你可以打开当前工作目录中的chapter1.pdf文件来阅读傲慢与偏见的第一章。
另一种从 PDF 中提取多页的方法是利用PdfFileReader.pages支持切片标记的事实。让我们使用.pages重复前面的例子,而不是在一个 range对象上循环。
首先初始化一个新的PdfFileWriter对象:
>>> pdf_writer = PdfFileWriter()
现在从开始于1结束于4的索引开始循环一段.pages:
>>> for page in input_pdf.pages[1:4]:
... pdf_writer.addPage(page)
...
请记住,切片中的值范围是从切片中第一个索引处的项目到切片中第二个索引处的项目,但不包括这两个项目。所以.pages[1:4]返回一个包含索引为1、2和3的页面的 iterable。
最后,将pdf_writer的内容写入输出文件:
>>> with Path("chapter1_slice.pdf").open(mode="wb") as output_file:
... pdf_writer.write(output_file)
...
现在打开当前工作目录中的chapter1_slice.pdf文件,并将其与通过循环range对象创建的chapter1.pdf文件进行比较。它们包含相同的页面!
有时你需要从 PDF 中提取每一页。您可以使用上面举例说明的方法来做到这一点,但是PyPDF2提供了一个快捷方式。PdfFileWriter实例有一个.appendPagesFromReader()方法,可以用来从PdfFileReader实例追加页面。
要使用.appendPagesFromReader(),向方法的reader参数传递一个PdfFileReader实例。例如,以下代码将傲慢与偏见 PDF 中的每一页复制到PdfFileWriter实例中:
>>> pdf_writer = PdfFileWriter()
>>> pdf_writer.appendPagesFromReader(pdf_reader)
pdf_writer现在包含了pdf_reader中的每一页!
检查你的理解能力
展开下面的方框,检查您的理解程度:
从Pride_and_Prejudice.pdf文件中提取最后一页,并将其保存到主目录中一个名为last_page.pdf的新文件中。
您可以展开下面的方框查看解决方案:
设置Pride_and_Prejudice.pdf文件的路径:
# First, import the needed classes and libraries
from pathlib import Path
from PyPDF2 import PdfFileReader, PdfFileWriter
# Then create a `Path` object to the PDF file.
# You might need to change this to match the path
# on your computer.
pdf_path = (
Path.home()
/ "creating-and-modifying-pdfs"
/ "practice_files"
/ "Pride_and_Prejudice.pdf"
)
现在您可以创建PdfFileReader实例了:
pdf_reader = PdfFileReader(str(pdf_path))
记住PdfFileReader对象只能用路径字符串实例化,不能用Path对象实例化!
使用.pages属性获取 PDF 中所有页面的 iterable。最后一页可以用索引-1访问:
last_page = pdf_reader.pages[-1]
现在您可以创建一个PdfFileWriter实例,并将最后一个页面添加到其中:
pdf_writer = PdfFileWriter()
pdf_writer.addPage(last_page)
最后,将pdf_writer的内容写入主目录中的文件last_page.pdf:
output_path = Path.home() / "last_page.pdf"
with output_path.open(mode="wb") as output_file:
pdf_writer.write(output_file)
当你准备好了,你可以进入下一部分。
连接和合并 pdf 文件
处理 PDF 文件时的两个常见任务是将几个 PDF 连接并合并到一个文件中。
当您连接两个或更多 pdf 时,您将文件一个接一个地合并成一个文档。例如,一家公司可能会在月末将几份每日报告合并成一份月度报告。
合并两个 pdf 也会合并成一个文件。但是合并允许您在第一个 PDF 的特定页面之后插入它,而不是将第二个 PDF 连接到第一个 PDF 的末尾。然后,它将插入点之后的第一个 PDF 的所有页面推到第二个 PDF 的结尾。
在本节中,您将学习如何使用PyPDF2包的PdfFileMerger来连接和合并 pdf。
使用PdfFileMerger类
PdfFileMerger类很像您在上一节中了解的PdfFileWriter类。您可以使用这两个类来编写 PDF 文件。在这两种情况下,都将页添加到类的实例中,然后将它们写入文件。
两者的主要区别在于,PdfFileWriter只能将页面追加或连接到已经包含在编写器中的页面列表的末尾,而PdfFileMerger可以在任何位置插入或合并页面。
继续创建您的第一个PdfFileMerger实例。在 IDLE 的交互窗口中,键入以下代码以导入PdfFileMerger类并创建一个新实例:
>>> from PyPDF2 import PdfFileMerger
>>> pdf_merger = PdfFileMerger()
对象第一次实例化时是空的。在对对象进行任何操作之前,您需要向对象添加一些页面。
有几种方法可以将页面添加到pdf_merger对象,使用哪一种取决于您需要完成的任务:
.append()将现有 PDF 文档中的每一页连接到当前PdfFileMerger中页面的末尾。.merge()将现有 PDF 文档中的所有页面插入到PdfFileMerger中的特定页面之后。
在本节中,您将从.append()开始查看这两种方法。
将 pdf 与.append() 连接
practice_files/文件夹有一个名为expense_reports的子目录,其中包含名为 Peter Python 的雇员的三份费用报告。
Peter 需要将这三个 PDF 文件连接起来,作为一个 PDF 文件提交给他的雇主,这样他就可以报销一些与工作相关的费用。
首先,您可以使用pathlib模块获取expense_reports/文件夹中三份费用报告的Path对象列表:
>>> from pathlib import Path
>>> reports_dir = (
... Path.home()
... / "creating-and-modifying-pdfs"
... / "practice_files"
... / "expense_reports"
... )
导入Path类后,您需要构建到expense_reports/目录的路径。请注意,您可能需要修改上面的代码,以便在您的计算机上获得正确的路径。
一旦将expense_reports/目录的路径分配给了reports_dir变量,您就可以使用.glob()来获取目录中 PDF 文件的路径。
看看目录中有什么:
>>> for path in reports_dir.glob("*.pdf"):
... print(path.name)
...
Expense report 1.pdf
Expense report 3.pdf
Expense report 2.pdf
列出了三个文件的名称,但它们没有按顺序排列。此外,您在计算机输出中看到的文件顺序可能与此处显示的输出不一致。
一般来说,.glob()返回的路径顺序是不确定的,所以你需要自己排序。您可以通过创建一个包含三个文件路径的列表,然后在该列表上调用 .sort() 来实现:
>>> expense_reports = list(reports_dir.glob("*.pdf"))
>>> expense_reports.sort()
记住.sort()就地对列表进行排序,所以不需要将返回值赋给变量。在.list()被调用后,expense_reports列表将按文件名的字母顺序排序。
为了确认排序成功,再次循环expense_reports并打印出文件名:
>>> for path in expense_reports:
... print(path.name)
...
Expense report 1.pdf
Expense report 2.pdf
Expense report 3.pdf
看起来不错!
现在您可以连接这三个 pdf 文件。为此,您将使用PdfFileMerger.append(),它需要一个表示 PDF 文件路径的字符串参数。当您调用.append()时,PDF 文件中的所有页面都会被追加到PdfFileMerger对象中的页面集合中。
让我们来看看实际情况。首先,导入PdfFileMerger类并创建一个新实例:
>>> from PyPDF2 import PdfFileMerger
>>> pdf_merger = PdfFileMerger()
现在遍历排序后的expense_reports列表中的路径,并将它们附加到pdf_merger:
>>> for path in expense_reports:
... pdf_merger.append(str(path))
...
注意,expense_reports/中的每个Path对象在被传递给pdf_merger.append()之前都被转换成一个带有str()的字符串。
将expense_reports/目录中的所有 PDF 文件连接到pdf_merger对象中,您需要做的最后一件事就是将所有内容写入一个输出 PDF 文件。PdfFileMerger实例有一个.write()方法,就像PdfFileWriter.write()一样工作。
以二进制写模式打开一个新文件,然后将 file 对象传递给pdf_merge.write()方法:
>>> with Path("expense_reports.pdf").open(mode="wb") as output_file:
... pdf_merger.write(output_file)
...
您现在在当前工作目录中有一个名为expense_reports.pdf的 PDF 文件。用 PDF 阅读器打开它,您会发现所有三份费用报告都在同一个 PDF 文件中。
使用.merge()和合并 pdf
要合并两个或多个 pdf,请使用PdfFileMerger.merge()。该方法类似于.append(),除了您必须指定在输出 PDF 中的什么位置插入您正在合并的 PDF 中的所有内容。
看一个例子。Goggle,Inc .准备了一份季度报告,但忘记包括目录。Peter Python 注意到了这个错误,并很快创建了一个缺少目录的 PDF。现在,他需要将 PDF 文件合并到原始报告中。
报告 PDF 和目录 PDF 都可以在practice_files文件夹的quarterly_report/子文件夹中找到。报告在名为report.pdf的文件中,目录在名为toc.pdf的文件中。
在 IDLE 的交互窗口中,导入PdfFileMerger类,为report.pdf和toc.pdf文件创建Path对象:
>>> from pathlib import Path
>>> from PyPDF2 import PdfFileMerger
>>> report_dir = (
... Path.home()
... / "creating-and-modifying-pdfs"
... / "practice_files"
... / "quarterly_report"
... )
>>> report_path = report_dir / "report.pdf"
>>> toc_path = report_dir / "toc.pdf"
您要做的第一件事是使用.append()将报告 PDF 附加到一个新的PdfFileMerger实例:
>>> pdf_merger = PdfFileMerger()
>>> pdf_merger.append(str(report_path))
现在pdf_merger中已经有了一些页面,您可以将目录 PDF 合并到它的正确位置。如果您用 PDF 阅读器打开report.pdf文件,那么您会看到报告的第一页是一个标题页。第二个是简介,其余的页面包含不同的报告部分。
您希望在标题页之后、简介部分之前插入目录。由于 PDF 页面索引从PyPDF2中的0开始,您需要在索引0处的页面之后和索引1处的页面之前插入目录。
为此,用两个参数调用pdf_merger.merge():
- 整数
1,表示应该插入目录的页面索引 - 包含目录的 PDF 文件路径的字符串
看起来是这样的:
>>> pdf_merger.merge(1, str(toc_path))
目录 PDF 中的每一页都在索引1处的页面之前插入。因为目录 PDF 只有一页,所以它被插入到索引1处。当前在索引1的页面然后被转移到索引2。当前在索引2的页面被转移到索引3,等等。
现在将合并的 PDF 写入输出文件:
>>> with Path("full_report.pdf").open(mode="wb") as output_file:
... pdf_merger.write(output_file)
...
您现在在当前工作目录中有一个full_report.pdf文件。用 PDF 阅读器打开它,检查目录是否插入正确的位置。
连接和合并 pdf 是常见的操作。虽然本节中的示例确实有些做作,但是您可以想象一个程序对于合并成千上万的 pdf 或自动化日常任务是多么有用,否则这些任务将需要花费大量的时间来完成。
检查你的理解能力
展开下面的方框,检查您的理解程度:
在本文的配套存储库中的practice_files/文件夹中,有两个名为merge1.pdf和merge2.pdf的文件。
使用一个PdfFileMerge实例,通过.append()连接两个文件。如果您的计算机的主目录,将连接的 pdf 保存到一个名为concatenated.pdf的新文件中。
您可以展开下面的方框查看解决方案:
设置 PDF 文件的路径:
# First, import the needed classes and libraries
from pathlib import Path
from PyPDF2 import PdfFileMerger
# Then create a `Path` objects to the PDF files.
# You might need to change this to match the path
# on your computer.
BASE_PATH = (
Path.home()
/ "creating-and-modifying-pdfs"
/ "practice_files"
)
pdf_paths = [BASE_PATH / "merge1.pdf", BASE_PATH / "merge2.pdf"]
现在您可以创建PdfFileMerger实例了:
pdf_merger = PdfFileMerger()
现在将每个文件的pdf_paths和.append()中的路径循环到pdf_merger:
for path in pdf_paths:
pdf_merger.append(str(path))
最后,将pdf_merger的内容写入主目录中名为concatenated.pdf的文件:
output_path = Path.home() / "concatenated.pdf"
with output_path.open(mode="wb") as output_file:
pdf_merger.write(output_file)
当你准备好了,你可以进入下一部分。
旋转和裁剪 PDF 页面
到目前为止,您已经学习了如何从 PDF 中提取文本和页面,以及如何连接和合并两个或多个 PDF 文件。这些都是 pdf 的常见操作,但是PyPDF2还有许多其他有用的特性。
**注:**本教程改编自 Python 基础知识:Python 实用入门 3 中“创建和修改 PDF 文件”一章。如果你喜欢你正在阅读的东西,那么一定要看看这本书的其余部分。
在本节中,您将学习如何旋转和裁剪 PDF 文件中的页面。
旋转页面
您将从学习如何翻页开始。对于这个例子,您将使用practice_files文件夹中的ugly.pdf文件。这个ugly.pdf文件包含了安徒生的丑小鸭的可爱版本,除了每一个奇数页都被逆时针旋转了 90 度。
让我们解决这个问题。在一个新的空闲交互窗口中,开始从PyPDF2导入PdfFileReader和PdfFileWriter类,以及从pathlib模块导入Path类:
>>> from pathlib import Path
>>> from PyPDF2 import PdfFileReader, PdfFileWriter
现在为ugly.pdf文件创建一个Path对象:
>>> pdf_path = (
... Path.home()
... / "creating-and-modifying-pdfs"
... / "practice_files"
... / "ugly.pdf"
... )
最后,创建新的PdfFileReader和PdfFileWriter实例:
>>> pdf_reader = PdfFileReader(str(pdf_path))
>>> pdf_writer = PdfFileWriter()
您的目标是使用pdf_writer创建一个新的 PDF 文件,其中所有页面都具有正确的方向。PDF 中偶数页的方向已经正确,但是奇数页逆时针旋转了 90 度。
要纠正这个问题,您将使用PageObject.rotateClockwise()。该方法采用一个整数参数,以度数为单位,将页面顺时针旋转该度数。例如,.rotateClockwise(90)顺时针旋转 PDF 页面 90 度。
**注:**除了.rotateClockwise(),PageObject类还有逆时针旋转页面的.rotateCounterClockwise()。
有几种方法可以在 PDF 中旋转页面。我们将讨论做这件事的两种不同方法。它们都依赖于.rotateClockwise(),但是它们采用不同的方法来决定哪些页面被旋转。
第一种技术是遍历 PDF 中页面的索引,并检查每个索引是否对应于需要旋转的页面。如果是这样,那么您将调用.rotateClockwise()来旋转页面,然后将页面添加到pdf_writer。
看起来是这样的:
>>> for n in range(pdf_reader.getNumPages()):
... page = pdf_reader.getPage(n)
... if n % 2 == 0:
... page.rotateClockwise(90)
... pdf_writer.addPage(page)
...
请注意,如果索引是偶数,页面会旋转。这可能看起来很奇怪,因为 PDF 中奇数页是旋转不正确的页面。但是,PDF 中的页码以1开始,而页面索引以0开始。这意味着奇数编号的 PDF 页面具有偶数索引。
如果这让你头晕,不要担心!即使在多年处理这类事情之后,职业程序员仍然会被这类事情绊倒!
**注意:**当你执行上面的for循环时,你会在 IDLE 的交互窗口看到一堆输出。这是因为.rotateClockwise()返回了一个PageObject实例。
您现在可以忽略这个输出。当你从 IDLE 的编辑器窗口执行程序时,这个输出是不可见的。
现在您已经旋转了 PDF 中的所有页面,您可以将pdf_writer的内容写入一个新文件,并检查一切是否正常:
>>> with Path("ugly_rotated.pdf").open(mode="wb") as output_file:
... pdf_writer.write(output_file)
...
现在,在当前工作目录中应该有一个名为ugly_rotated.pdf的文件,来自ugly.pdf文件的页面都被正确旋转。
您刚刚使用的旋转ugly.pdf文件中页面的方法的问题是,它依赖于提前知道哪些页面需要旋转。在现实世界中,浏览整个 PDF 并注意要旋转哪些页面是不现实的。
事实上,您可以在没有先验知识的情况下确定哪些页面需要旋转。嗯,有时候你可以。
让我们看看如何从一个新的PdfFileReader实例开始:
>>> pdf_reader = PdfFileReader(str(pdf_path))
您需要这样做,因为您通过旋转页面改变了旧的PdfFileReader实例中的页面。所以,通过创建一个新的实例,你可以从头开始。
实例维护一个包含页面信息的值字典:
>>> pdf_reader.getPage(0)
{'/Contents': [IndirectObject(11, 0), IndirectObject(12, 0),
IndirectObject(13, 0), IndirectObject(14, 0), IndirectObject(15, 0),
IndirectObject(16, 0), IndirectObject(17, 0), IndirectObject(18, 0)],
'/Rotate': -90, '/Resources': {'/ColorSpace': {'/CS1':
IndirectObject(19, 0), '/CS0': IndirectObject(19, 0)}, '/XObject':
{'/Im0': IndirectObject(21, 0)}, '/Font': {'/TT1':
IndirectObject(23, 0), '/TT0': IndirectObject(25, 0)}, '/ExtGState':
{'/GS0': IndirectObject(27, 0)}}, '/CropBox': [0, 0, 612, 792],
'/Parent': IndirectObject(1, 0), '/MediaBox': [0, 0, 612, 792],
'/Type': '/Page', '/StructParents': 0}
呀!混杂在这些看起来毫无意义的东西中的是一个名为/Rotate的键,您可以在上面的第四行输出中看到它。这个键的值是-90。
您可以使用下标符号访问PageObject上的/Rotate键,就像您可以访问 Python dict对象一样:
>>> page = pdf_reader.getPage(0)
>>> page["/Rotate"]
-90
如果您查看pdf_reader中第二页的/Rotate键,您会看到它的值为0:
>>> page = pdf_reader.getPage(1)
>>> page["/Rotate"]
0
这意味着索引0处的页面旋转值为-90度。换句话说,它逆时针旋转了 90 度。索引1处的页面旋转值为0,因此根本没有旋转。
如果使用.rotateClockwise()旋转第一页,则/Rotate的值从-90变为0:
>>> page = pdf_reader.getPage(0)
>>> page["/Rotate"]
-90
>>> page.rotateClockwise(90)
>>> page["/Rotate"]
0
现在您已经知道如何检查/Rotate键,您可以使用它来旋转ugly.pdf文件中的页面。
你需要做的第一件事是重新初始化你的pdf_reader和pdf_writer对象,这样你就有了一个新的开始:
>>> pdf_reader = PdfFileReader(str(pdf_path))
>>> pdf_writer = PdfFileWriter()
现在编写一个循环,遍历pdf_reader.pages iterable 中的页面,检查/Rotate的值,如果该值为-90,则旋转页面:
>>> for page in pdf_reader.pages:
... if page["/Rotate"] == -90:
... page.rotateClockwise(90)
... pdf_writer.addPage(page)
...
这个循环不仅比第一个解决方案中的循环稍短,而且它不依赖于需要旋转哪些页面的任何先验知识。您可以使用这样的循环来旋转任何 PDF 中的页面,而不必打开它查看。
要完成解决方案,将pdf_writer的内容写入一个新文件:
>>> with Path("ugly_rotated2.pdf").open(mode="wb") as output_file:
... pdf_writer.write(output_file)
...
现在您可以打开当前工作目录中的ugly_rotated2.pdf文件,并将其与您之前生成的ugly_rotated.pdf文件进行比较。它们应该看起来一样。
**注意:**关于/Rotate键的一个警告:它不能保证存在于页面中。
如果/Rotate键不存在,那么通常意味着页面没有被旋转。然而,这并不总是一个安全的假设。
如果一个PageObject没有/Rotate键,那么当你试图访问它的时候会出现一个 KeyError 。你可以用一个try...except块来捕捉这个异常。
/Rotate的值可能不总是你所期望的。例如,如果您将页面逆时针旋转 90 度来扫描纸质文档,那么 PDF 的内容将会旋转。然而,/Rotate键可能具有值0。
这是使处理 PDF 文件令人沮丧的许多怪癖之一。有时你只需要在 PDF 阅读器程序中打开一个 PDF,然后手动解决问题。
裁剪页面
pdf 的另一个常见操作是裁剪页面。您可能需要这样做来将单个页面分割成多个页面,或者只提取页面的一小部分,例如签名或图形。
例如,practice_files文件夹包含一个名为half_and_half.pdf的文件。这个 PDF 包含了安徒生的小美人鱼的一部分。
此 PDF 中的每一页都有两栏。让我们把每一页分成两页,每一栏一页。
首先,从PyPDF2导入PdfFileReader和PdfFileWriter类,从pathlib模块导入Path类:
>>> from pathlib import Path
>>> from PyPDF2 import PdfFileReader, PdfFileWriter
现在为half_and_half.pdf文件创建一个Path对象:
>>> pdf_path = (
... Path.home()
... / "creating-and-modifying-pdfs"
... / "practice_files"
... / "half_and_half.pdf"
... )
接下来,创建一个新的PdfFileReader对象并获取 PDF 的第一页:
>>> pdf_reader = PdfFileReader(str(pdf_path))
>>> first_page = pdf_reader.getPage(0)
要裁剪页面,您首先需要对页面的结构有更多的了解。像first_page这样的PageObject实例有一个.mediaBox属性,代表一个定义页面边界的矩形区域。
您可以使用 IDLE 的交互窗口浏览.mediaBox,然后使用它裁剪页面:
>>> first_page.mediaBox
RectangleObject([0, 0, 792, 612])
.mediaBox属性返回一个RectangleObject这个对象在PyPDF2包中定义,代表页面上的一个矩形区域。
输出中的列表[0, 0, 792, 612]定义了矩形区域。前两个数字是矩形左下角的 x 和 y 坐标。第三和第四个数字分别代表矩形的宽度和高度。所有数值的单位都是磅,等于 1/72 英寸。
RectangleObject([0, 0, 792, 612])表示一个矩形区域,左下角为原点,宽度为792点,即 11 英寸,高度为 612 点,即 8.5 英寸。这是一个标准信纸大小的横向页面的尺寸,用于《小美人鱼的 PDF 示例。纵向的信纸大小的 PDF 页面将返回输出RectangleObject([0, 0, 612, 792])。
一个RectangleObject有四个返回矩形角坐标的属性:.lowerLeft、.lowerRight、.upperLeft和.upperRight。就像宽度和高度值一样,这些坐标以磅为单位给出。
你可以使用这四个属性来获得RectangleObject的每个角的坐标:
>>> first_page.mediaBox.lowerLeft
(0, 0)
>>> first_page.mediaBox.lowerRight
(792, 0)
>>> first_page.mediaBox.upperLeft
(0, 612)
>>> first_page.mediaBox.upperRight
(792, 612)
每个属性返回一个 tuple 包含指定角的坐标。您可以像访问任何其他 Python 元组一样,使用方括号访问各个坐标:
>>> first_page.mediaBox.upperRight[0]
792
>>> first_page.mediaBox.upperRight[1]
612
您可以通过给一个属性分配一个新的元组来改变一个mediaBox的坐标:
>>> first_page.mediaBox.upperLeft = (0, 480)
>>> first_page.mediaBox.upperLeft
(0, 480)
当您更改.upperLeft坐标时,.upperRight属性会自动调整以保持矩形形状:
>>> first_page.mediaBox.upperRight
(792, 480)
当您更改由.mediaBox返回的RectangleObject的坐标时,您有效地裁剪了页面。first_page对象现在只包含新RectangleObject边界内的信息。
继续将裁剪后的页面写入新的 PDF 文件:
>>> pdf_writer = PdfFileWriter()
>>> pdf_writer.addPage(first_page)
>>> with Path("cropped_page.pdf").open(mode="wb") as output_file:
... pdf_writer.write(output_file)
...
如果你打开当前工作目录中的cropped_page.pdf文件,你会看到页面的顶部已经被移除。
如何裁剪页面,以便只看到页面左侧的文本?你需要将页面的水平尺寸减半。您可以通过改变.mediaBox对象的.upperRight坐标来实现这一点。让我们看看它是如何工作的。
首先,您需要获得新的PdfFileReader和PdfFileWriter对象,因为您刚刚修改了pdf_reader中的第一页并将其添加到pdf_writer:
>>> pdf_reader = PdfFileReader(str(pdf_path))
>>> pdf_writer = PdfFileWriter()
现在获取 PDF 的第一页:
>>> first_page = pdf_reader.getPage(0)
这一次,让我们使用第一页的副本,这样您刚刚提取的页面保持不变。您可以通过从 Python 的标准库中导入copy模块并使用deepcopy()来制作页面的副本:
>>> import copy
>>> left_side = copy.deepcopy(first_page)
现在你可以改变left_side而不改变first_page的属性。这样,你可以稍后使用first_page来提取页面右侧的文本。
现在你需要做一点数学。您已经知道需要将.mediaBox的右上角移动到页面的顶部中央。为此,您将创建一个新的tuple,其第一个组件等于原始值的一半,并将其赋给.upperRight属性。
首先,获取.mediaBox右上角的当前坐标。
>>> current_coords = left_side.mediaBox.upperRight
然后创建一个新的tuple,其第一个坐标是当前坐标的一半,第二个坐标与原始坐标相同:
>>> new_coords = (current_coords[0] / 2, current_coords[1])
最后,将新坐标分配给.upperRight属性:
>>> left_side.mediaBox.upperRight = new_coords
现在,您已经裁剪了原始页面,只包含左侧的文本!接下来让我们提取页面的右侧。
首先获取first_page的新副本:
>>> right_side = copy.deepcopy(first_page)
移动.upperLeft角而不是.upperRight角:
>>> right_side.mediaBox.upperLeft = new_coords
这会将左上角设置为提取页面左侧时将右上角移动到的相同坐标。所以,right_side.mediaBox现在是一个矩形,它的左上角在页面的顶部中心,它的右上角在页面的右上角。
最后,将left_side和right_side页面添加到pdf_writer,并将其写入一个新的 PDF 文件:
>>> pdf_writer.addPage(left_side)
>>> pdf_writer.addPage(right_side)
>>> with Path("cropped_pages.pdf").open(mode="wb") as output_file:
... pdf_writer.write(output_file)
...
现在用 PDF 阅读器打开cropped_pages.pdf文件。您应该看到一个有两页的文件,第一页包含原始第一页左侧的文本,第二页包含原始第二页右侧的文本。
检查你的理解能力
展开下面的方框,检查您的理解程度:
在本文的配套存储库中的practice_files/文件夹中,有一个名为split_and_rotate.pdf的文件。
在您计算机的主目录中创建一个名为rotated.pdf的新文件,其中包含来自split_and_rotate.pdf的所有页面,但是每一页都逆时针旋转 90 度。
您可以展开下面的方框查看解决方案:
设置 PDF 文件的路径:
# First, import the needed classes and libraries
from pathlib import Path
from PyPDF2 import PdfFileReader
# Then create a `Path` object to the PDF file.
# You might need to change this to match the path
# on your computer.
pdf_path = (
Path.home()
/ "creating-and-modifying-pdfs"
/ "practice_files"
/ "split_and_rotate.pdf"
)
现在您可以创建PdfFileReader和PdfFileWriter实例:
pdf_reader = PdfFileReader(str(pdf_path))
pdf_writer = PdfFileWriter()
循环浏览pdf_reader中的页面,使用.rotateCounterClockwise()将其全部旋转 90 度,并添加到pdf_writer:
for page in pdf_reader.pages:
rotated_page = page.rotateCounterClockwise(90)
pdf_writer.addPage(rotated_page)
最后,将pdf_writer的内容写入计算机主目录中名为rotated.pdf的文件:
output_path = Path.home() / "rotated.pdf"
with output_path.open(mode="wb") as output_file:
pdf_writer.write(output_file)
加密和解密 pdf
有时 PDF 文件受密码保护。使用PyPDF2包,您可以处理加密的 PDF 文件,并为现有的 PDF 文件添加密码保护。
**注:**本教程改编自 Python 基础知识:Python 实用入门 3 中“创建和修改 PDF 文件”一章。如果你喜欢你正在阅读的东西,那么一定要看看这本书的其余部分。
加密 pdf
您可以使用PdfFileWriter()实例的.encrypt()方法为 PDF 文件添加密码保护。它有两个主要参数:
user_pwd设置用户密码。这允许打开和阅读 PDF 文件。owner_pwd设置所有者密码。这使得打开 PDF 没有任何限制,包括编辑。
让我们使用.encrypt()为 PDF 文件添加密码。首先,打开practice_files目录下的newsletter.pdf文件:
>>> from pathlib import Path
>>> from PyPDF2 import PdfFileReader, PdfFileWriter
>>> pdf_path = (
... Path.home()
... / "creating-and-modifying-pdfs"
... / "practice_files"
... / "newsletter.pdf"
... )
>>> pdf_reader = PdfFileReader(str(pdf_path))
现在创建一个新的PdfFileWriter实例,并将来自pdf_reader的页面添加到其中:
>>> pdf_writer = PdfFileWriter()
>>> pdf_writer.appendPagesFromReader(pdf_reader)
接下来,用pdf_writer.encrypt()添加密码"SuperSecret":
>>> pdf_writer.encrypt(user_pwd="SuperSecret")
当您只设置了user_pwd时,owner_pwd参数默认为相同的字符串。因此,上面的代码行设置了用户和所有者密码。
最后,将加密的 PDF 写到主目录中的输出文件newsletter_protected.pdf:
>>> output_path = Path.home() / "newsletter_protected.pdf"
>>> with output_path.open(mode="wb") as output_file:
... pdf_writer.write(output_file)
当您使用 PDF 阅读器打开 PDF 时,系统会提示您输入密码。输入"SuperSecret"打开 PDF。
如果您需要为 PDF 设置单独的所有者密码,那么将第二个字符串传递给owner_pwd参数:
>>> user_pwd = "SuperSecret"
>>> owner_pwd = "ReallySuperSecret"
>>> pdf_writer.encrypt(user_pwd=user_pwd, owner_pwd=owner_pwd)
在本例中,用户密码是"SuperSecret",所有者密码是"ReallySuperSecret"。
当您使用密码加密 PDF 文件并试图打开它时,您必须提供密码才能查看其内容。这种保护扩展到在 Python 程序中读取 PDF。接下来我们来看看如何用PyPDF2解密 PDF 文件。
解密 pdf 文件
要解密加密的 PDF 文件,请使用PdfFileReader实例的.decrypt()方法。
.decrypt()有一个名为password的参数,可以用来提供解密的密码。您在打开 PDF 时拥有的权限取决于您传递给password参数的参数。
让我们打开您在上一节中创建的加密的newsletter_protected.pdf文件,并使用PyPDF2来解密它。
首先,用受保护 PDF 的路径创建一个新的PdfFileReader实例:
>>> from pathlib import Path
>>> from PyPDF2 import PdfFileReader, PdfFileWriter
>>> pdf_path = Path.home() / "newsletter_protected.pdf"
>>> pdf_reader = PdfFileReader(str(pdf_path))
在您解密 PDF 之前,请检查如果您尝试获取第一页会发生什么:
>>> pdf_reader.getPage(0)
Traceback (most recent call last):
File "/Users/damos/github/realpython/python-basics-exercises/venv/
lib/python38-32/site-packages/PyPDF2/pdf.py", line 1617, in getObject
raise utils.PdfReadError("file has not been decrypted")
PyPDF2.utils.PdfReadError: file has not been decrypted
出现一个PdfReadError异常,通知您 PDF 文件还没有被解密。
**注:**上述追溯已被缩短以突出重要部分。你在电脑上看到的回溯会更长。
现在开始解密文件:
>>> pdf_reader.decrypt(password="SuperSecret")
1
.decrypt()返回一个表示解密成功的整数:
0表示密码不正确。1表示用户密码匹配。2表示主人密码被匹配。
解密文件后,您可以访问 PDF 的内容:
>>> pdf_reader.getPage(0)
{'/Contents': IndirectObject(7, 0), '/CropBox': [0, 0, 612, 792],
'/MediaBox': [0, 0, 612, 792], '/Parent': IndirectObject(1, 0),
'/Resources': IndirectObject(8, 0), '/Rotate': 0, '/Type': '/Page'}
现在你可以提取文本和作物或旋转页面到你的心的内容!
检查你的理解能力
展开下面的方框,检查您的理解程度:
在本文的配套存储库中的practice_files/文件夹中,有一个名为top_secret.pdf的文件。
使用PdfFileWriter.encrypt(),用用户密码Unguessable加密文件。将加密文件保存为电脑主目录中的top_secret_encrypted.pdf。
您可以展开下面的方框查看解决方案:
设置 PDF 文件的路径:
# First, import the needed classes and libraries
from pathlib import Path
from PyPDF2 import PdfFileReader
# Then create a `Path` object to the PDF file.
# You might need to change this to match the path
# on your computer.
pdf_path = (
Path.home()
/ "creating-and-modifying-pdfs"
/ "practice_files"
/ "top_secret.pdf"
)
现在创建PdfFileReader和PdfFileWriter实例:
pdf_reader = PdfFileReader(str(pdf_path))
pdf_writer = PdfFileWriter()
您可以使用.appendPagesFromReader()添加从pdf_reader到pdf_writer的所有页面:
pdf_writer.appendPagesFromReader(pdf_reader)
现在使用encrypt()将用户密码设置为"Unguessable":
pdf_writer.encrypt(user_pwd="Unguessable")
最后,将pdf_writer的内容写入计算机主目录中名为top_secret_encrypted.pdf的文件:
output_path = Path.home() / "top_secret_encrypted.pdf"
with output_path.open(mode="wb") as output_file:
pdf_writer.write(output_file)
从头开始创建 PDF 文件
PyPDF2包非常适合阅读和修改现有的 PDF 文件,但是它有一个主要的限制:你不能用它来创建一个新的 PDF 文件。在本节中,您将使用 ReportLab 工具包从头开始生成 PDF 文件。
ReportLab 是用于创建 pdf 的全功能解决方案。有一个付费使用的商业版本,但也有一个功能有限的开源版本。
**注意:**这一部分并不是对 ReportLab 的详尽介绍,而是一个可能的示例。
更多示例,请查看 ReportLab 的代码片段页面。
安装reportlab
要开始使用,您需要安装带有pip的reportlab:
$ python3 -m pip install reportlab
您可以使用pip show来验证安装:
$ python3 -m pip show reportlab
Name: reportlab
Version: 3.5.34
Summary: The Reportlab Toolkit
Home-page: http://www.reportlab.com/
Author: Andy Robinson, Robin Becker, the ReportLab team
and the community
Author-email: reportlab-users@lists2.reportlab.com
License: BSD license (see license.txt for details),
Copyright (c) 2000-2018, ReportLab Inc.
Location: c:\users\davea\venv\lib\site-packages
Requires: pillow
Required-by:
在撰写本文时,reportlab的最新版本是 3.5.34。如果你有 IDLE open,那么你需要重新启动它才能使用reportlab包。
使用Canvas类
用reportlab创建 pdf 的主界面是Canvas类,它位于reportlab.pdfgen.canvas模块中。
打开一个新的空闲交互窗口,键入以下内容导入Canvas类:
>>> from reportlab.pdfgen.canvas import Canvas
当您创建一个新的Canvas实例时,您需要提供一个字符串,其中包含您正在创建的 PDF 的文件名。继续为文件hello.pdf创建一个新的Canvas实例:
>>> canvas = Canvas("hello.pdf")
现在您有了一个Canvas实例,它被赋予了变量名canvas,并且与当前工作目录中的一个名为hello.pdf的文件相关联。但是文件hello.pdf还不存在。
让我们给 PDF 添加一些文本。为此,您可以使用.drawString():
>>> canvas.drawString(72, 72, "Hello, World")
传递给.drawString()的前两个参数决定了文本在画布上的书写位置。第一个指定距画布左边缘的距离,第二个指定距下边缘的距离。
传递给.drawString()的值以磅为单位。因为一个点等于 1/72 英寸,所以.drawString(72, 72, "Hello, World")将字符串"Hello, World"绘制在页面左侧一英寸和底部一英寸处。
要将 PDF 保存到文件,请使用.save():
>>> canvas.save()
您现在在当前工作目录中有一个名为hello.pdf的 PDF 文件。可以用 PDF 阅读器打开,看到页面底部的文字Hello, World!
对于您刚刚创建的 PDF,有一些事情需要注意:
- 默认页面尺寸是 A4,这与标准的美国信函页面尺寸不同。
- 字体默认为 Helvetica,字号为 12 磅。
你不会被这些设置束缚住。
设置页面尺寸
当实例化一个Canvas对象时,可以用可选的pagesize参数改变页面大小。该参数接受一个由浮点值组成的元组,以磅为单位表示页面的宽度和高度。
例如,要将页面大小设置为宽8.5英寸,高11英寸,您可以创建下面的Canvas:
canvas = Canvas("hello.pdf", pagesize=(612.0, 792.0))
(612, 792)代表信纸大小的纸张,因为8.5次72是612,而11次72是792。
如果你不喜欢计算将磅转换成英寸或厘米,那么你可以使用reportlab.lib.units模块来帮助你转换。.units模块包含几个助手对象,比如inch和cm,它们简化了你的转换。
继续从reportlab.lib.units模块导入inch和cm对象:
>>> from reportlab.lib.units import inch, cm
现在,您可以检查每个对象,看看它们是什么:
>>> cm
28.346456692913385
>>> inch
72.0
cm和inch都是浮点值。它们代表每个单元中包含的点数。inch是72.0点,cm是28.346456692913385点。
要使用单位,请将单位名称乘以要转换为点的单位数。例如,下面是如何使用inch将页面尺寸设置为8.5英寸宽乘11英寸高:
>>> canvas = Canvas("hello.pdf", pagesize=(8.5 * inch, 11 * inch))
通过向pagesize传递一个 tuple,您可以创建任意大小的页面。然而,reportlab包有一些更容易使用的标准内置页面大小。
页面尺寸位于reportlab.lib.pagesizes模块中。例如,要将页面大小设置为 letter,可以从pagesizes模块导入LETTER对象,并在实例化Canvas时将其传递给pagesize参数:
>>> from reportlab.lib.pagesizes import LETTER
>>> canvas = Canvas("hello.pdf", pagesize=LETTER)
如果您检查LETTER对象,那么您会看到它是一个浮点元组:
>>> LETTER
(612.0, 792.0)
reportlab.lib.pagesize模块包含许多标准页面尺寸。以下是一些尺寸:
| 页面大小 | 规模 |
|---|---|
A4 |
210 毫米 x 297 毫米 |
LETTER |
8.5 英寸 x 11 英寸 |
LEGAL |
8.5 英寸 x 14 英寸 |
TABLOID |
11 英寸 x 17 英寸 |
除此之外,该模块还包含所有 ISO 216 标准纸张尺寸的定义。
设置字体属性
当您向Canvas写入文本时,您还可以更改字体、字体大小和字体颜色。
要更改字体和字体大小,可以使用.setFont()。首先,用文件名font-example.pdf和信纸大小创建一个新的Canvas实例:
>>> canvas = Canvas("font-example.pdf", pagesize=LETTER)
然后将字体设置为 Times New Roman,大小为18磅:
>>> canvas.setFont("Times-Roman", 18)
最后,将字符串"Times New Roman (18 pt)"写入画布并保存:
>>> canvas.drawString(1 * inch, 10 * inch, "Times New Roman (18 pt)")
>>> canvas.save()
使用这些设置,文本将被写在离页面左侧 1 英寸,离页面底部 10 英寸的地方。打开当前工作目录中的font-example.pdf文件并检查它!
默认情况下,有三种字体可用:
"Courier""Helvetica""Times-Roman"
每种字体都有粗体和斜体两种变体。以下是reportlab中所有可用字体的列表:
"Courier""Courier-Bold"Courier-BoldOblique""Courier-Oblique""Helvetica""Helvetica-Bold""Helvetica-BoldOblique""Helvetica-Oblique""Times-Bold""Times-BoldItalic"Times-Italic""Times-Roman"
您也可以使用.setFillColor()设置字体颜色。在下面的示例中,您创建了一个名为font-colors.pdf的带有蓝色文本的 PDF 文件:
from reportlab.lib.colors import blue
from reportlab.lib.pagesizes import LETTER
from reportlab.lib.units import inch
from reportlab.pdfgen.canvas import Canvas
canvas = Canvas("font-colors.pdf", pagesize=LETTER)
# Set font to Times New Roman with 12-point size
canvas.setFont("Times-Roman", 12)
# Draw blue text one inch from the left and ten
# inches from the bottom
canvas.setFillColor(blue)
canvas.drawString(1 * inch, 10 * inch, "Blue text")
# Save the PDF file
canvas.save()
blue是从reportlab.lib.colors模块导入的对象。这个模块包含几种常见的颜色。完整的颜色列表可以在 reportlab源代码中找到。
本节中的例子强调了使用Canvas对象的基础。但你只是触及了表面。使用reportlab,您可以从头开始创建表格、表单,甚至高质量的图形!
ReportLab 用户指南包含了大量如何从头开始生成 PDF 文档的例子。如果您有兴趣了解更多关于使用 Python 创建 pdf 的内容,这是一个很好的起点。
检查你的理解能力
展开下面的方框,检查您的理解程度:
在您计算机的主目录中创建一个名为realpython.pdf的 PDF,其中包含文本"Hello, Real Python!"的信纸大小的页面放置在距离页面左边缘 2 英寸和下边缘 8 英寸的位置。
您可以展开下面的方框查看解决方案:
用信纸大小的页面设置Canvas实例:
from reportlab.lib.pagesizes import LETTER
from reportlab.lib.units import inch
from reportlab.pdfgen.canvas import Canvas
canvas = Canvas("font-colors.pdf", pagesize=LETTER)
现在画一条线"Hello, Real Python!",距离左边两英寸,距离底部八英寸:
canvas.drawString(2 * inch, 8 * inch, "Hello, Real Python!")
最后,保存canvas来编写 PDF 文件:
canvas.save()
当你准备好了,你可以进入下一部分。
结论:用 Python 创建和修改 PDF 文件
在本教程中,您学习了如何使用PyPDF2和reportlab包创建和修改 PDF 文件。如果您想了解刚才看到的示例,请务必点击下面的链接下载材料:
下载示例材料: 单击此处获取您将在本教程中使用学习创建和修改 PDF 文件的材料。
通过PyPDF2,你学会了如何:
- 读取 PDF 文件,使用
PdfFileReader类提取文本 - 使用
PdfFileWriter类编写新的 PDF 文件 - 连接和使用
PdfFileMerger类合并 PDF 文件 - 旋转和裁剪 PDF 页面
- 用密码加密和解密 PDF 文件
您还了解了如何使用reportlab包从头开始创建 PDF 文件。你学会了如何:
- 使用
Canvas类 - 用
.drawString()写文本到Canvas - 用
.setFont()设置字体和字体大小 - 用
.setFillColor()改变字体颜色
reportlab是一个强大的 PDF 创建工具,而你只是触及了它的表面。如果你喜欢在这个例子中从 Python 基础知识:Python 3 实用介绍中学到的东西,那么一定要看看本书的其余部分。
编码快乐!**********