小编给大家分享一下Python中如何使用pypdf2合并、分割、加密pdf文件,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!
python可以做什么
Python是一种编程语言,内置了许多有效的工具,Python几乎无所不能,该语言通俗易懂、容易入门、功能强大,在许多领域中都有广泛的应用,例如最热门的大数据分析,人工智能,Web开发等。
朋友需要对一个pdf文件进行分割,在网上查了查发现这个pypdf2可以完成这些操作,所以就研究了下这个库,并做一些记录。首先pypdf2是python3版本的,在之前的2版本有一个对应pypdf库。
可以使用pip直接安装:
pip install pypdf2
官方文档: pythonhosted.org/PyPDF2/
里面主要有这几个类:
PdfFileReader 。
该类主要提供了对pdf文件的读操作,其构造方法为:
PdfFileReaderstream, strict=True, warndest=None, overwriteWarnings=True)
第一个参数可以传入一个文件流,或者一个文件路径。后面三个参数都是用来设置警告的处理方式,直接使用默认的即可。
得到实例之后,就可以对pdf进行一些操作了。主要的有以下几个操作:
-
decryptpassword):如果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文件中的页数。
-
getPagepageNumber):会得到pdf文件中对应的pageNumber页数的页面对象,返回值为PageObject实例。在得到PageObject实例之后就可以将其加添、插入等操作。
-
getPageNumberpage):与上面的方法对立,可以传入PageObject实例,然后得到该实例是pdf文件中第几页的。
-
getOutlinesnode=None, outlines=None):检索文档中出现的文档大纲。
-
isEncrypted:记录该pdf是否加密。如果文件本身加密,即使在使用解密decrypt方法之后,还是会返回true。
-
numPages:pdf总共的页数,相当于访问getNumPages)的只读属性。
PdfFileWriter 。
该类支持对pdf文件进行写操作,通常是使用PdfFileReader读取一些pdf数据,然后使用该类进行一些操作。
创建该类的实例时不需要参数。
其主要的方法有:
-
addAttachmentfname, fdata):向pdf添加文件。
-
addBlankPagewidth=None, height=None):给pdf添加一个空白页到最后,如果没有指定大小就使用当前Weiter中pdf最后一页的大小。
-
addPagepage):添加page到pdf中,通常这个page是由上面的Reader获取的。
-
appendPagesFromReaderreader, after_page_append=None):将reader中的数据拷贝到当前的Writer实例中,并且如果指定after_page_append的话,最后还有回掉该函数并且将writer中的数据传入其中。
-
encryptuser_pwd, owner_pwd=None, use_128bit=True):将pdf进行加密,其中官方说userpwd是允许用户使用一些限制的权限打开pdf文件,也就是使用该密码的话可能会有一些限制,但是本人并没有在文档中找到设置权限的内容。而ownerpwd则是允许用户无限制的使用。第三个参数是是否使用128位加密。
-
getNumPages):得到pdf页数。
-
getPagepageNumber):得到对应页数的Page,是一个PageObject对象,可以使用上面的addPage方法将page进行添加。
-
insertPagepage, index=0):将page添加到pdf中,index指定的是被插入的位置。
-
writestream):将该Writer中的内容写入到文件中。
PdfFileMerger。
该类用来合并pdf文件,该类的构造方法有一个参数:PdfFileMergerstrict=True),注意这里的参数后面会介绍:
常用方法:
-
addBookmarktitle, pagenum, parent=None):给pdf添加一个书签,title是书签的标题,pagenum是该书签指向的页面。
-
appendfileobj, bookmark=None, pages=None, import_bookmarks=True):将指定的fileobj文件添加到文件的末尾,bookmark是赎前,pages可以使用start, stop[, step])或者一个 Page Range来设定将fileobj中的指定范围的页面进行添加。
-
mergeposition, fileobj, bookmark=None, pages=None, import_bookmarks=True):与append方法类似,不过可以使用position参数指定添加的位置。
-
writefileobj):将数据写入到文件中。
使用的时候可以创建一个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.merge0, 'ex1.pdf') # 添加书签 pdf_merger.addBookmark'这是一个书签', 1) # 将其写入到文件中 pdf_merger.write'merge_pdf.pdf')
下面看一下PdfFileMergerstrict=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 NameObjectname.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_readerfilename, password): try: old_file = openfilename, 'rb') except IOError as err: print'文件打开失败!' + strerr)) return None # 创建读实例 pdf_reader = PdfFileReaderold_file, strict=False) # 解密操作 if pdf_reader.isEncrypted: if password is None: print'%s文件被加密,需要密码!' % filename) return None else: if pdf_reader.decryptpassword) != 1: print'%s密码不正确!' % filename) return None if old_file in locals): old_file.close) return pdf_reader def encrypt_pdffilename, 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_readerfilename, old_password) if pdf_reader is None: return # 创建一个写操作的实例 pdf_writer = PdfFileWriter) # 从之前Reader中将数据写入到Writer中 pdf_writer.appendPagesFromReaderpdf_reader) # 重新使用新密码加密 pdf_writer.encryptnew_password) if encrypted_filename is None: # 使用旧文件名 + encrypted 作为新的文件名 encrypted_filename = "".joinfilename.split'.')[:-1]) + '_' + 'encrypted' + '.pdf' pdf_writer.writeopenencrypted_filename, 'wb')) def decrypt_pdffilename, password, decrypted_filename=None): """ 将加密的文件及逆行解密,并生成一个无需密码pdf文件 :param filename: 原先加密的pdf文件 :param password: 对应的密码 :param decrypted_filename: 解密之后的文件名 :return: """ # 生成一个Reader和Writer pdf_reader = get_readerfilename, password) if pdf_reader is None: return if not pdf_reader.isEncrypted: print'文件没有被加密,无需操作!') return pdf_writer = PdfFileWriter) pdf_writer.appendPagesFromReaderpdf_reader) if decrypted_filename is None: decrypted_filename = "".joinfilename.split'.')[:-1]) + '_' + 'decrypted' + '.pdf' # 写入新文件 pdf_writer.writeopendecrypted_filename, 'wb')) def split_by_pagesfilename, pages, password=None): """ 将文件按照页数进行平均分割 :param filename: 所要分割的文件名 :param pages: 分割之后每个文件对应的页数 :param password: 如果文件加密,需要进行解密操作 :return: """ # 得到Reader pdf_reader = get_readerfilename, 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 intpages_nums / pages) print'pdf文件被分为%d份,每份有%d页!' % pdf_num, pages)) # 依次生成pdf文件 for cur_pdf_num in range1, pdf_num + 1): # 创建一个新的写实例 pdf_writer = PdfFileWriter) # 生成对应的文件名称 split_pdf_name = "".joinfilename)[:-1] + '_' + strcur_pdf_num) + '.pdf' # 计算出当前开始的位置 start = pages * cur_pdf_num - 1) # 计算出结束的位置,如果是最后一份就直接返回最后的页数,否则用每份页数*已经分好的文件数 end = pages * cur_pdf_num if cur_pdf_num != pdf_num else pages_nums # printstrstart) + ',' + strend)) # 依次读取对应的页数 for i in rangestart, end): pdf_writer.addPagepdf_reader.getPagei)) # 写入文件 pdf_writer.writeopensplit_pdf_name, 'wb')) def split_by_numfilename, nums, password=None): """ 将pdf文件分为nums份 :param filename: 文件名 :param nums: 要分成的份数 :param password: 如果需要解密,输入密码 :return: """ pdf_reader = get_readerfilename, 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 range1, nums + 1): pdf_writer = PdfFileWriter) # 生成对应的文件名称 split_pdf_name = "".joinfilename)[:-1] + '_' + strnum) + '.pdf' # 计算出当前开始的位置 start = each_pdf * num - 1) # 计算出结束的位置,如果是最后一份就直接返回最后的页数,否则用每份页数*已经分好的文件数 end = each_pdf * num if num != nums else pages printstrstart) + ',' + strend)) for i in rangestart, end): pdf_writer.addPagepdf_reader.getPagei)) pdf_writer.writeopensplit_pdf_name, 'wb')) def merger_pdffilenames, merged_name, passwords=None): """ 传进来一个文件列表,将其依次融合起来 :param filenames: 文件列表 :param passwords: 对应的密码列表 :return: """ # 计算共有多少文件 filenums = lenfilenames) # 注意需要使用False 参数 pdf_merger = PdfFileMergerFalse) for i in rangefilenums): # 得到密码 if passwords is None: password = None else: password = passwords[i] pdf_reader = get_readerfilenames[i], password) if not pdf_reader: return # append默认添加到最后 pdf_merger.appendpdf_reader) pdf_merger.writeopenmerged_name, 'wb')) def insert_pdfpdf1, 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_readerpdf1, password1) pdf2_reader = get_readerpdf2, 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 = PdfFileMergerFalse) m_pdf.appendpdf1) m_pdf.mergeinsert_num, pdf2) m_pdf.writeopenmerged_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')