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中的with语句与上下文管理器学习总结
Jun 28 Python
python数字图像处理之高级形态学处理
Apr 27 Python
Python基于Floyd算法求解最短路径距离问题实例详解
May 16 Python
python write无法写入文件的解决方法
Jan 23 Python
详解Python正则表达式re模块
Mar 19 Python
python实现文件的备份流程详解
Jun 18 Python
浅析Python与Mongodb数据库之间的操作方法
Jul 01 Python
Python中使用gflags实例及原理解析
Dec 13 Python
利用keras加载训练好的.H5文件,并实现预测图片
Jan 24 Python
在 Golang 中实现 Cache::remember 方法详解
Mar 30 Python
聊聊Python String型列表求最值的问题
Jan 18 Python
Python制作表白爱心合集
Jan 22 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
用PHP为SHOPEX增加日志功能代码
2010/07/02 PHP
深入php socket的讲解与实例分析
2013/06/13 PHP
php去掉文件前几行的方法
2015/07/29 PHP
百度地图API使用方法详解
2015/08/25 PHP
PHP实现递归无限级分类
2015/10/22 PHP
基于thinkPHP框架实现留言板的方法
2016/10/17 PHP
使用javascript访问XML数据的实例
2006/12/27 Javascript
IE php关于强制下载文件的代码
2008/08/23 Javascript
在模板页面的js使用办法
2010/04/01 Javascript
jquery插件之easing使用
2010/08/19 Javascript
js选择并转移导航菜单示例代码
2014/08/19 Javascript
JavaScript中setFullYear()方法的使用详解
2015/06/11 Javascript
谈谈对offsetleft兼容性的理解
2015/11/11 Javascript
JS原型、原型链深入理解
2016/02/27 Javascript
JavaScrpt判断一个数是否是质数的实例代码
2017/06/11 Javascript
jQuery Json数据格式排版高亮插件json-viewer.js使用方法详解
2017/06/12 jQuery
详解如何让Express支持async/await
2017/10/09 Javascript
vue  自定义组件实现通讯录功能
2018/09/30 Javascript
手把手教你写一个微信小程序(推荐)
2018/10/17 Javascript
如何解决.vue文件url引用文件的问题
2019/01/18 Javascript
原生JS forEach()和map()遍历的区别、兼容写法及jQuery $.each、$.map遍历操作
2019/02/27 jQuery
解决vue单页面应用中动态修改title问题
2019/06/09 Javascript
vue实现标签云效果的方法详解
2019/08/28 Javascript
javascript实现点亮灯泡特效示例
2019/10/15 Javascript
JS面向对象编程——ES6 中class的继承用法详解
2020/03/03 Javascript
python中的sort方法使用详解
2014/07/25 Python
python中使用xlrd、xlwt操作excel表格详解
2015/01/29 Python
Python中类型检查的详细介绍
2017/02/13 Python
Anaconda2下实现Python2.7和Python3.5的共存方法
2018/06/11 Python
Python简单基础小程序的实例代码
2019/04/28 Python
python openpyxl使用方法详解
2019/07/18 Python
Python 静态方法和类方法实例分析
2019/11/21 Python
HTML5新增加的功能详解
2016/09/05 HTML / CSS
外贸公司实习自我鉴定
2013/09/24 职场文书
天地会口号
2014/06/17 职场文书
Mysql使用全文索引(FullText index)的实例代码
2022/04/03 MySQL