【问题标题】:Search and replace for text within a pdf, in Python在 Python 中搜索和替换 pdf 中的文本
【发布时间】:2017-01-20 17:20:46
【问题描述】:

我正在编写邮件合并软件作为 Python 网络应用程序的一部分。

我有一个名为 letter.pdf 的模板,它是从 MS Word 文件生成的,其中包含居民姓名所在的文本 {name}。我也有一个 c 的列表。 100个居民的名字。

我想要做的是阅读letter.pdf 并搜索"{name}" 并将其替换为居民的姓名(对于每个居民),然后将结果写入另一个pdf。然后,我想将所有这些 pdf 文件收集在一起,而不是一个大的 pdf(每封信一页),我的网络应用程序的用户将打印出来以创建他们的信件。

是否有任何 Python 库可以做到这一点?我查看了 pdfrw 和 pdfminer,但我看不出他们能在哪里做到这一点。

(注意:我也有 MS Word 文件,所以如果有另一种不通过 pdf 的方式使用它,那也可以。)

【问题讨论】:

    标签: python pdf


    【解决方案1】:

    这可以通过 PyPDF2 包来完成。实现可能取决于原始 PDF 模板结构。但是,如果模板足够稳定并且不经常更改,则替换代码不应该是通用的而是简单的。

    我做了一个关于如何替换 PDF file 中的文本的小草图。它将所有出现的PDF 标记替换为DOC

    import os
    import argparse
    from PyPDF2 import PdfFileReader, PdfFileWriter
    from PyPDF2.generic import DecodedStreamObject, EncodedStreamObject
    
    
    def replace_text(content, replacements = dict()):
        lines = content.splitlines()
    
        result = ""
        in_text = False
    
        for line in lines:
            if line == "BT":
                in_text = True
    
            elif line == "ET":
                in_text = False
    
            elif in_text:
                cmd = line[-2:]
                if cmd.lower() == 'tj':
                    replaced_line = line
                    for k, v in replacements.items():
                        replaced_line = replaced_line.replace(k, v)
                    result += replaced_line + "\n"
                else:
                    result += line + "\n"
                continue
    
            result += line + "\n"
    
        return result
    
    
    def process_data(object, replacements):
        data = object.getData()
        decoded_data = data.decode('utf-8')
    
        replaced_data = replace_text(decoded_data, replacements)
    
        encoded_data = replaced_data.encode('utf-8')
        if object.decodedSelf is not None:
            object.decodedSelf.setData(encoded_data)
        else:
            object.setData(encoded_data)
    
    
    if __name__ == "__main__":
        ap = argparse.ArgumentParser()
        ap.add_argument("-i", "--input", required=True, help="path to PDF document")
        args = vars(ap.parse_args())
    
        in_file = args["input"]
        filename_base = in_file.replace(os.path.splitext(in_file)[1], "")
    
        # Provide replacements list that you need here
        replacements = { 'PDF': 'DOC'}
    
        pdf = PdfFileReader(in_file)
        writer = PdfFileWriter()
    
        for page_number in range(0, pdf.getNumPages()):
    
            page = pdf.getPage(page_number)
            contents = page.getContents()
    
            if isinstance(contents, DecodedStreamObject) or isinstance(contents, EncodedStreamObject):
                process_data(contents, replacements)
            elif len(contents) > 0:
                for obj in contents:
                    if isinstance(obj, DecodedStreamObject) or isinstance(obj, EncodedStreamObject):
                        streamObj = obj.getObject()
                        process_data(streamObj, replacements)
    
            writer.addPage(page)
    
        with open(filename_base + ".result.pdf", 'wb') as out_file:
            writer.write(out_file)
    

    结果是

    2021-03-21 更新:

    更新了代码示例以处理 DecodedStreamObjectEncodedStreamObject,它们实际上包含要更新的文本的数据流。

    【讨论】:

    • 这适用于示例文件,但在处理证书时出现此错误。 data = object.getData() AttributeError: 'NameObject' object has no attribute 'getData' 对此有何解决方案?
    • 同样的问题! AttributeError: 'NameObject' object has no attribute 'getData'
    • 这意味着PDF内容流结构不同。您能否提供您正在处理的示例 PDF 的链接。然后我可以更新答案。
    • 例如从谷歌文档下载的这个 pdf。我们.tl / t-pYzmky0R5B
    • @Dmytro 任何解决方案,我也遇到了同样的问题AttributeError: 'NameObject' object has no attribute 'getData'
    【解决方案2】:

    如果@Dmytrio 解决方案不改变最终的PDF

    Dymitrio 的更新代码示例用于处理实际上包含要更新的文本的数据流的 DecodedStreamObject 和 EncodedStreamObject 可以正常运行,但使用与示例不同的文件,无法更改 pdf 文本内容。

    根据编辑 3,来自How to replace text in a PDF using Python?

    通过在writer.addPage(page) 之前插入page[NameObject("/Contents")] = contents.decodedSelf,我们强制pyPDF2 更新页面对象的内容。

    通过这种方式,我能够克服这个问题并替换 pdf 文件中的文本。

    最终代码应如下所示:

    import os
    import argparse
    from PyPDF2 import PdfFileReader, PdfFileWriter
    from PyPDF2.generic import DecodedStreamObject, EncodedStreamObject, NameObject
    
    
    def replace_text(content, replacements = dict()):
        lines = content.splitlines()
    
        result = ""
        in_text = False
    
        for line in lines:
            if line == "BT":
                in_text = True
    
            elif line == "ET":
                in_text = False
    
            elif in_text:
                cmd = line[-2:]
                if cmd.lower() == 'tj':
                    replaced_line = line
                    for k, v in replacements.items():
                        replaced_line = replaced_line.replace(k, v)
                    result += replaced_line + "\n"
                else:
                    result += line + "\n"
                continue
    
            result += line + "\n"
    
        return result
    
    
    def process_data(object, replacements):
        data = object.getData()
        decoded_data = data.decode('utf-8')
    
        replaced_data = replace_text(decoded_data, replacements)
    
        encoded_data = replaced_data.encode('utf-8')
        if object.decodedSelf is not None:
            object.decodedSelf.setData(encoded_data)
        else:
            object.setData(encoded_data)
    
    
    if __name__ == "__main__":
        ap = argparse.ArgumentParser()
        ap.add_argument("-i", "--input", required=True, help="path to PDF document")
        args = vars(ap.parse_args())
    
        in_file = args["input"]
        filename_base = in_file.replace(os.path.splitext(in_file)[1], "")
    
        # Provide replacements list that you need here
        replacements = { 'PDF': 'DOC'}
    
        pdf = PdfFileReader(in_file)
        writer = PdfFileWriter()
    
        for page_number in range(0, pdf.getNumPages()):
    
            page = pdf.getPage(page_number)
            contents = page.getContents()
    
            if isinstance(contents, DecodedStreamObject) or isinstance(contents, EncodedStreamObject):
                process_data(contents, replacements)
            elif len(contents) > 0:
                for obj in contents:
                    if isinstance(obj, DecodedStreamObject) or isinstance(obj, EncodedStreamObject):
                        streamObj = obj.getObject()
                        process_data(streamObj, replacements)
    
            # Force content replacement
            page[NameObject("/Contents")] = contents.decodedSelf
            writer.addPage(page)
    
        with open(filename_base + ".result.pdf", 'wb') as out_file:
            writer.write(out_file)
    

    重要提示: from PyPDF2.generic import NameObject

    【讨论】:

    • 我有这个问题,但似乎data.decode('utf-8') 没有解码为文本格式?
    • 您的 PDF 可能不使用 utf-8 编码。您可能想测试data.decode("ascii") 是否适合您。顺便说一句,如果你住在拉丁美洲(比如我),你可能想试试data.decode("iso-8859-1")。如果这没有帮助,您可以尝试通过解析data.decode("utf-8", "ignore") 进行暴力解码
    • 我在每个已知标准上运行了for 循环,但它不起作用。我只能假设使用 Word 中的Save As 时 Acrobat 对 PDF 的编码方式不同?
    • @alias51,你有没有试过 print(data = object.getData()) 在 proces_data() 里面?如果这没有为您提供 pdf 的文本内容,则您的文件可能已被 Acrobat 密码加密。您可以在此处获得有关密码解密的一些参考:github.com/mstamy2/PyPDF2/issues/378github.com/atlanhq/camelot/issues/325 ; github.com/mstamy2/PyPDF2/issues/378#issuecomment-689585779
    【解决方案3】:
    1. 解压pdf,让解析更容易(解决了上一个答案中的很多问题)。我使用pdftk。 (如果此步骤失败,预处理 pdf 的一个技巧是在 OSX Preview 中打开 pdf,打印它,然后从打印菜单中选择另存为 pdf。然后重试下面的命令。)
    pdftk original.pdf output uncompressed.pdf uncompress
    
    1. 使用PyPDF2解析和替换。
    from PyPDF2 import PdfFileReader, PdfFileWriter
    
    replacements = [
        ("old string", "new string")
    ]
    
    pdf = PdfFileReader(open("uncompressed.pdf", "rb"))
    writer = PdfFileWriter() 
    
    for page in pdf.pages:
        contents = page.getContents().getData()
        for (a,b) in replacements:
            contents = contents.replace(a.encode('utf-8'), b.encode('utf-8'))
        page.getContents().setData(contents)
        writer.addPage(page)
        
    with open("modified.pdf", "wb") as f:
         writer.write(f)
    
    1. [可选] 重新压缩 pdf。
    pdftk modified.pdf output recompressed.pdf compress
    

    【讨论】:

    • 结果为@​​987654326@
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-16
    • 2018-12-27
    • 1970-01-01
    • 2010-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多