Python 自动化办公 ——— 用 PyPDF2 库对 PDF 实现拆分、合并、水印添加、加密解密操作

大家好,我是小张~,今天文章与自动化办公相关,目前个人认为 Python 库中处理 PDF 比较不错的有三个,分别是 PyPDF2,Pdfplumer 和 PDFminer;

image-20210313210858337

今天教程内容主要聚焦于 PyPDF2 ,借助它对 PDF 实现以下基本操作

  • 1,将单个 PDF 拆分为多个 PDF 文件 ;

  • 2,将多个 PDF 合并为一个 PDF 文件 ;

  • 3,将 PDF 中某页进行旋转 ;

  • 4,对 PDF 添加水印 ;

  • 5,对 PDF 加密 ;

  • 6,对 PDF 进行解密;

  • 6,获取 PDF 基本信息,例如作者、标题、页数等;

PyPDF2 历史

正文开始之前,说一下 PyPDF2 的发展历史 ,PyPDF 的前身是 pyPDf 包在2005年发布,该包的最后一个版本发布于2010年,后来大约经过一年左右, 名为 Phasit 的公司赞助 PyPdf 的一个分支后来命名为 PyPDF2,两个版本功能都基本一样,最大区别就是 PyPDF2 中 加入了支持 Python3 特性;

PyPDF2 近期也没有再更新了,最近一个版本发布在2016年,但使用热度依然没有消退;虽然后面又出现了 PyPDF3、PyPDF4 等不同版本,但这些包并没有对 PyPDF2 功能向后完全兼容,用户受欢迎程度当然也不如 PyPDF2

PyPDF2 安装

与其它Python 库一样,安装可通过 pip 或 conda 工具

pip install pypdf2

PDF 信息提取

使用 PyPDF2 可以从 PDF 中提取到一些元数据和文本信息,对 PDF 有个大致了解

用 PyPDF2 能够提取的数据如下

  • 作者;

  • 创建者;

  • 制作者;

  • Subject;

  • 标题;

  • 页数;

这里我下载了官网提供的 PDF 样本《Seige_of_Vicksburg_Sample_OCR》一共六页,作为测试数据

image-20210313230206113
from  PyPDF2 import PdfFileReader


# # pdf 文档
pdf_path = "D:/Data/自动化办公/PDF/Seige_of_Vicksburg_Sample_OCR.pdf"

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

 txt = f'''{pdf_path} information:
 Author : {infomation.author},
 Creator : {infomation.creator},
 Producer : {infomation.producer},
 Subject : {infomation.subject},
 Title : {infomation.title},
 Number of pages : {number_of_pages}
 '''
 print(txt)

下面为打印结果

D:/Data/自动化办公/PDF/Seige_of_Vicksburg_Sample_OCR.pdf information:
 Author : DSI,
 Creator : LuraDocument PDF Compressor Server 5.5.46.38,
 Producer : LuraDocument PDF v2.38,
 Subject : None,
 Title : Binder1.pdf,
 Number of pages : 6

在上面例子中用到了 PdfFileReader 类,用于与 pdf 文件交互;调用该类中的 getDocumentInfo() 方法返回一个 DocumentInformation 的实例,该实例中存储着我们需要的信息;对 reader 对象调用 getNumPages 方法也可以返回文档页数;

个人看法,这里面的数据也就 页数 有点价值,当批量统计时该方法很适用

PDF 页面旋转

PyPDF2 中 pdf 每一页都是以 page 对象存在,返回某一页的实例可通过 reader 对象中的 get_Page(page_index) 方法,其中 page_index 表示索引

对某一页旋转,有两种方式

  • rotateClockwise(90),顺时针旋转90度;

  • rotateCounterClockwise(90),逆时针旋转 90 度;

下面代码表示将目标 PDF 中第一页顺时针方向旋转 90 度,第二页以逆时针方向旋转 90 度,其它页位置角度不变;

from  PyPDF2 import PdfFileReader,PdfFileWriter

pdf_writer = PdfFileWriter()
pdf_reader = PdfFileReader(pdf_path)
# Rotate page 90 degrees to the right
page_1 = pdf_reader.getPage(0).rotateClockwise(90)
pdf_writer.addPage(page_1)
# Rotate page 90 degrees to the left
page_2 = pdf_reader.getPage(1).rotateCounterClockwise(90)
pdf_writer.addPage(page_2)
# 之后的正常写出
for i in range(2,pdf_reader.getNumPages()):
 pdf_writer.addPage(pdf_reader.getPage(i))

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

结果如下

image-20210313232532349

代码中同时用到了PdfFileReader,PdfFileWriter 这两个类,页面旋转并不是在原有 PDF 基础上进行操作而是在内存处创建了一个新的PDF流对象,将操作后的每一页通过 addPage() 方法加入到这个对象中,之后将内存中的这个对象写入到文件中;

写到这里,说实话其实 页面旋转 这个功能没基本没什么作用,加在这里只是想充当一些字数,哈哈哈

单个 PDF 拆分成多个PDF

from  PyPDF2 import PdfFileReader,PdfFileWriter

# # pdf 文档
pdf_path = "D:/Data/自动化办公/PDF/Seige_of_Vicksburg_Sample_OCR.pdf"
save_path = 'D:/Data/自动化办公/PDF/'

# Split Pages of PDF

pdf_reader = PdfFileReader(pdf_path)
for i in range(0,pdf_reader.getNumPages()):
 pdf_writer = PdfFileWriter()
 pdf_writer.addPage(pdf_reader.getPage(i))
 # Every page write to a path
 with open(save_path+'{}.pdf'.format(str(i)), 'wb') as fh:
 pdf_writer.write(fh)
 print('{} Save Sucessfully !\n'.format(str(i)))

代码将 PDF 原文件中的每一页拆分到每一个PDF文件,其中文件名用页索引来命名;

image-20210313235957539

通过拆分也可以提取到 pdf 文件中固定页码范围,例如我只想提取 pdf 中的 2-5 页,其它部分不要,那么代码将写成下面形式

pdf_writer = PdfFileWriter()
pdf_reader = PdfFileReader(pdf_path)
for i in range(1,5):
 # pdf_writer = PdfFileWriter()
 pdf_writer.addPage(pdf_reader.getPage(i))
 # Every page write to a path
with open(save_path+'2_5.pdf', 'wb') as fh:
 pdf_writer.write(fh)

多个 PDF 文件合并为单个

pdf 拆分与合并方向虽然相反,但用到的类、原理都是一样的

PdfFileReader读取每个pdf,并递归获取每一页page 对象, PdfFileWrite 新建一个流对象,把前面内存中读取到的 page 对象按顺序写入到这个流对象中,最后写入到磁盘文件

···
from PyPDF2 import PdfFileReader,PdfFileWriter

p1_pdf = "D:/Data/自动化办公/PDF/Seige_of_Vicksburg_Sample_OCR.pdf"
p2_pdf = "D:/Data/自动化办公/PDF/Seige_of_Vicksburg_Sample_OCR.pdf"

merge_pdf = 'D:/Data/自动化办公/PDF/merge.pdf'

p1_reader = PdfFileReader(p1_pdf)
p2_reader = PdfFileReader(p2_pdf)

merge = PdfFileWriter()

Write p1

for i in range(0,p1_reader.getNumPages()):
merge.addPage(p1_reader.getPage(i))

Write p2

for j in range(0,p2_reader.getNumPages()):
merge.addPage(p2_reader.getPage(j))

Write out

with open(merge_pdf,'wb') as f:
merge.write(f)
···

运行结果如下

image-20210314002536754

PDF 添加水印

在今天列举的这么多功能中,我想这个功能是最有用,批量添加水印主要用到 page 对象中的 margePage() 方法,通过将两个页面合并来达到添加水印的效果

因为 PyPDF2 只能操作 pdf 对象,因此在添加水印之前,需要将准备添加的水印存放到一个 pdf 文件中

···
from PyPDF2 import PdfFileReader,PdfFileWriter
watermark = 'D:/Data/自动化办公/PDF/watermark.pdf'
input_pdf = 'D:/Data/自动化办公/PDF/merge.pdf'
output = 'D:/Data/自动化办公/PDF/merge_watermark.pdf'

watermark_obj = PdfFileReader(watermark)
watermark_page = watermark_obj.getPage(0)

pdf_reader = PdfFileReader(input_pdf)
pdf_writer = PdfFileWriter()

Watermark all the pages

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)
···

效果如下,从左到右,依次为原图、水印、添加水印后的原图    
从左到右,依次是原图、水印、加完水印的效果图

上面效果不好是因为制作水印时没有考虑到页面布局问题,所以合并时出现一部分缺失;

用以上代码添加水印的好处是,可以对 pdf 指定页田间水印,比如说只对奇数页添加偶数页不管,不但灵活性强而且高效,当然也可以对多个文件进行批量操作

PDF加密解密

pdf加密

对一份 pdf 文件,如果我们不想让其他人能够读取里面的内容,可以通过 pypdf2 对它设置密码,如果只是单个文件的话,建议最好自己找个工具受手动操作一下会高效一点,但若是多个文件,非常建议用下面方法

···
watermark = 'D:/Data/自动化办公/PDF/Seige_of_Vicksburg_Sample_OCR.pdf'
input_pdf = 'D:/Data/自动化办公/PDF/merge.pdf'
output = 'D:/Data/自动化办公/PDF/merge_watermark1.pdf'

watermark_obj = PdfFileReader(watermark)
watermark_page = watermark_obj.getPage(0)

pdf_reader = PdfFileReader(input_pdf)
pdf_writer = PdfFileWriter()

Watermark all the pages

for page in range(pdf_reader.getNumPages()):
page = pdf_reader.getPage(page)
page.mergePage(watermark_page)
pdf_writer.addPage(page)
pdf_writer.encrypt(user_pwd='123456',
use_128bit=True)
with open(output, 'wb') as out:
pdf_writer.write(out)
···

image-20210314092935806

主要用到 encrypt 函数,需要注意三个参数

  • user_pwd,str,用户密码,用来限制打开读取文件;

  • owner_pwd,str,比用户密码更高一级,提供时可让打开文件不受任何限制,不指定时默认owner_pwd 与 user_pwd 相同;

  • use_128bit 布尔值,用来表示是否使用128位作为密码,False 时代表用 40 位密码,默认为True;

pdf解密

解密是在读取文件时用的,用到 decrypt() 函数

from PyPDF2 import PdfFileWriter, PdfFileReader

input_pdf='reportlab-encrypted.pdf'
output_pdf='reportlab.pdf'
password='twofish'

pdf_writer = PdfFileWriter()
pdf_reader = PdfFileReader(input_pdf)
pdf_reader = pdf_reader.decrypt(password)

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

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

上面例子中解密原理是 通过将一个加密文件进行读取,并写入到一个非加密 pdf 中

小结

本文介绍了 PyPDF2 库的基本用法,借助它加上代码实例实现了一些基本操作;但在这里提醒一下,所有上面这些操作只适用于批量操作场景,如果对象是单个文件的话建议用常规做法,过于炫技的话只会浪费时间

关于 pdf 内的图文内容提取、写入本文并没有涉猎,源于 pypdf2 对于这方面并不擅长,而 Pdfplumber 和 PDFminer 在文本提取方面要好得多,工欲善其事,必先利其器;在之后的教程中我将会介绍一下这方面的内容,期待大家的关注!

好了以上就是本篇内容的全部内容,最后感谢大家的阅读,我们下期见~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容