PDF 表示 Portable Document Format,使用.pdf 文件扩展名。虽然 PDF 支持许多
功能,但本章将专注于最常做的两件事:从 PDF 读取文本内容和从已有的文档生成
新的 PDF。用于处理 PDF的模块是 PyPDF2。要安装它,就从命令行运行 pip install PyPDF2。
这个模块名称是区分大小写的,所以要确保 y 是小写,其他字母都是大写(请查看
附录 A,了解安装第三方模块的所有细节)。如果该模块安装正确,在交互式环境
中运行 import PyPDF2,应该不会显示任何错误。
从 PDF 提取文本
PyPDF2 没有办法从 PDF 文档中提取图像、图表或其他媒体,但它可以提取文
本,并将文本返回为 Python 字符串。为了开始学习 PyPDF2 的工作原理,我们将它
用于一个示例 PDF,如图 13-1 所示
从 http://nostarch.com/automatestuff/下载这个 PDF 文件,并在交互式环境中输入以下代码:
>>> import PyPDF2
>>> pdfFileObj = open('meetingminutes.pdf', 'rb')
>>> pdfReader = PyPDF2.PdfFileReader(pdfFileObj)
>>> pdfReader.numPages
19
>>> pageObj = pdfReader.getPage(0)
>>> pageObj.extractText()
'OOFFFFIICCIIAALL BBOOAARRDD MMIINNUUTTEESS Meeting of \nMarch 7\n, 2014\n \n The Board of Elementary and Secondary Education shall provide leadership and \ncreate policies for education that expand opportunities for children, empower \nfamilies and communities, and advance Louisiana in an increasingly \ncompetitive glob\nal market.\n BOARD \n of ELEMENTARY\n and \n SECONDARY\n EDUCATION\n '
首先,导入 PyPDF2 模块。然后以读二进制模式打开 meetingminutes.pdf,并将它保存在 pdfFileObj 中。为了取得表示这个 PDF 的 PdfFileReader 对象,调用 PyPDF2.PdfFileReader()并向它传入 pdfFileObj。将这个 PdfFileReader 对象保存在 pdfReader 中。该文档的总页数保存在 PdfFileReader 对象的 numPages 属性中。示例 PDF 文档有 19 页,但我们只提取第一页的文本。要从一页中提取文本,需要通过 PdfFileReader 对象取得一个 Page 对象,它表示 PDF 中的一页。可以调用 PdfFileReader 对象的 getPage()方法,向它传入感兴趣的页码(在我们的例子中是 0),从而取得 Page 对象。PyPDF2 在取得页面时使用从 0 开始的下标:第一页是 0 页,第二页是 1 页,以此类推。 事情总是这样,即使文档中页面的页码不同。例如,假定你的 PDF 是从一个较长的报告中抽取出 3 页,它的页码分别是 42、 43 和 44,要取得这个文档的第一页,需要调用 pdfReader.getPage(0),而不是 getPage(42)或 getPage(1)。在取得 Page 对象后,调用它的 extractText()方法,返回该页文本的字符串。文本提取并不完美:该 PDF 中的文本 Charles E.“Chas” Roemer, President,在函数返回的字符串中消失了,而且空格有时候也会没有。但是,这种近似的 PDF 文本内容,可能对你的程序来说已经足够了。
解密 PDF
某些 PDF 文档有加密功能,以防止别人阅读,只有在打开文档时提供口令才能阅读。在交互式环境中输入以下代码,处理下载的 PDF,它已经用口令 rosebud 加密:
>>> import PyPDF2
>>> pdfReader = PyPDF2.PdfFileReader(open('encrypted.pdf', 'rb'))
>>> pdfReader.isEncrypted
True
>>> pdfReader.getPage(0)
Traceback (most recent call last):
File "", line 1, in
pdfReader.getPage()
--snip--
File "C:\Python34\lib\site-packages\PyPDF2\pdf.py", line 1173, in getObject
raise utils.PdfReadError("file has not been decrypted")
PyPDF2.utils.PdfReadError: file has not been decrypted
>>> pdfReader.decrypt('rosebud')
1
>>> pageObj = pdfReader.getPage(0)
所有 PdfFileReader 对象都有一个 isEncrypted 属性,如果 PDF 是加密的,它就是 True,如果不是,它就是 False。在文件用正确的口令解密之前,尝试调用函数来读取文件,将会导致错误。要读取加密的 PDF,就调用 decrypt()函数,传入口令字符串。在用正确的口令调用 decrypt()后,你会看到调用 getPage()不再导致错误。如果提供了错误的口令,decrypt()函数将返回 0,并且 getPage()会继续失败。请注意, decrypt()方法只解密了PdfFileReader 对象,而不是实际的 PDF 文件。在程序中止后,硬盘上的文件仍然是加密的。程序下次运行时,仍然需要再次调用 decrypt()。
创建 PDF
在 PyPDF2 中,与 PdfFileReader 对象相对的是 PdfFileWriter 对象,它可以创建一个新的 PDF 文件。但 PyPDF2 不能将任意文本写入 PDF,就像 Python 可以写入纯文本文件那样。 PyPDF2 写入 PDF 的能力,仅限于从其他 PDF 中拷贝页面、旋转页面、重叠页面和加密文件。模块不允许直接编辑 PDF。必须创建一个新的 PDF,然后从已有的文档拷贝内容。本节的例子将遵循这种一般方式:
1.打开一个或多个已有的 PDF(源 PDF),得到 PdfFileReader 对象。
2.创建一个新的 PdfFileWriter 对象。
3.将页面从 PdfFileReader 对象拷贝到 PdfFileWriter 对象中。
4.最后,利用 PdfFileWriter 对象写入输出的 PDF。
创建一个 PdfFileWriter 对象,只是在 Python 中创建了一个代表 PDF 文档的值,这并没有创建实际的 PDF 文件,要实际生成文件,必须调用 PdfFileWriter 对象的 write()方法。write()方法接受一个普通的 File 对象,它以写二进制的模式打开。你可以用两个参数调用 Python 的 open()函数,得到这样的 File 对象:一个是要打开的 PDF 文件名字符串,一个是'wb',表明文件应该以写二进制的模式打开。如果这听起来有些令人困惑,不用担心,在接下来的代码示例中,你会看到这种工作方式。
拷贝页面
可以利用 PyPDF2,从一个 PDF 文档拷贝页面到另一个 PDF 文档。这让你能够组合多个 PDF 文件,去除不想要的页面,或调整页面的次序。从 http://nostarch.com/automatestuff/下载 meetingminutes.pdf 和 meetingminutes2.pdf,放在当前工作目录中。在交互式环境中输入以下代码:
>>> import PyPDF2
>>> pdf1File = open('meetingminutes.pdf', 'rb')
>>> pdf2File = open('meetingminutes2.pdf', 'rb')
>>> pdf1Reader = PyPDF2.PdfFileReader(pdf1File)
>>> pdf2Reader = PyPDF2.PdfFileReader(pdf2File)
>>> pdfWriter = PyPDF2.PdfFileWriter()
>>> for pageNum in range(pdf1Reader.numPages):
... pageObj = pdf1Reader.getPage(pageNum)
... pdfWriter.addPage(pageObj)
...
>>> for pageNum in range(pdf2Reader.numPages):
... pageObj = pdf2Reader.getPage(pageNum)
... pdfWriter.addPage(pageObj)
...
>>> pdfOutputFile = open('combinedminutes.pdf', 'wb')
>>> pdfWriter.write(pdfOutputFile)
>>> pdfOutputFile.close()
>>> pdf1File.close()
>>> pdf2File.close()
以读二进制的模式打开两个 PDF 文件,将得到的两个 File 对象保存在 pdf1File 和pdf2File 中。调用 PyPDF2.PdfFileReader(),传入 pdf1File,得到一个表示 meetingminutes.pdf的 PdfFileReader 对象。再次调用 PyPDF2.PdfFileReader(), 传入 pdf2File,得到一个表示 meetingminutes2.pdf 的 PdfFileReader 对象。然后创建一个新的 PdfFileWriter对象,它表示一个空白的 PDF 文档。接下来,从两个源 PDF 拷贝所有的页面,将它们添加到 PdfFileWriter 对象。在PdfFileReader 对象上调用 getPage(),取得 Page 对象。然后将这个 Page 对象传递给 PdfFileWriter 的 addPage()方法。这些步骤先是针对 pdf1Reader 进行, 然后再针对pdf2Reader 进行。在拷贝页面完成后,向 PdfFileWriter 的 write()方法传入一个 File对象,写入一个新的 PDF 文档,名为 combinedminutes.pdf。现在你创建了一个新的 PDF 文件,将来自 meetingminutes.pdf 和 meetingminutes2.pdf 的页面组合在一个文档中。要记住,传递给 PyPDF2.PdfFileReader()的 File 对象,需要以读二进制的方式打开。即使用'rb'作为 open()的第二个参数。类似的,传入 PyPDF2.PdfFileWriter()的 File 对象需要以写二进制的模式打开,即使用'wb'。
旋转页面
利用 rotateClockwise()和 rotateCounterClockwise()方法, PDF 文档的页面也可以旋转 90 度的整数倍。向这些方法传入整数 90、 180 或 270 就可以了。在交互式环境中输入以下代码,同时将 meetingminutes.pdf 放在当前工作目录中:
>>> import PyPDF2
>>> minutesFile = open('meetingminutes.pdf', 'rb')
>>> pdfReader = PyPDF2.PdfFileReader(minutesFile)
>>> page = pdfReader.getPage(0)
>>> page.rotateClockwise(90)
{'/Contents': [IndirectObject(961, 0), IndirectObject(962, 0),
--snip--
}
>>> pdfWriter = PyPDF2.PdfFileWriter()
>>> pdfWriter.addPage(page)
>>> resultPdfFile = open('rotatedPage.pdf', 'wb')
>>> pdfWriter.write(resultPdfFile)
>>> resultPdfFile.close()
>>> minutesFile.close()
这里 ,我们使用 getPage(0)来选择 PDF 的第一页,然后对该页调用rotateClockwise(90)。我们将旋转过的页面写入一个新的 PDF 文档,并保存为rotatedPage.pdf。得到的 PDF文件有一个页面,顺时针旋转了 90 度,如图 13-2 所示。 rotateClockwise()和 rotateCounterClockwise()的返回值包含许多信息,你可以忽略。
叠加页面
PyPDF2 也可以将一页的内容叠加到另一页上,这可以用来在页面上添加公司标志、时间戳或水印。利用 Python,很容易为多个文件添加水印,并且只针对程序指定的页面添加。从 http://nostarch.com/automatestuff/下载 watermark.pdf,将它和 meetingminutes.pdf 一起放在当前工作目录中。然后在交互式环境中输入以下代码:
>>> import PyPDF2
>>> minutesFile = open('meetingminutes.pdf', 'rb')
>>> pdfReader = PyPDF2.PdfFileReader(minutesFile)
>>> minutesFirstPage = pdfReader.getPage(0)
>>> pdfWatermarkReader = PyPDF2.PdfFileReader(open('watermark.pdf', 'rb'))
>>> minutesFirstPage.mergePage(pdfWatermarkReader.getPage(0))
>>> pdfWriter = PyPDF2.PdfFileWriter()
>>> pdfWriter.addPage(minutesFirstPage)
>>> for pageNum in range(1, pdfReader.numPages):
... pageObj = pdfReader.getPage(pageNum)
... pdfWriter.addPage(pageObj)
...
>>> resultPdfFile = open('watermarkCover.pdf', 'wb')
>>> pdfWriter.write(resultPdfFile)
>>> minutesFile.close()
>>> resultPdfFile.close()
这里我们生成了 meetingminutes.pdf 的 PdfFileReader 对象。调用 getPage(0),取得第一页的 Page 对象,并将它保存在 minutesFirstPage 中。然后生成了 watermark.pdf的 PdfFileReader 对象,并在 minutesFirstPage 上调用 mergePage()。传递给 mergePage()的参数,是 watermark.pdf 第一页的 Page 对象。既然我们已经在 minutesFirstPage 上调用了 mergePage(), minutesFirstPage 就代表加了水印的第一页。我们创建一个 PdfFileWriter 对象,并加入加了水印的第一页。然后循环遍历 meetingminutes.pdf 的剩余页面,将它们添加到 PdfFileWriter 对象中。最后,我们打开一个新的 PDF 文件 watermarkedCover.pdf,并将 PdfFileWriter 的内容写入该文件。图 13-3 展示了结果。新的 PDF 文件 watermarkedCover.pdf,包含 meetingminutes.pdf的全部内容,并在第一页加了水印。
加密 PDF
PdfFileWriter 对象也可以为 PDF 文档进行加密。在交互式环境中输入以下代码:
>>> import PyPDF2
>>> pdfFile = open('meetingminutes.pdf', 'rb')
>>> pdfReader = PyPDF2.PdfFileReader(pdfFile)
>>> pdfWriter = PyPDF2.PdfFileWriter()
>>> for pageNum in range(pdfReader.numPages):
... pdfWriter.addPage(pdfReader.getPage(pageNum))
...
>>> pdfWriter.encrypt('swordfish')
>>> resultPdf = open('encryptedmiutes.pdf', 'wb')
>>> pdfWriter.write(resultPdf)
>>> resultPdf.close()
在调用 write()方法保存文件之前,调用 encrypt()方法,传入口令字符串。 PDF 可以有一个用户口令(允许查看这个 PDF)和一个拥有者口令(允许设置打印、注释、提取文本和其他功能的许可)。用户口令和拥有者口令分别是 encrypt()的第一个和第二
个参数。如果只传入一个字符串给 encrypt(),它将作为两个口令。在这个例子中,我们将 meetingminutes.pdf 的页面拷贝到 PdfFileWriter 对象。用口令 swordfish 加密了 PdfFileWriter,打开了一个名为 encryptedminutes.pdf 的新 PDF,将PdfFileWriter 的内容写入新 PDF。任何人要查看 encryptedminutes.pdf, 都必须输入这个口令。在确保文件的拷贝被正确加密后,你可能会删除原来的未加密的文件。