社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

利用Python玩转PDF,简单实用

挖地兔 • 6 年前 • 755 次点击  


TUSHARE  金融与技术学习兴趣小组 


翻译 | Thoudancer

 

本期编辑 | Little monster 


译者简介:苏州大学应用统计硕士,信息与计算科学本科,专注数据科学,目前在学习量化投资相关知识。



作者:Real Python




类似PDF的可移植的文档格式能够可靠地跨操作系统呈现或交换文档。尽管PDF最开始是由Adobe发明的,但它现在已经成为国际标准组织ISO维护的公开标准了。大家可以在Python中通过PyPDF2包来处理已存在的PDF。PyPDF2是一个纯Python的包,通过它可以进行多种不同类型的PDF操作。通过阅读本文,您将了解以下技能:



提取PDF信息

旋转PDF页面

合并PDF

拆分PDF

添加水印

加密PDF




目录



· PyPdf、PyPDF2、PyPDF4的发展史

· PDF工具包:pdfrw

· 安装 PyPDF2

· 提取PDF信息

· 旋转PDF页面

· 合并PDF

· 拆分PDF

· 添加水印

· 加密PDF

· 总结





01 


PyPdf、PyPDF2、PyPDF4的发展史



最初的pyPdf包是在2005年发行的。最后一个官方pyPdf是在2010年发行的。大约一年后,一家名为Phasit的公司赞助了pyPdf的一个分支PyPDF2。这个包的代码可以与最初的版本兼容,并且在好几年内都运行得相当好,直到2016年发行最后一版。



在PyPDF3发布了几个版本后,项目被重命名为PyPDF4。虽然这些项目版本几乎做同样的事,但pyPdf和PyPDF2+之间最大的不同在于后来的版本支持Python3。虽然Python3也有一个原始pyPdf分支,但并没有维持多少年。



尽管PyPDF2最近不再使用,新的PyPDF4也无法完全向后兼容PyPDF2。尽管本文的大多数例子使用PyPDF4都运行得非常顺利,但其中有一些不行,这正是本文没有过多强调PyPDF4的原因。当然,您也可以导入PyPDF4来替换PyPDF2,观察它是如何实现的。




02


 PDF工具包:pdfrw



Patrick Maupin 发明了pdfrw包,可以完成很多PyPDF2的工作。您可以使用pdfrw处理本文中用PyPDF2处理的同类型任务,加密任务除外。



pdfrw最大的不同在于整合了ReportLab包,因此您可以使用已存在的PDF,通过ReportLab将这些已存在的PDF的部分或全部创建成新的文档。




03


 安装PyPDF2



如果您刚好使用Anaconda,而不是纯Python,可以用pip或者conda安装PyPDF2,以下用pip安装PyPDF2。



pip install pypdf2



PyPDF2的安装不需要依赖任何包,所以安装很快,并且下载包的速度和安装的速度差不多。




04


提取PDF信息



您可以使用PyPDF2从PDF中提取元数据和一些文本。当您在已存在的PDF文件中执行某些类型的自动化时,这会比较有用。



可以提取的数据类型如下:

· 作者

· 创建者

· 生成器

· 主题

· 标题

· 页数



您需要找到一个PDF来运行这个例子,电脑上的任意PDF皆可。为了简便,我去Leanpub选取了我一本书中的一个样本。您可以下载这个样本—reportlab-sample.pdf.



以下代码处理PDF,您可以了解到这些属性:


# extract_doc_info.py

from PyPDF2 import PdfFileReader

def extract_information(pdf_path):
    with open(pdf_path, 'rb'as f:
        pdf = PdfFileReader(f)
        information = pdf.getDocumentInfo()
        number_of_pages = pdf.getNumPages()

    txt = f"""
    Information about {pdf_path}

    Author: {information.author}
    Creator: {information.creator}
    Producer: {information.producer}
    Subject: {information.subject}
    Title: {information.title}
    Number of pages: {number_of_pages}
    """


    print(txt)
    return information

if __name__ == '__main__':
    path = 'reportlab-sample.pdf'
    extract_information(path)



从PyPDF2包中导入PdfFileReader。PdfFileReader是一个类,包含与PDF文件交互的几个方法。在此例子中,调用.getDocumentInfo(),返回DocumentInformation的一个实例,包含了您感兴趣的大多数信息。您也可以调用对象的.getNumPages(),返回文件的页数。



可以用Information变量的几个属性来获取文档中的其余元数据。可以打印出信息和返回信息以备未来之需。



尽管PyPDF2的.extractText()可用在页数对象上(此例子未展示),但并不好用。因为有些PDF会返回文本内容,有些则会返回空字符。当您希望从一篇PDFF中提取文档,您需要检查PDFMiner。PDFMiner是专门为从PDF中提取文档的功能设计的,更为稳定。



【注】最后一段代码使用Python3最新的字符串格式。如果您想学习更多,可以查阅Python 3’s f-Strings: An Improved String Formatting Syntax (Guide).




05


旋转PDF页面



有时候,您会收到一些横向模式而不是纵向模式的PDF,或者这些文档是上下颠倒的。如下图:





这种情况经常出现在人们把文件扫描成PDF或者邮件。您可以打印文件,了解文章版本,或者使用Python旋转有问题的页面。在此例中,您可以去往Real Python article 打印PDF来学习PyPDF2。



以下是用PyPDF2旋转文章页面的代码:


# rotate_pages.py

from PyPDF2 import PdfFileReader, PdfFileWriter

def rotate_pages(pdf_path):
    pdf_writer = PdfFileWriter()
    pdf_reader = PdfFileReader(path)
    # Rotate page 90 degrees to the right
    page_1 = pdf_reader.getPage(0).rotateClockwise(90)
    pdf_writer.addPage(page_1)
    # Rotate page 90 degrees to the left
    page_2 = pdf_reader.getPage(1).rotateCounterClockwise(90)
    pdf_writer.addPage(page_2)
    # Add a page in normal orientation
    pdf_writer.addPage(pdf_reader.getPage(2))

    with open('rotate_pages.pdf''wb'as fh:
        pdf_writer.write(fh)

if __name__ == '__main__':
    path = 'Jupyter_Notebook_An_Introduction.pdf'
    rotate_pages(path)
    """

    print(txt)
    return information

if __name__ == '__main__':
    path = 'reportlab-sample.pdf'
    extract_information(path)



在此例中,您需要导入PdfFileWriterPdfFileReader,因为您需要输出新的PDF。rotate_pages()输入您想要修正的PDF路径。在这个功能中,您需要创建一个写对象pdf_writer,一个读对象pdf_reader



然后,您可以使用.getPage()获取想要的页面。您可以抓取页面0,即首页。然后您可以调用页面对象的.rotateClockwise()方法,输入90度。同样的,对于页面2,您可以调用.rotateCounterClockwise()并输入90度。



每次调用旋转方法后,调用.addPage()来添加页面的旋转版本到写对象。添加到写对象的最后一页是没有任何旋转的页面3。



最后,用.write() 写入新的PDF,其用file-like object作为参数。新的PDF包含3页,前两页以相反方向旋转成横向,第三页正常。



【注】PyPDF2包只允许您以90度的增量旋转页面,如90、180、270……,否则会收到AssertionError报错。




06


合并PDF



很多情况下需要将两个或多个PDF合并成一个PDF。比如,有一个标准封面需要加到多种类型的报告中。您可以使用Python帮您处理。



在此例中,您可以打开一个PDF,输出一个页面作为独立的PDF,重复输出获得不同页面,这样就有两个输入作为本例的目的。以下是合并的代码:



# pdf_merging.py

from PyPDF2 import PdfFileReader, PdfFileWriter

def merge_pdfs(paths, output):
    pdf_writer = PdfFileWriter()

    for path in paths:
        pdf_reader = PdfFileReader(path)
        for page in range(pdf_reader.getNumPages()):
            # Add each page to the writer object
            pdf_writer.addPage(pdf_reader.getPage(page))

    # Write out the merged PDF
    with open(output, 'wb'as out:
        pdf_writer.write(out)

if __name__ == '__main__':
    paths = ['document1.pdf''document2.pdf']
    merge_pdfs(paths, output='merged.pdf')


若想合并一系列PDF,可以调用merge_pdfs(),并且需要知道保存结果的路径。因此这个函数的输入参数是这些PDF的路径以及结果保存的路径。



然后遍历原PDF的路径,并为每个路径创建一个PDF读对象。接着,您需要遍历该路径对应的PDF所有页面,通过.addPage()添加到页面本身。完成所有PDF所有页面的处理后,即可以进行写操作,输出PDF。



需要注意的是,如果您不想合并每个PDF的所有页面,需要添加一系列要添加的页面来强化这个脚本。若您愿意挑战,也可以使用Python的argparse模块为这功能创建命令行界面。




07


拆分PDF



有时候需要将一个PDF拆分成多个PDF,尤其当PDF包含很多扫描的内容,但是也有众多其它原因需要去拆分PDF。以下是使用PyPDF2将一个PDF拆分成多个。



# pdf_splitting.py

from PyPDF2 import PdfFileReader, PdfFileWriter

def split(path, name_of_split):
    pdf = PdfFileReader(path)
    for page in range(pdf.getNumPages()):
        pdf_writer = PdfFileWriter()
        pdf_writer.addPage(pdf.getPage(page))

        output = f'{name_of_split}{page}.pdf'
        with open(output, 'wb'as output_pdf:
            pdf_writer.write(output_pdf)

if __name__ == '__main__':
    path = 'Jupyter_Notebook_An_Introduction.pdf'
    split(path, 'jupyter_page')



在此例中,首先创建PDF读对象,并遍历页面。对每个页面,创建新的PDF写实例,并添加新的页面。然后将页面写入独立命名的文件。当脚本结束运行,得到原始PDF的每个页面拆分成独立PDF。




08


添加水印



水印可以识别印刷和数字文件的图片和图像模式。有些水印只能在特殊光照条件下才能看见。水印非常重要,可以保护知识产权,比如图像或者PDF。水印的另一术语是叠加。



可以通过Python和PyPDF2为文件添加水印。您的PDF必须只包含水印图或者文本。以下是添加水印代码:


# pdf_watermarker.py

from PyPDF2 import PdfFileWriter, PdfFileReader

def create_watermark(input_pdf, output, watermark):
    watermark_obj = PdfFileReader(watermark)
    watermark_page = watermark_obj.getPage(0)

    pdf_reader = PdfFileReader(input_pdf)
    pdf_writer = PdfFileWriter()

    # Watermark all the pages
    for page in range(pdf_reader.getNumPages()):
        page = pdf_reader.getPage(page)
        page.mergePage(watermark_page)
        pdf_writer.addPage(page)

    with open(output, 'wb' as out:
        pdf_writer.write(out)

if __name__ == '__main__':
    create_watermark(
        input_pdf='Jupyter_Notebook_An_Introduction.pdf'
        output='watermarked_notebook.pdf',
        watermark='watermark.pdf')



create_watermark()接收三个参数:

① input_pdf:需要加水印的PDF路径

② output:水印版本PDF的保存路径

watermark:含有水印图像或文本的PDF



代码中,打开水印PDF,从文档的第一页抓取信息,因为第一页即保存了水印内容。然后用input_pdf创建PDF读对象并创建通用的pdf_writer对象来写入加了水印的PDF。



下一步是遍历input_pdf的所有页面,魔法开始了。调用.mergePage()并传入watermark_page。这一步是将水印页面叠加到现有页面中,然后将新的叠加页面添加到pdf_writer对象中。最后,把新的加了水印的PDF写入到磁盘中,完成。





09


加密PDF



PyPDF2目前只支持对已有PDF添加用户密码和所有者密码。在PDF中,所有者密码基本上拥有对该PDF的管理员权限,允许对文档设置权限。另一方面, 用户密码仅允许打开文件。



正如所说的,PyPDF2事实上并不能对文档设置任何权限,即使可以设置所有者密码。无论如何,以下是添加密码、本质上加密PDF的代码:



# pdf_encrypt.py

from PyPDF2 import PdfFileWriter, PdfFileReader

def add_encryption(input_pdf, output_pdf, password):
    pdf_writer = PdfFileWriter()
    pdf_reader = PdfFileReader(input_pdf)

    for page in range(pdf_reader.getNumPages()):
        pdf_writer.addPage(pdf_reader.getPage(page))

    pdf_writer.encrypt(user_pwd=password, owner_pwd=None
                       use_128bit=True)

    with open(output_pdf, 'wb'as fh:
        pdf_writer.write(fh)

if __name__ == '__main__':
    add_encryption(input_pdf= 'reportlab-sample.pdf',
                   output_pdf='reportlab-encrypted.pdf',
                   password='twofish')



add_encryption()需要传入输入和输出PDF的路径以及添加到PDF的密码。打开PDF读和写对象,遍历PDF所有页面并添加到写对象。最后调用.encrypt(),参数为用户密码、所有者密码以及是否128位加密。默认128位加密,若设置False, 则为40位加密。


 

【注】根据pdflib.com,PDF加密技术要么采用RC4,要么采用AES(Advanced Encryption Standard)。因为加密了PDF并不意味着百分百安全,也有一些工具能从PDF中删除密码。若想了解更多,卡内基梅隆大学在这方面有一个有趣的论文【1】。




10


总结



PyPDF2包相当有用,通常情况下也比较快。您可以使用PyPDF2自动化大量的工作,利用其能力帮您更好地工作。



在此教程中,包含了以下内容:

· 从PDF提取元数据

· 旋转页面

· 合并和拆分PDF

· 添加水印

· 加密PDF



基于以上基本功能,也可以拓展其它功能,如删除页面。代码如下:


# pdf_delete.py

from PyPDF2 import PdfFileWriter, PdfFileReader

def delete(path, name_of_delete, delete_page):
    pdf_reader = PdfFileReader(path)
    pdf_writer = PdfFileWriter()
    for page in range(0, delete_page):
        pdf_writer.addPage(pdf_reader.getPage(page))
    for page in range(delete_page+1, pdf_reader.getNumPages()):
        pdf_writer.addPage(pdf_reader.getPage(page))
    output = f'{name_of_delete}.pdf'
    with open(output_pdf, 'wb'as fh:
        pdf_writer.write(fh)
if __name__ == '__main__':
path  = 'merged.pdf'
delete(path, name_of_delete='new.pdf', delete_page=2)



在此删除例子中,输入原PDF路径和删除后需要保存的路径以及删除页面(本例为第3页),然后通过读对象向写对象添加除了第三页之外的所有页面,然后写入磁盘保存。若删除多页,只要在循环添加页面时省去那些页面即可。大家也可以考虑更多的功能。



关注更新的PyPDF4,不久后它将取代PyPDF2。您也可以尝试pdfrw,实现PyPDF2能做的同样的任务。



【更多阅读】

如果您想了解更多关于Python处理PDF的内容,可以查看下列资源:


https://pythonhosted.org/PyPDF2/

https://github.com/claird/PyPDF4

https://github.com/pmaupin/pdfrw

https://www.reportlab.com/

https://github.com/euske/pdfminer

https://github.com/socialcopsdev/camelot



END


更多内容请关注“挖地兔”公众号。



【参考链接】

https://realpython.com/pdf-python/#further-reading

https://www.cs.cmu.edu/~dst/Adobe/Gallery/PDFsecurity.pdf【1】


【扩展阅读】

年化1000%?!什么才是回测的正确姿势之系列一

利用Tushare数据实现知识图谱效果

你真的懂线程吗?史上最全Python线程解析

大佬告诉你如何成为一名Quant

他才是有史以来最伟大的因子组合投资者

利用python进行蒙特卡罗模拟



Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/32388
 
755 次点击