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绘图库Matplotlib的安装
Jul 03 Python
Python写入数据到MP3文件中的方法
Jul 10 Python
简介二分查找算法与相关的Python实现示例
Aug 26 Python
Python使用Selenium+BeautifulSoup爬取淘宝搜索页
Feb 24 Python
对python中的for循环和range内置函数详解
Apr 17 Python
numpy 进行数组拼接,分别在行和列上合并的实例
May 08 Python
pandas DataFrame行或列的删除方法的实现示例
Aug 02 Python
python自动结束mysql慢查询会话的实例代码
Oct 27 Python
Keras中 ImageDataGenerator函数的参数用法
Jul 03 Python
python下载的库包存放路径
Jul 27 Python
使用Pytorch实现two-head(多输出)模型的操作
May 28 Python
浅谈Python从全局与局部变量到装饰器的相关知识
Jun 21 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 论坛采集程序 模拟登陆,抓取页面 实现代码
2009/07/09 PHP
PHP 文章中的远程图片采集到本地的代码
2009/07/30 PHP
php学习笔记之 函数声明
2011/06/09 PHP
深入解析PHP中逗号与点号的区别
2013/08/05 PHP
PHP中实现生成静态文件的方法缓解服务器压力
2014/01/07 PHP
使用xampp搭建运行php虚拟主机的详细步骤
2015/10/21 PHP
PHP常用日期加减计算方法实例小结
2018/07/31 PHP
jQuery对象和DOM对象的相互转化实现代码
2010/03/02 Javascript
jQuery中对未来的元素绑定事件用bind、live or on
2014/04/17 Javascript
js操作iframe父子窗体示例
2014/05/22 Javascript
基于jquery实现图片上传本地预览功能
2016/01/08 Javascript
详解AngularJS如何实现跨域请求
2016/08/22 Javascript
防止页面url缓存中ajax中post请求的处理方法
2017/10/10 Javascript
jQuery实现简单复制json对象和json对象集合操作示例
2018/07/09 jQuery
javascript实现商品图片放大镜
2019/11/28 Javascript
Vue.js中Line第三方登录api的实现代码
2020/06/29 Javascript
JavaScript实现浏览器网页自动滚动并点击的示例代码
2020/12/05 Javascript
[01:01:43]EG vs VP 2018国际邀请赛淘汰赛BO3 第二场 8.24
2018/08/25 DOTA
[07:01]DOTA2-DPC中国联赛正赛 Aster vs Magma 3月5日 赛后选手采访
2021/03/11 DOTA
python实现计算资源图标crc值的方法
2014/10/05 Python
在ironpython中利用装饰器执行SQL操作的例子
2015/05/02 Python
Python 结巴分词实现关键词抽取分析
2017/10/21 Python
python smtplib发送带附件邮件小程序
2018/05/22 Python
python实现年会抽奖程序
2019/01/22 Python
Python GUI编程 文本弹窗的实例
2019/06/11 Python
Python常用数据类型之间的转换总结
2019/09/06 Python
python+selenium+chromedriver实现爬虫示例代码
2020/04/10 Python
HTML5 Canvas锯齿图代码实例
2014/04/10 HTML / CSS
应届生自我鉴定
2013/12/11 职场文书
领导干部群众路线教育实践活动剖析材料
2014/10/10 职场文书
审计局2014法制宣传日活动总结
2014/11/01 职场文书
旅游投诉信范文
2015/07/02 职场文书
2015年国庆节新闻稿
2015/07/18 职场文书
python实现图片九宫格分割的示例
2021/04/25 Python
MySQL系列之五 视图、存储函数、存储过程、触发器
2021/07/02 MySQL