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下singleton模式的实现方法
Jul 16 Python
Python语言实现获取主机名根据端口杀死进程
Mar 31 Python
Python中字符串的常见操作技巧总结
Jul 28 Python
Python连接SQLServer2000的方法详解
Apr 19 Python
Python cookbook(数据结构与算法)根据字段将记录分组操作示例
Mar 19 Python
numpy中实现ndarray数组返回符合特定条件的索引方法
Apr 17 Python
window环境pip切换国内源(pip安装异常缓慢的问题)
Dec 31 Python
python线程join方法原理解析
Feb 11 Python
Python使用os.listdir和os.walk获取文件路径
May 21 Python
Django中和时区相关的安全问题详解
Oct 12 Python
Python如何使用ConfigParser读取配置文件
Nov 12 Python
python办公自动化之excel的操作
May 23 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性能优化准备篇图解PEAR安装
2011/12/05 PHP
利用php实现禁用IE和火狐的缓存问题
2012/12/03 PHP
深入PHP内存相关的功能特性详解
2013/06/08 PHP
PHP资源管理框架Assetic简介
2014/06/12 PHP
万能的php分页类
2017/07/06 PHP
推荐17个优美新鲜的jQuery的工具提示插件
2012/09/14 Javascript
Javascript对象属性方法汇总
2013/11/21 Javascript
jQuery ajax应用总结
2016/06/02 Javascript
Bootstrap组件之下拉菜单,多级菜单及按钮布局方法实例
2017/05/25 Javascript
Nodejs实现多房间简易聊天室功能
2017/06/20 NodeJs
vue 请求后台数据的实例代码
2017/06/22 Javascript
JS中offset和匀速动画详解
2018/02/06 Javascript
浅谈js中的bind
2019/03/18 Javascript
python 打印直角三角形,等边三角形,菱形,正方形的代码
2017/11/21 Python
详解Django之admin组件的使用和源码剖析
2018/05/04 Python
python求最大值最小值方法总结
2019/06/25 Python
Django中celery执行任务结果的保存方法
2019/07/12 Python
Pytorch 实现冻结指定卷积层的参数
2020/01/06 Python
python3 logging日志封装实例
2020/04/08 Python
python import 上级目录的导入
2020/11/03 Python
canvas探照灯效果的示例代码
2018/11/30 HTML / CSS
Mixbook加拿大:照片书,照片卡,剪贴簿,年历和日历
2017/02/21 全球购物
德国童装购物网站:NICKI´S.com
2018/04/20 全球购物
测绘工程个人的自我评价
2013/11/10 职场文书
优秀团支部事迹材料
2014/02/08 职场文书
2014年健康教育实施方案
2014/02/17 职场文书
农村改厕实施方案
2014/03/22 职场文书
单位承诺书格式
2014/05/21 职场文书
计算机专业求职信
2014/06/02 职场文书
党员民主评议总结
2014/10/20 职场文书
乡镇防汛工作汇报
2014/10/28 职场文书
研究生导师评语
2014/12/31 职场文书
事业单位财务人员岗位职责
2015/04/14 职场文书
世界各国短波电台对东亚播送时间频率表(SW)
2021/06/28 无线电
Python OpenCV超详细讲解基本功能
2022/04/02 Python