如果你不知道如何安排自己的生活,会有很多人帮你安排他们需要你做的事情。我们经常使用PDF文件,尤其是这两个场景:下载参考资料,比如各种报告文档,共享只读资料方便传播,同时保留源文件场景和模块所以,对于PDF文件,有两个共同的需求:处理文件本身属于文件页面级操作,如合并/拆分PDF页面、添加/解密、添加/去除水印;处理文件内容属于内容级操作,如提取文本、表格数据、图表等。目前Python处理PDF主要有3个模块:PyPDF2:模块成熟,上次更新2年前。适用于页面级操作,文本提取效果较差。PDFMiner:擅长文本提取,目前主分支已经停止维护,取而代之的是pdfminer.sixpdfplumber:基于pdfminer.six的文本内容提取工具,使用门槛较低,比如支持表格提取。在实战中,可以根据需求的类型来选择模块。如果是页面级别的操作,使用PyPDF2。如果您需要内容提取,请先使用pdfplumber。对应模块安装:pipinstallpypdf2pipinstallpdfminer.sixpipinstallpdfplumber下面根据使用场景分别演示三个模块的使用。PyPDF2PyPDF2的主要能力是在页面层面进行操作,例如:获取PDF文档的基本信息PDF拆分合并PDF旋转排序PDF水印去水印PDF加密解密PyPDF2的两个核心类PdfFileReader和PdfFileWriter完成PDF文件读写操作。获取PDF文档的基本信息importpathlibfromPyPDF2importPdfFileReaderpath=list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path=path.joinpath('2020-NewCoronary中国连锁餐饮业影响的肺炎疫情研究报告-中国连锁经营协会.pdf')withopen(f_path,'rb')asf: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拆分合并importpathlibfromPyPDF2importPdfFileReader,PdfFileWriterpath=list(pathlib.Path.cwd().parents)[1.joinpath('data/automate/002pdf')f_path=path.joinpath('2020-新冠肺炎疫情对中国连锁餐饮业影响研究报告-中连锁店iseAssociation.pdf')out_path=path.joinpath('002pdf_split_merge.pdf')out_path_1=路径。joinpath('002pdf_split_half_front.pdf')out_path_2=path.joinpath('002pdf_split_half_back.pdf')#将文件分成两半withopen(f_path,'rb')asf,open(out_path_1,'wb')asf_out1,打开(out_path_2,'wb')作为f_out2:pdf=PdfFileReader(f)pdf_out1=PdfFileWriter()pdf_out2=PdfFileWriter()cnt_pages=pdf.getNumPages()print(f'atotalof{cnt_pages}pages')foriinrange(cnt_pages):如果我<=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)#然后合并后半部分文件与前半部分文件合并,后半部分文件在前面withopen(out_path,'wb')asf_out:cnt_f,cnt_b=pdf_out1.getNumPages(),pdf_out2.getNumPages()pdf_out=PdfFileWriter()foriinrange(cnt_b):pdf_out.addPage(pdf_out2.getPage(i))foriinrange(cnt_f):pdf_out.addPage(pdf_out1.getPage(i))pdf_out.write(f_out)PDF旋转和排序importpathlibfromPyPDF2importPdfFileReader,PdfFileWriterpath=list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path=path.joinpath('2020-新冠影响研究报告中国连锁餐饮业的肺炎疫情-中国连锁经营协会.pdf')out_path=path.joinpath('002pdf_rotate.pdf')withopen(f_path,'rb')asf,open(out_path,'wb')asf_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添加水印和去除水印Adding图片水印其实就是给页面添加一张背景透明的图片,可以通过页面的mergePage方法来完成importpathlibfromPyPDF2importPdfFileReader,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')withopen(f_path,'rb')asf,open(wm_path,'rb')asf_wm,open(out_path,'wb')asf_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()foriinrange(cnt_pages):页面=pdf.getPage(i)page.mergePage(wm_cn_page)pdf_out.addPage(page)pdf_out.write(f_out)去水印比较复杂,需要根据不同情况具体分析。由于水印可能是文字、图片或各种组合,关键是识别特征。常见的三种去水印思路:找到特征词并替换,适用于英文文档,不适用于中文等CJK字符。将PDF页面转换为图片后,使用图片算法去除水印,但这会破坏文件原有的信息结构。根据水印的大小和位置特征,找到所有元素并删除。这是比较推荐的方式。第三种方法效果最好,但是如果遇到一些复杂的文档水印,那就很考验耐心了。必须一一识别操作命令,边替换边检查效果,直到去水印成功。但是,并不是所有的剩余页面都可以用相同的特征模式来消除,因为这个PDF可能已经被多人打过水印,并且已经包含了多种打水印的方法。因此,目前还没有100%安全有效(删除信息)、万能的去水印方法。加水印和去水印本质上是一种攻防策略。例如,一些工具推出了去水印功能。一旦公开,水印器就可以识别并避开其去除方法。最后,尊重版权是每个人都应该有的态度。除了学习,正式使用时,还应遵守内容创作者的规则。PDF加解密PDF密码,分为用户密码和所有者密码。PyPDF2提供基本的加密功能,“防君子不防小人”。如果打开PDF文件后复制了一个新文件,则新文件不受所有者密码的约束,可以修改。importpathlibfromPyPDF2importPdfFileReader,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')withopen(f_path,'rb')作为f,打开(out_path_encrypt,'wb')作为f_out:pdf=PdfFileReader(f)pdf_out=PdfFileWriter()cnt_pages=pdf.getNumPages()foriinrange(cnt_pages):page=pdf.getPage(i)pdf_out.addPage(page)pdf_out.encrypt('123456',owner_pwd='654321')pdf_out.write(f_out)#重新读取加密文件生成解密文件withopen(out_path_encrypt,'rb')asf,open(out_path_decrypt,'wb')asf_out:pdf=PdfFileReader(f)ifnotpdf.isEncrypted:print('Thefileisnotencrypted')else:success=pdf.decrypt('123456')#如果不成功:pdf_out=PdfFileWriter()pdf_out.appendPagesFromReader(pdf)pdf_out.write(f_out)pdfminer.sixPDFMiner操作门槛较高,需要部分了解PDF文档结构模型,适合定制开发复杂的内容处理工具直接使用PDFMiner比较少见。这里我们只演示基本的文档内容操作:pdfminer.converterimportPDFPageAggregatorpath=list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path=path.joinpath('2020-新能源影响研究报告中国连锁餐饮业的新冠肺炎疫情-中国连锁经营协会.pdf')withopen(f_path,'rb')asf:parser=PDFParser(f)doc=PDFDocument(parser)rsrcmgr=PDFResourceManager()laparams=LAParams()device=PDFPageAggregator(rsrcmgr,laparams=laparams)interpreter=PDFPageInterpreter(rsrcmgr,device)forpageinPDFPage.create_pages(doc):interpreter.process_page(page)layout=device布局中x的.get_result():#获取文本对象ifisinstance(x,LTTextBox):print(x.get_text().strip())#获取图像对象ifisinstance(x,LTImage):print('Getapicturehere')#获取图形对象ifisinstance(x,LTFigure):print('Afigureobjecthasbeenobtainedhere')虽然pdfminer的使用门槛很高,但是在复杂的情况下,必须在当前的开源模块中使用。它对PDF的支持应该是最全的。下面的pdfplumber是基于pdfminer.six开发的模块,降低了使用门槛。与pdfminer.six相比,pdfplumber提供了更方便的PDF内容提取接口。日常工作中常用的操作,如:提取PDF内容,保存到txt文件,将PDF中的表格提取到Excel,提取PDF中的图片,提取PDF中的图表,提取PDF内容,保存到txt文件中importpathlibimportpdfplumberpath=list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path=path.joinpath('2020-新冠肺炎疫情对中国连锁餐饮行业影响研究报告-中国连锁经营协会.pdf')out_path=path.joinpath('002pdf_out.txt')withpdfplumber.open(f_path)aspdf,open(out_path,'a')astxt:forpageinpdf.pages:textdata=page.extract_text()txt.write(textdata)将PDF中的表格提取到ExcelimportpathlibimportpdfplumberfromopenpyxlimportWorkbookpath=list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path=path.joinpath('2020-新冠肺炎疫情对中国连锁餐饮行业影响研究报告-中连锁和法郎hiseAssociation.pdf')out_path=path.joinpath('002pdf_excel.xlsx')wb=Workbook()sheet=wb.activewithpdfplumber.open(f_path)aspdf:foriinrange(19,22):page=pdf.pages[i]table=page.extract_table()forrowintable:sheet.append(row)wb.save(out_path)上面用到了openpyxl的函数创建了一个Excel文件,后面会有单独的文章介绍提取PDF中的图片importpathlibimportpdfplumberfromPILimportImagepath=list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path=path.joinpath('2020-疫情影响下的华人社区趋势研究-艾睿.pdf')out_path=path.joinpath('002pdf_images.png')withpdfplumber.open(f_path)aspdf,open(out_path,'wb')asfout:page=pdf.pages[10]#forimginpage.images:im=page.to_image()im.save(out_path,format='PNG')imgs=page.i的图像,枚举(imgs)中的img:size=img['width'],img['height']data=img['stream'].get_data()out_path=path.joinpath(f'002pdf_images_{i}.png')withopen(out_path,'wb')asfimg_out:fimg_out.write(data)使用PIL(Pillow)的函数处理图片。提取PDF中的图表图表不同于图像,是指数据生成的图表,如直方图和饼图。importpathlibimportpdfplumberfromPILimportImagepath=list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/002pdf')f_path=path.joinpath('2020-新冠肺炎疫情将影响中国链餐饮业影响研究报告-中国连锁经营协会.pdf')out_path=path.joinpath('002pdf_figures.png')withpdfplumber.open(f_path)aspdf,open(out_path,'wb')asfout:page=PDF。页面[7]im=page.to_image()im.save(out_path,format='PNG')figures=page.figuresfori,figinenumerate(figures):size=fig['width'],fig['height']crop=page.crop((fig['x0'],fig['top'],fig['x1'],fig['bottom']))img_crop=crop.to_image()out_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#showinnotebook摘要本文介绍了PDF的常见使用场景以及Python处理PDF的三个主要模块。应该补充一点,PDF标准规范由Adob??e主导。通常我们不需要去参考规范,但是如果遇到一些比较复杂的场景,尤其是模块没有直接支持的时候,我们只能硬着头皮看文档。文档是公开的,可以去搜索引擎搜索关键词:pdf_reference_1-7.pdf。最后建一个学习群,有兴趣的可以加入,前100名免费(弹窗付费信息可以无视)。代码和demo资料正在整理中,群里发布交流中。
