你不懂得安顿本人的人生,会有很多人帮你安顿,他们须要你做的事。

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的外围两个类是PdfFileReaderPdfFileWriter,实现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个常见思路参考:

  1. 找到特征词后替换,适宜英文文档,但不适用于中文等CJK字符。
  2. 把PDF页转成图片后,用图像算法去水印,但这样会毁坏文件原信息结构。
  3. 依据水印大小地位特色,找到所有元素后删除。这是更举荐的形式。

第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.sixpdfplumber提供了更便捷的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)

下面用到了PILPillow)的性能解决图片。

提取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名收费(弹出付费信息能够疏忽)。

正在整顿代码和演示数据,群内公布交换。