PDF文档
PDF 表示 Portable Document Format,使用.pdf文件扩展名。虽然 PDF 支持许多功能,但本章将专注于最常做的两件事:从 PDF 读取文本内容和从已有的文档生成新的PDF。
用于处理 PDF 的模块是 PyPDF2 。要安装它,就从命令行运行 pip install PyPDF2
。这个模块名称是区分大小写的,所以要确保y是小写,其他字母都是大写。如果该模块安装正确,在交互式环境中运行import PyPDF2,应该不会显示任何错误。
PyPDF2 默认只能处理英文,如果想让 PyPDF2 能够处理中文,则需要小小改动一下源码(见附录)。
pip install PyPDF2
pip install pdfplumber
下载测试文件
创建 PDF
在 PyPDF2 中,与 PdfReader 对象相对的是 PdfWriter 对象,它可以创建一个新的 PDF 文件。但 PyPDF2 不能将任意文本写入 PDF ,就像 Python 可以写入纯文本文件那样。PyPDF2 写入 PDF 的能力,仅限于从其他 PDF 中拷贝页面、旋转页面、重叠页面和加密文件。
模块不允许直接编辑 PDF。必须创建一个新的 PDF,然后从已有的文档拷贝内容。本节的例子将遵循这种一般方式:
1.打开一个或多个已有的 PDF(源PDF),得到 PdfReader 对象。
2.创建一个新的 PdfWriter 对象。
3.将页面从 PdfFileReader 对象拷贝到 PdfFileWriter 对象中。
4.最后,利用 PdfWrite r 对象写入输出的 PDF。
创建一个 PdfWriter 对象,只是在 Python 中创建了一个代表 PDF 文档的值,这并没有创建实际的 PDF 文件,要实际生成文件,必须调用 PdfWriter 对象的 write() 方法。
write() 方法接受一个普通的 File 对象,它以写二进制的模式打开。你可以用两个参数调用 Python 的 open() 函数,得到这样的 File 对象:一个是要打开的 PDF 文件名字符串,一个是 'wb' ,表明文件应该以写二进制的模式打开。
如果这听起来有些令人困惑,不用担心,在接下来的代码示例中,你会看到这种工作方式。
拷贝页面
可以利用 PyPDF2 ,从一个 PDF 文档拷贝页面到另一个 PDF 文档。这让你能够组合多个 PDF 文件,去除不想要的页面,或调整页面的次序。
from PyPDF2 import PdfReader, PdfWriter
"""分割前二十页"""
# 读取 pdf 文件
pdf_reader = PdfReader('金发科技2019年年报.pdf')
# 获取所有的总页数
for page in range(len(pdf_reader.pages)):
# 创建一个 pdf 写入对象
pdf_writer = PdfWriter()
# 往写入对象中添加一页pdf内容
pdf_writer.add_page(pdf_reader.pages[page])
# 将 pdf 写入对象保存到本地
with open(f'金发科技2019年年报 {page}.pdf', 'wb') as out:
pdf_writer.write(out)
# 写入前二十页就可以了
if page > 20:
break
提取多页写入一个文件
from PyPDF2 import PdfReader, PdfWriter
"""一次分割多页数据"""
pdf_reader = PdfReader('金发科技2019年年报.pdf')
pdf_writer = PdfWriter()
# 循环添加二十页数据
for page in range(20):
pdf_writer.add_page(pdf_reader.pages[page])
with open('金发科技2019年年报20页.pdf', 'wb') as out:
pdf_writer.write(out)
从 PDF 提取文本
PyPDF2 没有办法从 PDF 文档中提取图像、图表或其他媒体,但它可以提取文本,并将文本返回为 Python 字符串。为了开始学习 PyPDF2 的工作原理,我们将它用于一个示例 PDF
有问题的PDF格式
虽然PDF文件对文本布局非常好,让人们很容易打印并阅读,但软件要将它们解析为纯文本却并不容易。因此,PyPDF2从PDF提取文本时可能会出错,甚至根本不能打开某些PDF。遗憾的是,你对此没有什么办法,PyPDF2可能就是不能处理某些PDF文件。话虽这样说,我至今没有发现不能用PyPDF2打开的PDF文件。
import pdfplumber
import openpyxl
"""
中文内容读取需要修改部分源码内容
"""
pdf_filename = '金发科技2019年年报20页.pdf'
with pdfplumber.open(pdf_filename) as pdf:
"""提取文字"""
# 读取第一页数据
first_page = pdf.pages[0]
print(first_page.extract_text())
for page in pdf.pages:
print(page.extract_text())
"""提取表格"""
# 第五页
page_5 = pdf.pages[5 - 1]
print(page_5.extract_table())
# 第六页
page_6 = pdf.pages[6 - 1]
print(page_6.extract_tables())
"""
1.打开文件金发科技2019年年报20页.pdf
2.提取第5页的表格
3.保存到 第五页表格.xlsx 文件
"""
table = page_5.extract_table()
workbook = openpyxl.Workbook()
sheet = workbook.active
for row in table:
print(row)
sheet.append(row)
workbook.save('第五页表格.xlsx')
添加水印
PyPDF2 也可以将一页的内容叠加到另一页上,这可以用来在页面上添加公司标志、时间戳或水印。利用Python,很容易为多个文件添加水印,并且只针对程序指定的页面添加。
from PyPDF2 import PdfWriter, PdfReader
import copy
# 加载水印文件
watermark_paf = PdfReader('logo.pdf', strict=False)
watermark_page = watermark_paf.pages[len(watermark_paf.pages) - 1]
pdf_reader = PdfReader("金发科技2019年年报20页.pdf")
pdf_writer = PdfWriter()
"""
对每一页都进行合并水印的操作 ,注意.mergePage() 方法合成的页面顺序
下面的内容.mergePage(出现在上面的内容)
"""
for page in range(len(pdf_reader.pages)):
original_page = pdf_reader.pages[page]
# 复制一页水印
new_page = copy.copy(watermark_page)
# 合并水印
new_page.merge_page(original_page)
pdf_writer.add_page(new_page)
with open('金发科技2019年年报20页 加水印.pdf', 'wb') as out:
pdf_writer.write(out)
加密 PDF
PdfFileWriter 对象也可以为 PDF 文档进行加密。
在调用 write() 方法保存文件之前,调用 encrypt() 方法,传入口令字符串。PDF 可以有一个用户口令(允许查看这个PDF)和一个拥有者口令(允许设置打印、注释、提取文本和其他功能的许可)。用户口令和拥有者口令分别是 encrypt() 的第一个和第二个参数。如果只传入一个字符串给 encrypt(),它将作为两个口令。
from PyPDF2 import PdfWriter, PdfReader
pdf_reader = PdfReader("Netease Q2 2019 Earnings Release-Final.pdf")
pdf_writer = PdfWriter()
for page in range(pdf_reader.getNumPages()):
pdf_writer.addPage(pdf_reader.getPage(page))
"""在保存之前加密
pdf_writer.encrypt(密码)
"""
pdf_writer.encrypt('123456')
with open('encrypt.pdf', 'wb') as out:
pdf_writer.write(out)
print('写入完毕')
解密 PDF
某些PDF文档有加密功能,以防止别人阅读,只有在打开文档时提供口令才能阅读。在交互式环境中输入以下代码,处理下载的PDF,它已经用口令rosebud加密:
"""读取时解密
pdf_reader.decrypt(密码)
"""
decrypt_reader = PdfReader('encrypt.pdf')
print('开始解密')
decrypt_reader.decrypt('123456')
print(decrypt_reader.getNumPages())
for page in range(decrypt_reader.getNumPages()):
print(decrypt_reader.pages)
所有 PdfReader 对象都有一个 isEncrypted 属性,如果 PDF 是加密的,它就是 True,如果不是,它就是 False。在文件用正确的口令解密之前,尝试调用函数来读取文件,将会导致错误。
要读取加密的 PDF,就调用 decrypt() 函数,传入口令字符串。在用正确的口令调用 decrypt() 后,你会看到调用 get_page() 不再导致错误。如果提供了错误的口令,decrypt() 函数将返回0,并且 get_page() 会继续失败。请注意,decrypt() 方法只解密了 PdfReader 对象,而不是实际的 PDF 文件。在程序中止后,硬盘上的文件仍然是加密的。程序下次运行时,仍然需要再次调用 decrypt() 。