你不懂得安顿本人的人生,会有很多人帮你安顿,他们须要你做的事。
PDF文件咱们常常用,尤其是这两个场景:
- 下载参考资料,如各类报告、文档
- 分享只读材料,不便流传同时保留源文件
场景和模块
所以,对于PDF文件,常见的需要也就是两类:
- 解决文件自身,属于文件页面级操作,如合并/分拆PDF页面、加/解密、加/去水印;
- 解决文件内容,属于内容级操作,如提取文字、表格数据、图表等。
目前Python用于解决PDF的模块,次要有3个:
PyPDF2
:模块成熟,最初一次更新在2年前,适宜页面级操作,文字提取成果较差。PDFMiner
:善于文字抽取,目前主分支已进行保护,取而代之的是pdfminer.six
pdfplumber
:基于pdfminer.six
的文本内容抽取工具,应用门槛更低,如反对表格提取。
实战中,能够依据需要的类型抉择模块。如果是页面级的操作,就用PyPDF2
,如果须要内容抽取,优先应用pdfplumber
。
对应的模块装置:
pip install pypdf2
pip install pdfminer.six
pip install pdfplumber
上面按应用场景演示3个模块的应用。
PyPDF2
PyPDF2
的次要能力在页面级操作,比方:
- 获取PDF文档根本信息
- PDF宰割及合并
- PDF的旋转及排序
- PDF加水印及去水印
- PDF加密及解密
PyPDF2
的外围两个类是PdfFileReader
和PdfFileWriter
,实现PDF文件的读写操作。
获取PDF文档根本信息
import pathlibfrom PyPDF2 import PdfFileReaderpath = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path = path.joinpath('2020-新冠肺炎疫情对中国连锁餐饮行业的影响调研报告-中国连锁经营协会.pdf')with open(f_path, 'rb') as f: pdf = PdfFileReader(f) info = pdf.getDocumentInfo() cnt_page = pdf.getNumPages() is_encrypt = pdf.getIsEncrypted()print(f'''作者: {info.author}创建者: {info.creator}制作者: {info.producer}主题: {info.subject}题目: {info.title}总页数: {cnt_page}是否加密: {is_encrypt}''')
PDF宰割及合并
import pathlibfrom PyPDF2 import PdfFileReader, PdfFileWriterpath = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path = path.joinpath('2020-新冠肺炎疫情对中国连锁餐饮行业的影响调研报告-中国连锁经营协会.pdf')out_path = path.joinpath('002pdf_split_merge.pdf')out_path_1 = path.joinpath('002pdf_split_half_front.pdf')out_path_2 = path.joinpath('002pdf_split_half_back.pdf')# 把文件分为两半with open(f_path, 'rb') as f, open(out_path_1, 'wb') as f_out1, open(out_path_2, 'wb') as f_out2: pdf = PdfFileReader(f) pdf_out1 = PdfFileWriter() pdf_out2 = PdfFileWriter() cnt_pages = pdf.getNumPages() print(f'共 {cnt_pages} 页') for i in range(cnt_pages): if i <= cnt_pages //2: pdf_out1.addPage(pdf.getPage(i)) else: pdf_out2.addPage(pdf.getPage(i)) pdf_out1.write(f_out1) pdf_out2.write(f_out2)# 再把后半个文件与前半个文件合并,后半个文件在前with open(out_path, 'wb') as f_out: cnt_f, cnt_b = pdf_out1.getNumPages(), pdf_out2.getNumPages() pdf_out = PdfFileWriter() for i in range(cnt_b): pdf_out.addPage(pdf_out2.getPage(i)) for i in range(cnt_f): pdf_out.addPage(pdf_out1.getPage(i)) pdf_out.write(f_out)
PDF的旋转及排序
import pathlibfrom PyPDF2 import PdfFileReader, PdfFileWriterpath = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path = path.joinpath('2020-新冠肺炎疫情对中国连锁餐饮行业的影响调研报告-中国连锁经营协会.pdf')out_path = path.joinpath('002pdf_rotate.pdf')with open(f_path, 'rb') as f, open(out_path, 'wb') as f_out: pdf = PdfFileReader(f) pdf_out = PdfFileWriter() page = pdf.getPage(0).rotateClockwise(90) pdf_out.addPage(page) # 把第二页放到后面 pdf_out.addPage(pdf.getPage(2)) page = pdf.getPage(1).rotateCounterClockwise(90) pdf_out.addPage(page) pdf_out.write(f_out)
PDF加水印及去水印
加图片水印,其实就是在页面中减少一个通明背景的图片,通过页面的mergePage
办法即可实现。
import pathlibfrom PyPDF2 import PdfFileReader, PdfFileWriterpath = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path = path.joinpath('2020-新冠肺炎疫情对中国连锁餐饮行业的影响调研报告-中国连锁经营协会.pdf')wm_path = path.joinpath('watermark.pdf')en_path = path.joinpath('002pdf_with_watermark_en.pdf')out_path = path.joinpath('002pdf_with_watermark.pdf')with open(f_path, 'rb') as f, open(wm_path, 'rb') as f_wm, open(out_path, 'wb') as f_out: pdf = PdfFileReader(f) pdf_wm = PdfFileReader(f_wm) pdf_out = PdfFileWriter() wm_cn_page = pdf_wm.getPage(0) wm_en_page = pdf_wm.getPage(1) cnt_pages = pdf.getNumPages() for i in range(cnt_pages): page = pdf.getPage(i) page.mergePage(wm_cn_page) pdf_out.addPage(page) pdf_out.write(f_out)
去水印,就比较复杂,须要依据不同状况具体分析。因为水印可能是文字、图片或者各种组合,要害是辨认出特色。
去水印的3个常见思路参考:
- 找到特征词后替换,适宜英文文档,但不适用于中文等CJK字符。
- 把PDF页转成图片后,用图像算法去水印,但这样会毁坏文件原信息结构。
- 依据水印大小地位特色,找到所有元素后删除。这是更举荐的形式。
第3种形式成果最好,但如果碰到一些简单的文档水印,就十分考验急躁。
你得一个个辨认操作命令,一边替换一边查看成果,直到水印胜利去除。
但,未必剩下的所有页都能够用同样特色模式来打消,因为这份PDF可能通过多人加水印,曾经蕴含多种加水印形式。
所以,去水印并没有一种100%平安无效(不错删信息)且通用的办法。
加水印、去水印实质上是一种攻防策略。
比方一些工具推出去水印性能,一旦公开,加水印方就能辨认并避开它的去除办法。
最初,尊重版权,是每个人应有的态度。
除了学习外,正式应用时,应该恪守内容创作方的规定。
PDF加密解密
PDF里的明码,分为用户明码和所有者明码。
PyPDF2里提供了根本的加密性能,“防小人不防君子”。
如果关上PDF文件后,复制了新文件,那新文件就不受所有者明码的束缚,可被批改。
import pathlibfrom PyPDF2 import PdfFileReader, PdfFileWriterpath = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path = path.joinpath('2020-新冠肺炎疫情对中国连锁餐饮行业的影响调研报告-中国连锁经营协会.pdf')out_path_encrypt = path.joinpath('002pdf_encrypt.pdf')out_path_decrypt = path.joinpath('002pdf_decrypt.pdf')with open(f_path, 'rb') as f, open(out_path_encrypt, 'wb') as f_out: pdf = PdfFileReader(f) pdf_out = PdfFileWriter() cnt_pages = pdf.getNumPages() for i in range(cnt_pages): page = pdf.getPage(i) pdf_out.addPage(page) pdf_out.encrypt('123456', owner_pwd='654321') pdf_out.write(f_out)# 从新读取加密文件并生成解密文件with open(out_path_encrypt, 'rb') as f, open(out_path_decrypt, 'wb') as f_out: pdf = PdfFileReader(f) if not pdf.isEncrypted: print('文件未被加密') else: success = pdf.decrypt('123456') # if not success: pdf_out = PdfFileWriter() pdf_out.appendPagesFromReader(pdf) pdf_out.write(f_out)
pdfminer.six
PDFMiner的操作门槛比拟高,须要局部理解PDF的文档构造模型,适宜定制开发简单的内容解决工具。
平时间接用PDFMiner
比拟少,这里只演示根本的文档内容操作:
import pathlibfrom pdfminer.pdfparser import PDFParserfrom pdfminer.pdfdocument import PDFDocumentfrom pdfminer.pdfpage import PDFPagefrom pdfminer.pdfinterp import PDFResourceManagerfrom pdfminer.pdfinterp import PDFPageInterpreterfrom pdfminer.pdfdevice import PDFDevicefrom pdfminer.layout import LAParams, LTTextBox, LTFigure, LTImagefrom pdfminer.converter import PDFPageAggregatorpath = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path = path.joinpath('2020-新冠肺炎疫情对中国连锁餐饮行业的影响调研报告-中国连锁经营协会.pdf')with open(f_path, 'rb') as f: parser = PDFParser(f) doc = PDFDocument(parser) rsrcmgr = PDFResourceManager() laparams = LAParams() device = PDFPageAggregator(rsrcmgr, laparams=laparams) interpreter = PDFPageInterpreter(rsrcmgr, device) for page in PDFPage.create_pages(doc): interpreter.process_page(page) layout = device.get_result() for x in layout: # 获取文本对象 if isinstance(x, LTTextBox): print(x.get_text().strip()) # 获取图片对象 if isinstance(x,LTImage): print('这里获取到一张图片') # 获取 figure 对象 if isinstance(x,LTFigure): print('这里获取到一个 figure 对象')
尽管pdfminer
应用门槛较高,但遇到简单状况,最初还得用它。目前开源模块中,它对PDF的反对应该是最全的了。
上面这个pdfplumber
就是基于pdfminer.six
开发的模块,升高了应用门槛。
pdfplumber
相比pdfminer.six
,pdfplumber
提供了更便捷的PDF内容抽取接口。
日常工作中罕用的操作,比方:
- 提取PDF内容,保留到txt文件
- 提取PDF中的表格到Excel
- 提取PDF中的图片
- 提取PDF中的图表
提取PDF内容,保留到txt文件
import pathlibimport pdfplumberpath = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path = path.joinpath('2020-新冠肺炎疫情对中国连锁餐饮行业的影响调研报告-中国连锁经营协会.pdf')out_path = path.joinpath('002pdf_out.txt')with pdfplumber.open(f_path) as pdf, open(out_path ,'a') as txt: for page in pdf.pages: textdata = page.extract_text() txt.write(textdata)
提取PDF中的表格到Excel
import pathlibimport pdfplumberfrom openpyxl import Workbookpath = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path = path.joinpath('2020-新冠肺炎疫情对中国连锁餐饮行业的影响调研报告-中国连锁经营协会.pdf')out_path = path.joinpath('002pdf_excel.xlsx')wb = Workbook()sheet = wb.activewith pdfplumber.open(f_path) as pdf: for i in range(19, 22): page = pdf.pages[i] table = page.extract_table() for row in table: sheet.append(row)wb.save(out_path)
下面用到了openpyxl
的性能创立了一个Excel文件,前面会有独自文章介绍它。
提取PDF中的图片
import pathlibimport pdfplumberfrom PIL import Imagepath = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path = path.joinpath('2020-疫情影响下的中国社区趋势钻研-艾瑞.pdf')out_path = path.joinpath('002pdf_images.png')with pdfplumber.open(f_path) as pdf, open(out_path, 'wb') as fout: page = pdf.pages[10] # for img in page.images: im = page.to_image() im.save(out_path, format='PNG') imgs = page.images for i, img in enumerate(imgs): size = img['width'], img['height'] data = img['stream'].get_data() out_path = path.joinpath(f'002pdf_images_{i}.png') with open(out_path, 'wb') as fimg_out: fimg_out.write(data)
下面用到了PIL
(Pillow
)的性能解决图片。
提取PDF中的图表
图表与图像不同,指的是相似直方图、饼图之类的数据生成图。
import pathlibimport pdfplumberfrom PIL import Imagepath = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path = path.joinpath('2020-新冠肺炎疫情对中国连锁餐饮行业的影响调研报告-中国连锁经营协会.pdf')out_path = path.joinpath('002pdf_figures.png')with pdfplumber.open(f_path) as pdf, open(out_path, 'wb') as fout: page = pdf.pages[7] im = page.to_image() im.save(out_path, format='PNG') figures = page.figures for i, fig in enumerate(figures): size = fig['width'], fig['height'] crop = page.crop((fig['x0'], fig['top'], fig['x1'], fig['bottom'])) img_crop = crop.to_image() out_path = path.joinpath(f'002pdf_figures_{i}.png') img_crop.save(out_path, format='png') im.draw_rects(page.extract_words(), stroke='yellow') im.draw_rects(page.images, stroke='blue') im.draw_rects(page.figures)im # show in notebook
总结
本文介绍了PDF的常见应用场景,以及Python解决PDF的3个次要模块。
补充一点,PDF标准规范由Adobe公司主导。
平时咱们不须要参考标准,但如果遇到一些较简单的场景,尤其是模块没有间接反对,就只能硬着头皮翻阅文档了。文档是公开的,能够去搜索引擎搜寻关键词:pdf_reference_1-7.pdf。
最初,建个学习群,有趣味的能够退出,前100名收费(弹出付费信息能够疏忽)。
正在整顿代码和演示数据,群内公布交换。