【日常】从批量合并 PDF 到 PyPDF2 的使用

序言

临近卷铺走人因此有不少手续要办,提交文件遇到需要合并PDF文件的需求。恰好个人电脑还处于文件强制加密的状态,编辑文档保存会自动加密,出于某些原因不方便恢复到正常状态的备份,因此给合并PDF带来很多麻烦。

这时候会考虑是否有捷径可以走,这时候笔者发现Python是有可以进行PDF文件编辑操作的开源包PyPDF2,简单pip安装即可👇

pip install pypdf2

下面提供了一个非常便捷的用于批量合并PDF文件的函数,只需要传入需要合并的PDF文件所在目录,以及合并后的文件导出路径两个变量(pdf_path, save_path)即可👇

# -*- coding: UTF-8 -*-
# Author: 囚生CY
# 合并pdf的工具函数

import os
from PyPDF2 import PdfFileReader,PdfFileWriter

# 合并同一目录下的所有PDF文件
def merge_pdf(pdf_path,save_path):										 # pdf_path为需要合并的所有pdf所在路径, save_path为合并后的文件导出路径
	writer = PdfFileWriter()											 # 创建PDF文件书写器对象
	total_pages = 0														 # 标记页码数
	pdf_filepaths = []													 # 用于存储所有的pdf的路径
	for root,dirname,filenames in os.walk(pdf_path):
		for filename in filenames:								
			try: suffix = filename[-4:]
			except: continue											 # 这个报错说明文件名连4位都没有, 直接跳过即可
			if suffix==".pdf":											 # 说明是pdf文件
				pdf_filepaths.append(os.path.join(pdf_path,filename))

	for path in pdf_filepaths:											 # 遍历所有pdf我呢见
		reader = PdfFileReader(open(path,"rb"))							 # 读取pdf文件
		pages = reader.getNumPages()									 # 获取当前pdf文件的页码数
		total_pages += pages											 # 更新总页码数
		for i in range(pages): writer.addPage(reader.getPage(i))		 # 遍历当前pdf文件的每一页: 将每一页的信息添加到pdf文件书写器中
		print("已添加{}\t共{}页,累计{}页".format(path,pages,total_pages))
	writer.write(open(save_path,"wb"))
	print("PDF文件合并完成!")

if __name__ == "__main__":
	pdf_path = "raw"
	save_path = "merged_file.pdf"
	merge_pdf(pdf_path,save_path)

应该说合并的效率是非常高的,并且结果也是很满意的,注意合并的顺序是文件名的默认排序,如果需要自定义顺序的话可以在PDF文件名前添加001, 002, ... , 010的前缀标注顺序即可。

PyPDF2 使用

PyPDF2官方文档

事实上PyPDF2是个很小的包,在site-packages中可以看到它只有一层目录,而且主要的功能代码只在pdf.py中,但是这并不影响PyPDF2仍是一个有很多重要功能的包。因此在索用了前人的劳动成果解决问题后,细看当中还是否有其他的可取用的知识也是不错的选择。

其实从序言中的代码逻辑里不难看出,PyPDF2可以任意挑选PDF页面进行任意拼接的操作,因此可以很简单的实现PDF页面级别的增删改操作。事实上PyPDF2中只有两个功能类PdfFileWriter, PdfFileReader, 但是这两个类中都封装了很多功能,前者除了对PDF文件的简单页面编辑操作外,还可以对PDF文件进行一些改动,如加密、添加启动时的将执行的操作等;后者则是用于挖掘PDF文件中的信息,比如使用LATEX生成的PDF中就可以很容易的去查出reference, contents等信息,并且相对应PdfFileWriter中的加密,这里还给出了解密的功能。

PdfFileWriter类

writer对象可以为最终导出的PDF文件增加各种信息,如Metadata, Link等,这里主要介绍一下addJSencrypt方法👇

writer.addBlankPage(1080,1920)          # 添加一个空白页面, 参数为页面的宽与高
writer.addMetadata({"version":"1.0.1"}) # 添加metadata后使用PdfFileReader对象可以读取metadata
writer.addJS("alert('FBI Warning!');")  # 添加javascript可以在启动时执行,但是这个似乎对PDF阅读软件有限制,笔者使用的Foxit似乎不支持这个效果
writer.addBookMark(title="Chapter 1",pagenum=1) # 指定页码添加书签
writer.encrypt("123456","12345678")     # 加密文档: 第一个参数是使用者的密码, 以该密码进入后权限受限制, 后者为所有者密码, 拥有文档的所有权限
writer.removeImages()                   # 移除文档中的所有图片
writer.removeLinks()                    # 移除文档中的所有链接
writer.removeText()                     # 移除文档中的所有文字
  • addJS方法可以加入一段在导出的PDF文件启动时立即执行的JS,虽然作为JS小白并不能搞清楚脱离HTML页面究竟应该如何写JS代码,但是简单写个alert应该是没有太大问题的。但是这个似乎对PDF阅读器有所限制,至少笔者使用的Foxit阅读器是不能执行JS代码的。标准的Adobe阅读器应该是可行的。我有看到别人写"this.print({bUI:true,bSilent:false,bShrinkToFit:true});",虽然并不太懂是什么意思[Faceplam]
  • encrypt可以给导出的PDF文档加密,启动时需要输入密码才能进入,并且可以设定两种不同权限的密码——使用者密码与所有者密码,可以很容易的实现。

此外writer对象还可以最终移除导出文档中所有图片、链接、文字,以及最终可以修改页面格式👇

 

PdfFileReader类

reader中有很多方法是和writer相对应的,writer设置了一些什么,就会在reader中得到什么👇

reader.decrypt("123456") # 解密进入文档
reader.getNumPages()     # 总页数
reader.getOutlines()     # 获取提纲
reader.getPageLayout()   # 获取页面布局情况
reader.getPageMode()     # 获取页面模式
reader.getXmpMetadata()  # 获取元信息
reader.getBookMark()     # 获取书签

其他一些诸如getDocumentInfo()以及getFields()等PDF文件的属性信息不多作介绍。

 

PageObject

其实里面还有一个PageObject类,使用reader获取到的每一页就是一个PageObject类👇

page = reader.getPage(0) # 获取当前pdf文件的第一页

使用该类中的方法可以进行页面尺寸的裁剪,扩张,以及进行页面旋转的操作,这样使得在最终的合并结果中可以有形色各异的页面,不过这已经属于设计的范畴,笔者觉得没有太大深究的必要👇

page = page.rotateClockwise(90)        # 顺时针旋转90°
page = page.rotateCounterClockwise(90) # 逆时针旋转90°

page = page1.mergePage(page2)          # 合并两个页面, 比如当page2是水印图片页时可以起到对page1增加水印的效果

# 裁剪页面大小为当前的一半
page = page.mediaBox.upperRight = (
    page.mediaBox.getUpperRight_x() / 2,
    page.mediaBox.getUpperRight_y() / 2
)

值得注意的是PageObject类当中有一个page.extractText()方法可以用于挖掘页面中的文字信息,不过这个方法并不总是能奏效,只有当PDF是非常工整的文字型的PDF时才能比较精确的获取到页面中的文字信息。

后记

其实很多时候不仔细过一遍可能只觉得PyPDF2只是可以编辑重组页面,细细品后发现还是有很多有趣的功能。好在今晚不加班,抽空写了这篇博客。

总之分享学习,共同进步!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章