Python中使用pypdf2合并、分割、加密pdf文件的代码详解


Posted in Python onMay 21, 2019

朋友需要对一个pdf文件进行分割,在网上查了查发现这个pypdf2可以完成这些操作,所以就研究了下这个库,并做一些记录。首先pypdf2是python3版本的,在之前的2版本有一个对应pypdf库。

可以使用pip直接安装:

pip install pypdf2

官方文档: pythonhosted.org/PyPDF2/

里面主要有这几个类:

PdfFileReader 。

该类主要提供了对pdf文件的读操作,其构造方法为:

PdfFileReader(stream, strict=True, warndest=None, overwriteWarnings=True)

第一个参数可以传入一个文件流,或者一个文件路径。后面三个参数都是用来设置警告的处理方式,直接使用默认的即可。

得到实例之后,就可以对pdf进行一些操作了。主要的有以下几个操作:

  • decrypt(password):如果pdf文件加密的话,可以使用该方法对其解密。
  • getDocumentInfo():检索pdf文件的一些信息。其返回值为一个DocumentInformation 类型,直接输出的话会得到类似下面的信息:
{'/ModDate': "D:20150310202949-07'00'", '/Title': '', '/Creator': 'LaTeX with hyperref package', '/CreationDate': "D:20150310202949-07'00'", '/PTEX.Fullbanner': 'This is pdfTeX, Version 3.14159265-2.6-1.40.15 (TeX Live 2014/MacPorts 2014_6) kpathsea version 6.2.0', '/Producer': 'pdfTeX-1.40.15', '/Keywords': '', '/Trapped': '/False', '/Author': '', '/Subject': ''}

  • getNumPages():这个会pdf文件中的页数。
  • getPage(pageNumber):会得到pdf文件中对应的pageNumber页数的页面对象,返回值为PageObject实例。在得到PageObject实例之后就可以将其加添、插入等操作。
  • getPageNumber(page):与上面的方法对立,可以传入PageObject实例,然后得到该实例是pdf文件中第几页的。
  • getOutlines(node=None, outlines=None):检索文档中出现的文档大纲。
  • isEncrypted:记录该pdf是否加密。如果文件本身加密,即使在使用解密decrypt方法之后,还是会返回true。
  • numPages:pdf总共的页数,相当于访问getNumPages()的只读属性。

PdfFileWriter 。

该类支持对pdf文件进行写操作,通常是使用PdfFileReader读取一些pdf数据,然后使用该类进行一些操作。

创建该类的实例时不需要参数。

其主要的方法有:

  • addAttachment(fname, fdata):向pdf添加文件。
  • addBlankPage(width=None, height=None):给pdf添加一个空白页到最后,如果没有指定大小就使用当前Weiter中pdf最后一页的大小。
  • addPage(page):添加page到pdf中,通常这个page是由上面的Reader获取的。
  • appendPagesFromReader(reader, after_page_append=None):将reader中的数据拷贝到当前的Writer实例中,并且如果指定after_page_append的话,最后还有回掉该函数并且将writer中的数据传入其中。
  • encrypt(user_pwd, owner_pwd=None, use_128bit=True):将pdf进行加密,其中官方说userpwd是允许用户使用一些限制的权限打开pdf文件,也就是使用该密码的话可能会有一些限制,但是本人并没有在文档中找到设置权限的内容。而ownerpwd则是允许用户无限制的使用。第三个参数是是否使用128位加密。
  • getNumPages():得到pdf页数。
  • getPage(pageNumber):得到对应页数的Page,是一个PageObject对象,可以使用上面的addPage方法将page进行添加。
  • insertPage(page, index=0):将page添加到pdf中,index指定的是被插入的位置。
  • write(stream):将该Writer中的内容写入到文件中。

PdfFileMerger。

该类用来合并pdf文件,该类的构造方法有一个参数:PdfFileMerger(strict=True),注意这里的参数后面会介绍:

常用方法:

  • addBookmark(title, pagenum, parent=None):给pdf添加一个书签,title是书签的标题,pagenum是该书签指向的页面。
  • append(fileobj, bookmark=None, pages=None, import_bookmarks=True):将指定的fileobj文件添加到文件的末尾,bookmark是赎前,pages可以使用(start, stop[, step])或者一个 Page Range来设定将fileobj中的指定范围的页面进行添加。
  • merge(position, fileobj, bookmark=None, pages=None, import_bookmarks=True):与append方法类似,不过可以使用position参数指定添加的位置。
  • write(fileobj):将数据写入到文件中。

使用的时候可以创建一个PdfFileMerger实例,然后使用append或者merge将想要融合的pdf文件依次添加进去,最后使用write保存即可。

def merge_pdf():
  # 创建一个用来合并文件的实例
  pdf_merger = PdfFileMerger()

  # 首先添加一个Week1_1.pdf文件
  pdf_merger.append('Week1_1.pdf')
  # 然后在第0页后面添加ex1.pdf文件
  pdf_merger.merge(0, 'ex1.pdf')
  # 添加书签
  pdf_merger.addBookmark('这是一个书签', 1)
  # 将其写入到文件中
  pdf_merger.write('merge_pdf.pdf')

下面看一下PdfFileMerger(strict=True)中的这个参数:

官方对这个参数的解释:

strict (bool) ? Determines whether user should be warned of all problems and also causes some correctable problems to be fatal. Defaults to True.

确定是否应该警告用户所有问题,并且还会导致一些可纠正的问题。

刚开始感觉这个参数就是用来是否警告用户一些错误的,直接使用默认即可,但是当本人尝试合并带中文的pdf时,出现了如下错误:

Traceback (most recent call last):
 File "I:\python3.5\lib\site-packages\PyPDF2\generic.py", line 484, in readFromStream
  return NameObject(name.decode('utf-8'))
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc8 in position 10: invalid continuation byte

During handling of the above exception, another exception occurred:

PyPDF2.utils.PdfReadError: Illegal character in Name Object

在源码包中使用utf解码的时候出错了,尝试修改此处源码,让其使用gbk,但是还出现了其他的错误。最后发现当把构造函数中的strict设置为False时,控制台会打印下面的错误:

PdfReadWarning: Illegal character in Name Object [generic.py:489]

但是两个文件成功的合并了,并且大概看了下合并后的文件有时好又是坏,同样的代码运行多次,有时候能够正常处理中文,但有时候中文乱码。

除了列出的方法还有一些其他的方法,比如添加书签、添加链接等等,可以参考官方文档。

对pdf进行合并、分割、加密。

整合出来了加密、解密、合并、根据页数进行分割、根据份数进行分割的样例:

使用注意:如果时中文文件,运行结果可能会出现乱码,但是多运行几次,中间有正常显示中文的问题。具体原因还不清楚,但就是这么玄学。。。

代码传送门

# @Time  : 2018/3/26 23:48
# @Author : Leafage
# @File  : handlePDF.py
# @Software: PyCharm
# @Describe: 对pdf文件执行合并、分割、加密操作。
from PyPDF2 import PdfFileReader, PdfFileMerger, PdfFileWriter
def get_reader(filename, password):
  try:
    old_file = open(filename, 'rb')
  except IOError as err:
    print('文件打开失败!' + str(err))
    return None
  # 创建读实例
  pdf_reader = PdfFileReader(old_file, strict=False)
  # 解密操作
  if pdf_reader.isEncrypted:
    if password is None:
      print('%s文件被加密,需要密码!' % filename)
      return None
    else:
      if pdf_reader.decrypt(password) != 1:
        print('%s密码不正确!' % filename)
        return None
  if old_file in locals():
    old_file.close()
  return pdf_reader
def encrypt_pdf(filename, new_password, old_password=None, encrypted_filename=None):
  """
  对filename所对应的文件进行加密,并生成一个新的文件
  :param filename: 文件对应的路径
  :param new_password: 对文件加密使用的密码
  :param old_password: 如果旧文件进行了加密,需要密码
  :param encrypted_filename: 加密之后的文件名,省却时使用filename_encrypted;
  :return:
  """
  # 创建一个Reader实例
  pdf_reader = get_reader(filename, old_password)
  if pdf_reader is None:
    return
  # 创建一个写操作的实例
  pdf_writer = PdfFileWriter()
  # 从之前Reader中将数据写入到Writer中
  pdf_writer.appendPagesFromReader(pdf_reader)
  # 重新使用新密码加密
  pdf_writer.encrypt(new_password)
  if encrypted_filename is None:
    # 使用旧文件名 + encrypted 作为新的文件名
    encrypted_filename = "".join(filename.split('.')[:-1]) + '_' + 'encrypted' + '.pdf'
  pdf_writer.write(open(encrypted_filename, 'wb'))
def decrypt_pdf(filename, password, decrypted_filename=None):
  """
  将加密的文件及逆行解密,并生成一个无需密码pdf文件
  :param filename: 原先加密的pdf文件
  :param password: 对应的密码
  :param decrypted_filename: 解密之后的文件名
  :return:
  """
  # 生成一个Reader和Writer
  pdf_reader = get_reader(filename, password)
  if pdf_reader is None:
    return
  if not pdf_reader.isEncrypted:
    print('文件没有被加密,无需操作!')
    return
  pdf_writer = PdfFileWriter()
  pdf_writer.appendPagesFromReader(pdf_reader)
  if decrypted_filename is None:
    decrypted_filename = "".join(filename.split('.')[:-1]) + '_' + 'decrypted' + '.pdf'
  # 写入新文件
  pdf_writer.write(open(decrypted_filename, 'wb'))
def split_by_pages(filename, pages, password=None):
  """
  将文件按照页数进行平均分割
  :param filename: 所要分割的文件名
  :param pages: 分割之后每个文件对应的页数
  :param password: 如果文件加密,需要进行解密操作
  :return:
  """
  # 得到Reader
  pdf_reader = get_reader(filename, password)
  if pdf_reader is None:
    return
  # 得到总的页数
  pages_nums = pdf_reader.numPages
  if pages <= 1:
    print('每份文件必须大于1页!')
    return
  # 得到切分之后每个pdf文件的页数
  pdf_num = pages_nums // pages + 1 if pages_nums % pages else int(pages_nums / pages)
  print('pdf文件被分为%d份,每份有%d页!' % (pdf_num, pages))
  # 依次生成pdf文件
  for cur_pdf_num in range(1, pdf_num + 1):
    # 创建一个新的写实例
    pdf_writer = PdfFileWriter()
    # 生成对应的文件名称
    split_pdf_name = "".join(filename)[:-1] + '_' + str(cur_pdf_num) + '.pdf'
    # 计算出当前开始的位置
    start = pages * (cur_pdf_num - 1)
    # 计算出结束的位置,如果是最后一份就直接返回最后的页数,否则用每份页数*已经分好的文件数
    end = pages * cur_pdf_num if cur_pdf_num != pdf_num else pages_nums
    # print(str(start) + ',' + str(end))
    # 依次读取对应的页数
    for i in range(start, end):
      pdf_writer.addPage(pdf_reader.getPage(i))
    # 写入文件
    pdf_writer.write(open(split_pdf_name, 'wb'))
def split_by_num(filename, nums, password=None):
  """
  将pdf文件分为nums份
  :param filename: 文件名
  :param nums: 要分成的份数
  :param password: 如果需要解密,输入密码
  :return:
  """
  pdf_reader = get_reader(filename, password)
  if not pdf_reader:
    return
  if nums < 2:
    print('份数不能小于2!')
    return
  # 得到pdf的总页数
  pages = pdf_reader.numPages
  if pages < nums:
    print('份数不应该大于pdf总页数!')
    return
  # 计算每份应该有多少页
  each_pdf = pages // nums
  print('pdf共有%d页,分为%d份,每份有%d页!' % (pages, nums, each_pdf))
  for num in range(1, nums + 1):
    pdf_writer = PdfFileWriter()
    # 生成对应的文件名称
    split_pdf_name = "".join(filename)[:-1] + '_' + str(num) + '.pdf'
    # 计算出当前开始的位置
    start = each_pdf * (num - 1)
    # 计算出结束的位置,如果是最后一份就直接返回最后的页数,否则用每份页数*已经分好的文件数
    end = each_pdf * num if num != nums else pages
    print(str(start) + ',' + str(end))
    for i in range(start, end):
      pdf_writer.addPage(pdf_reader.getPage(i))
    pdf_writer.write(open(split_pdf_name, 'wb'))
def merger_pdf(filenames, merged_name, passwords=None):
  """
  传进来一个文件列表,将其依次融合起来
  :param filenames: 文件列表
  :param passwords: 对应的密码列表
  :return:
  """
  # 计算共有多少文件
  filenums = len(filenames)
  # 注意需要使用False 参数
  pdf_merger = PdfFileMerger(False)
  for i in range(filenums):
    # 得到密码
    if passwords is None:
      password = None
    else:
      password = passwords[i]
    pdf_reader = get_reader(filenames[i], password)
    if not pdf_reader:
      return
    # append默认添加到最后
    pdf_merger.append(pdf_reader)
  pdf_merger.write(open(merged_name, 'wb'))
def insert_pdf(pdf1, pdf2, insert_num, merged_name, password1=None, password2=None):
  """
  将pdf2全部文件插入到pdf1中第insert_num页
  :param pdf1: pdf1文件名称
  :param pdf2: pdf2文件名称
  :param insert_num: 插入的页数
  :param merged_name: 融合后的文件名称
  :param password1: pdf1对应的密码
  :param password2: pdf2对应的密码
  :return:
  """
  pdf1_reader = get_reader(pdf1, password1)
  pdf2_reader = get_reader(pdf2, password2)
  # 如果有一个打不开就返回
  if not pdf1_reader or not pdf2_reader:
    return
  # 得到pdf1的总页数
  pdf1_pages = pdf1_reader.numPages
  if insert_num < 0 or insert_num > pdf1_pages:
    print('插入位置异常,想要插入的页数为:%d,pdf1文件共有:%d页!' % (insert_num, pdf1_pages))
    return
  # 注意需要使用False参数,可能会出现中文乱码的情况
  m_pdf = PdfFileMerger(False)
  m_pdf.append(pdf1)
  m_pdf.merge(insert_num, pdf2)
  m_pdf.write(open(merged_name, 'wb'))
if __name__ == '__main__':
  # encrypt_pdf('ex1.pdf', 'leafage')
  # decrypt_pdf('ex1123_encrypted.pdf', 'leafage')
  # split_by_pages('ex1.pdf', 5)
  split_by_num('ex2.pdf', 3)
  # merger_pdf(['ex1.pdf', 'ex2.pdf'], 'merger.pdf')
  # insert_pdf('ex1.pdf', 'ex2.pdf', 10, 'pdf12.pdf')

总结

以上所述是小编给大家介绍的Python中使用pypdf2合并、分割、加密pdf文件的代码详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Python 相关文章推荐
python根据经纬度计算距离示例
Feb 16 Python
CentOS 6.X系统下升级Python2.6到Python2.7 的方法
Oct 12 Python
对numpy中布尔型数组的处理方法详解
Apr 17 Python
python3 打开外部程序及关闭的示例
Nov 06 Python
Python常见数据结构之栈与队列用法示例
Jan 14 Python
python占位符输入方式实例
May 27 Python
用Python画小女孩放风筝的示例
Nov 23 Python
Python tkinter实现图片标注功能(完整代码)
Dec 08 Python
Python操作注册表详细步骤介绍
Feb 05 Python
python 非线性规划方式(scipy.optimize.minimize)
Feb 11 Python
Python %r和%s区别代码实例解析
Apr 03 Python
python pyecharts 实现一个文件绘制多张图
May 13 Python
python+selenium实现简历自动刷新的示例代码
May 20 #Python
图文详解python安装Scrapy框架步骤
May 20 #Python
Python配置虚拟环境图文步骤
May 20 #Python
Python检测数据类型的方法总结
May 20 #Python
Python中的引用知识点总结
May 20 #Python
Python函数和模块的使用总结
May 20 #Python
详解Python的循环结构知识点
May 20 #Python
You might like
简单易用的计数器(数据库)
2006/10/09 PHP
基于PHP的cURL快速入门教程 (小偷采集程序)
2011/06/02 PHP
PHP @ at 记号的作用示例介绍
2014/10/10 PHP
thinkphp5 + ajax 使用formdata提交数据(包括文件上传) 后台返回json完整实例
2020/03/02 PHP
对联广告js flash激活
2006/10/19 Javascript
基于jQuery的的一个隔行变色,鼠标移动变色的小插件
2010/07/06 Javascript
js判断生效时间不得大于失效时间的思路及代码
2013/04/23 Javascript
js 获取、清空input type=&quot;file&quot;的值(示例代码)
2013/12/24 Javascript
基于jQuery实现下拉框
2014/11/24 Javascript
js给selected添加options的方法
2015/05/06 Javascript
在JavaScript中处理字符串之fontcolor()方法的使用
2015/06/08 Javascript
jQuery+PHP星级评分实现方法
2015/10/02 Javascript
javascript高级编程之函数表达式 递归和闭包函数
2015/11/29 Javascript
Javascript removeChild()删除节点及删除子节点的方法
2015/12/27 Javascript
jQuery实现指定区域外单击关闭指定层的方法【经典】
2016/06/22 Javascript
如何解决jQuery EasyUI 已打开Tab重新加载问题
2016/12/19 Javascript
微信小程序request出现400的问题解决办法
2017/05/23 Javascript
webpack 1.x升级过程中的踩坑总结大全
2017/08/09 Javascript
nodeJS微信分享
2017/12/20 NodeJs
Angular5.0 子组件通过service传递值给父组件的方法
2018/07/13 Javascript
解决微信小程序防止无法回到主页的问题
2018/09/28 Javascript
小程序hover-class点击态效果实现
2019/02/26 Javascript
详解JavaScript中的坐标和距离
2019/05/27 Javascript
vue登录页面cookie的使用及页面跳转代码
2019/07/10 Javascript
Layui选项卡制作历史浏览记录的方法
2019/09/28 Javascript
vue3.0实现插件封装
2020/12/14 Vue.js
[01:28:44]DOTA2-DPC中国联赛定级赛 RNG vs iG BO3第一场 1月10日
2021/03/11 DOTA
解决python3中解压zip文件是文件名乱码的问题
2018/03/22 Python
Tensorflow 实现将图像与标签数据转化为tfRecord文件
2020/02/17 Python
详解HTML5通讯录获取指定多个人的信息
2016/12/20 HTML / CSS
利用html5的websocket实现websocket聊天室
2013/12/12 HTML / CSS
Keds官方网站:购买帆布运动鞋和经典皮鞋
2016/11/12 全球购物
英国时尚饰品和发饰购物网站:Claire’s
2017/07/04 全球购物
应用心理学个人的求职信
2013/12/08 职场文书
感恩老师的演讲稿
2014/05/06 职场文书
表扬信范文
2015/05/04 职场文书