【问题标题】:Merge PDF files合并 PDF 文件
【发布时间】:2011-03-27 13:21:37
【问题描述】:

是否可以使用 Python 合并单独的 PDF 文件?

假设是这样,我需要进一步扩展它。我希望遍历目录中的文件夹并重复此过程。

我可能会碰运气,但是否可以排除每个 PDF 中包含的页面(我的报告生成总是会创建一个额外的空白页面)。

【问题讨论】:

    标签: python pdf file-io pypdf2 pypdf


    【解决方案1】:

    您可以使用PyPdf2s PdfMerger 类。

    文件串联

    您可以使用append 方法简单地concatenate 文件。

    from PyPDF2 import PdfFileMerger
    
    pdfs = ['file1.pdf', 'file2.pdf', 'file3.pdf', 'file4.pdf']
    
    merger = PdfFileMerger()
    
    for pdf in pdfs:
        merger.append(pdf)
    
    merger.write("result.pdf")
    merger.close()
    

    如果需要,您可以传递文件句柄而不是文件路径。

    文件合并

    如果您想要更精细的合并控制,可以使用PdfMergermerge 方法,它允许您在输出文件中指定插入点,这意味着您可以在文件中的任何位置插入页面。 append 方法可以被认为是一个merge,其中插入点是文件的结尾。

    例如

    merger.merge(2, pdf)
    

    这里我们将整个 pdf 插入到输出中,但在第 2 页。

    页面范围

    如果您希望控制从特定文件附加哪些页面,您可以使用appendmergepages 关键字参数,以(start, stop[, step]) 的形式传递一个元组(就像常规的@987654342 @函数)。

    例如

    merger.append(pdf, pages=(0, 3))    # first 3 pages
    merger.append(pdf, pages=(0, 6, 2)) # pages 1,3, 5
    

    如果你指定了一个无效的范围,你会得到一个IndexError

    注意:另外,为避免文件保持打开状态,应在写入合并文件时调用 PdfFileMergers 关闭方法。这可确保所有文件都及时关闭(输入和输出)。很遗憾 PdfFileMerger 没有实现为上下文管理器,因此我们可以使用 with 关键字,避免显式关闭调用并获得一些简单的异常安全性。

    您可能还想查看作为 pypdf2 的一部分提供的 pdfcat 脚本。您可以完全避免编写代码的需要。

    PyPdf2 github 上还有includes 一些演示合并的示例代码。

    PyMuPdf

    另一个可能值得一看的库是PyMuPdf,它似乎得到了积极的维护。合并同样简单

    从命令行:

    python -m fitz join -o result.pdf file1.pdf file2.pdf file3.pdf
    

    来自代码

    import fitz
    
    result = fitz.open()
    
    for pdf in ['file1.pdf', 'file2.pdf', 'file3.pdf']:
        with fitz.open(pdf) as mfile:
            result.insertPDF(mfile)
        
    result.save("result.pdf")
    

    有很多选择,详情见项目wiki

    【讨论】:

    • 这应该是选择的答案。
    • PyMuPDF 比 PyPDF2 快得多,因为我必须做的事情(连接 280 个单页 pdf)。谢谢!
    【解决方案2】:

    使用Pypdf或其后继PyPDF2

    作为 PDF 工具包构建的纯 Python 库。它能够:

    • 逐页拆分文档,
    • 逐页合并文档,

    (还有更多)

    这是一个适用于两个版本的示例程序。

    #!/usr/bin/env python
    import sys
    try:
        from PyPDF2 import PdfFileReader, PdfFileWriter
    except ImportError:
        from pyPdf import PdfFileReader, PdfFileWriter
    
    def pdf_cat(input_files, output_stream):
        input_streams = []
        try:
            # First open all the files, then produce the output file, and
            # finally close the input files. This is necessary because
            # the data isn't read from the input files until the write
            # operation. Thanks to
            # https://stackoverflow.com/questions/6773631/problem-with-closing-python-pypdf-writing-getting-a-valueerror-i-o-operation/6773733#6773733
            for input_file in input_files:
                input_streams.append(open(input_file, 'rb'))
            writer = PdfFileWriter()
            for reader in map(PdfFileReader, input_streams):
                for n in range(reader.getNumPages()):
                    writer.addPage(reader.getPage(n))
            writer.write(output_stream)
        finally:
            for f in input_streams:
                f.close()
            output_stream.close()
    
    if __name__ == '__main__':
        if sys.platform == "win32":
            import os, msvcrt
            msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
        pdf_cat(sys.argv[1:], sys.stdout)
    

    【讨论】:

    • 现在,pypi.python.org/pypi/PyPDF2 是 PyPDF 的后续项目
    • 仅适用于我以二进制模式打开(输入流和输出流)。 open(input_file), 'r+b',而不是 sys.stdout 我使用output_stream = open('result.pdf', 'w+b')
    • @SimeonBorko 去掉+,它的意思是“读和写”,两个文件都不是既读又写的。我添加了基于 stackoverflow.com/questions/2374427/… 的 Windows 支持输出支持。
    • PyPDF2/3 不稳定,如何在没有 PyPDF2/3 的情况下合并 pdf 文件。
    • 我不得不在 Python 3.6.8 (Linux) 中使用sys.stdout.buffer
    【解决方案3】:

    合并目录中存在的所有 pdf 文件

    将 pdf 文件放在一个目录中。启动程序。您会得到一份合并了所有 pdf 的 pdf。

    import os
    from PyPDF2 import PdfFileMerger
    
    x = [a for a in os.listdir() if a.endswith(".pdf")]
    
    merger = PdfFileMerger()
    
    for pdf in x:
        merger.append(open(pdf, 'rb'))
    
    with open("result.pdf", "wb") as fout:
        merger.write(fout)
    

    我今天如何编写与上面相同的代码

    from glob import glob
    from PyPDF2 import PdfFileMerger
    
    
    
    def pdf_merge():
        ''' Merges all the pdf files in current directory '''
        merger = PdfFileMerger()
        allpdfs = [a for a in glob("*.pdf")]
        [merger.append(pdf) for pdf in allpdfs]
        with open("Merged_pdfs.pdf", "wb") as new_file:
            merger.write(new_file)
    
    
    if __name__ == "__main__":
        pdf_merge()
    

    【讨论】:

    • 我用这个成功
    【解决方案4】:

    pdfrw library 可以很容易地做到这一点,前提是您不需要保留书签和注释,并且您的 PDF 没有加密。 cat.py 是示例连接脚本,subset.py 是示例页面子集脚本。

    连接脚本的相关部分——假设inputs 是输入文件名列表,outfn 是输出文件名:

    from pdfrw import PdfReader, PdfWriter
    
    writer = PdfWriter()
    for inpfn in inputs:
        writer.addpages(PdfReader(inpfn).pages)
    writer.write(outfn)
    

    从这里可以看出,省略最后一页很容易,例如类似:

        writer.addpages(PdfReader(inpfn).pages[:-1])
    

    免责声明:我是pdfrw的主要作者。

    【讨论】:

    • 这个是最稳定的。
    • 这个库值得更多的声誉。
    【解决方案5】:

    是否可以使用 Python 合并单独的 PDF 文件?

    是的。

    以下示例将一个文件夹中的所有文件合并为一个新的 PDF 文件:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    from argparse import ArgumentParser
    from glob import glob
    from pyPdf import PdfFileReader, PdfFileWriter
    import os
    
    def merge(path, output_filename):
        output = PdfFileWriter()
    
        for pdffile in glob(path + os.sep + '*.pdf'):
            if pdffile == output_filename:
                continue
            print("Parse '%s'" % pdffile)
            document = PdfFileReader(open(pdffile, 'rb'))
            for i in range(document.getNumPages()):
                output.addPage(document.getPage(i))
    
        print("Start writing '%s'" % output_filename)
        with open(output_filename, "wb") as f:
            output.write(f)
    
    if __name__ == "__main__":
        parser = ArgumentParser()
    
        # Add more options if you like
        parser.add_argument("-o", "--output",
                            dest="output_filename",
                            default="merged.pdf",
                            help="write merged PDF to FILE",
                            metavar="FILE")
        parser.add_argument("-p", "--path",
                            dest="path",
                            default=".",
                            help="path of source PDF files")
    
        args = parser.parse_args()
        merge(args.path, args.output_filename)
    

    【讨论】:

      【解决方案6】:
      from PyPDF2 import PdfFileMerger
      import webbrowser
      import os
      dir_path = os.path.dirname(os.path.realpath(__file__))
      
      def list_files(directory, extension):
          return (f for f in os.listdir(directory) if f.endswith('.' + extension))
      
      pdfs = list_files(dir_path, "pdf")
      
      merger = PdfFileMerger()
      
      for pdf in pdfs:
          merger.append(open(pdf, 'rb'))
      
      with open('result.pdf', 'wb') as fout:
          merger.write(fout)
      
      webbrowser.open_new('file://'+ dir_path + '/result.pdf')
      

      Git 仓库:https://github.com/mahaguru24/Python_Merge_PDF.git

      【讨论】:

        【解决方案7】:

        这里,http://pieceofpy.com/2009/03/05/concatenating-pdf-with-python/,给出了一个解决方案。

        类似:

        from pyPdf import PdfFileWriter, PdfFileReader
        
        def append_pdf(input,output):
            [output.addPage(input.getPage(page_num)) for page_num in range(input.numPages)]
        
        output = PdfFileWriter()
        
        append_pdf(PdfFileReader(file("C:\\sample.pdf","rb")),output)
        append_pdf(PdfFileReader(file("c:\\sample1.pdf","rb")),output)
        append_pdf(PdfFileReader(file("c:\\sample2.pdf","rb")),output)
        append_pdf(PdfFileReader(file("c:\\sample3.pdf","rb")),output)
        
            output.write(file("c:\\combined.pdf","wb"))
        

        【讨论】:

          【解决方案8】:

          使用字典的细微变化以获得更大的灵活性(例如排序、去重):

          import os
          from PyPDF2 import PdfFileMerger
          # use dict to sort by filepath or filename
          file_dict = {}
          for subdir, dirs, files in os.walk("<dir>"):
              for file in files:
                  filepath = subdir + os.sep + file
                  # you can have multiple endswith
                  if filepath.endswith((".pdf", ".PDF")):
                      file_dict[file] = filepath
          # use strict = False to ignore PdfReadError: Illegal character error
          merger = PdfFileMerger(strict=False)
          
          for k, v in file_dict.items():
              print(k, v)
              merger.append(v)
          
          merger.write("combined_result.pdf")
          

          【讨论】:

            【解决方案9】:

            您可以使用PyPDF2 模块中的PdfFileMerger

            例如,要合并路径列表中的多个 PDF 文件,您可以使用以下函数:

            from PyPDF2 import PdfFileMerger
            
            # pass the path of the output final file.pdf and the list of paths
            def merge_pdf(out_path: str, extracted_files: list [str]):
                merger   = PdfFileMerger()
                
                for pdf in extracted_files:
                    merger.append(pdf)
            
                merger.write(out_path)
                merger.close()
            
            merge_pdf('./final.pdf', extracted_files)
            

            这个函数可以递归地从父文件夹中获取所有文件:

            import os
            
            # pass the path of the parent_folder
            def fetch_all_files(parent_folder: str):
                target_files = []
                for path, subdirs, files in os.walk(parent_folder):
                    for name in files:
                        target_files.append(os.path.join(path, name))
                return target_files 
            
            # get a list of all the paths of the pdf
            extracted_files = fetch_all_files('./parent_folder')
            

            最后,您使用两个函数 declaring.a parent_folder_path 可以包含多个文档,output_pdf_path 用于合并 PDF 的目的地:

            # get a list of all the paths of the pdf
            parent_folder_path = './parent_folder'
            outup_pdf_path     = './final.pdf'
            
            extracted_files = fetch_all_files(parent_folder_path)
            merge_pdf(outup_pdf_path, extracted_files)
            

            您可以从这里获取完整代码(来源):How to merge PDF documents using Python

            【讨论】:

              【解决方案10】:

              我在 linux 终端上通过利用 subprocess 使用了 pdf unity(假设目录中存在 one.pdf 和 two.pdf),目的是将它们合并为 three.pdf

               import subprocess
               subprocess.call(['pdfunite one.pdf two.pdf three.pdf'],shell=True)
              

              【讨论】:

              • 这会起作用,但是调用 subprocess 并不比使用 PyPDF2 中的 PdfFileMerger 更可取。使用 shell=true 会带来安全隐患。
              【解决方案11】:

              Giovanni G. PY 以一种易于使用的方式给出了答案(至少对我而言):

              import os
              from PyPDF2 import PdfFileMerger
              
              def merge_pdfs(export_dir, input_dir, folder):
                  current_dir = os.path.join(input_dir, folder)
                  pdfs = os.listdir(current_dir)
                  
                  merger = PdfFileMerger()
                  for pdf in pdfs:
                      merger.append(open(os.path.join(current_dir, pdf), 'rb'))
              
                  with open(os.path.join(export_dir, folder + ".pdf"), "wb") as fout:
                      merger.write(fout)
              
              export_dir = r"E:\Output"
              input_dir = r"E:\Input"
              folders = os.listdir(input_dir)
              [merge_pdfs(export_dir, input_dir, folder) for folder in folders];
              

              【讨论】:

                【解决方案12】:

                以下是针对我的特定用例的最常见答案的时间比较:组合 5 个大型单页 pdf 文件的列表。每个测试我运行了两次。

                (免责声明:我在 Flask 中运行此功能,您的里程可能会有所不同)

                TL;DR

                pdfrw 是我测试过的 3 个 pdf 组合中最快的库。

                PyPDF2

                start = time.time()
                merger = PdfFileMerger()
                for pdf in all_pdf_obj:
                    merger.append(
                        os.path.join(
                            os.getcwd(), pdf.filename # full path
                                )
                            )
                formatted_name = f'Summary_Invoice_{date.today()}.pdf'
                merge_file = os.path.join(os.getcwd(), formatted_name)
                merger.write(merge_file)
                merger.close()
                end = time.time()
                print(end - start) #1 66.50084733963013 #2 68.2995400428772
                

                PyMuPDF

                start = time.time()
                result = fitz.open()
                
                for pdf in all_pdf_obj:
                    with fitz.open(os.path.join(os.getcwd(), pdf.filename)) as mfile:
                        result.insertPDF(mfile)
                formatted_name = f'Summary_Invoice_{date.today()}.pdf'
                
                result.save(formatted_name)
                end = time.time()
                print(end - start) #1 2.7166640758514404 #2 1.694727897644043
                

                pdfrw

                start = time.time()
                result = fitz.open()
                
                writer = PdfWriter()
                for pdf in all_pdf_obj:
                    writer.addpages(PdfReader(os.path.join(os.getcwd(), pdf.filename)).pages)
                
                formatted_name = f'Summary_Invoice_{date.today()}.pdf'
                writer.write(formatted_name)
                end = time.time()
                print(end - start) #1 0.6040127277374268 #2 0.9576816558837891
                

                【讨论】:

                  【解决方案13】:

                  您也可以使用pikepdf (source code documentation)。

                  示例代码可以是(取自documentation):

                  from glob import glob
                  
                  from pikepdf import Pdf
                  
                  pdf = Pdf.new()
                  
                  for file in glob('*.pdf'):  # you can change this to browse directories recursively
                      with Pdf.open(file) as src:
                          pdf.pages.extend(src.pages)
                  
                  pdf.save('merged.pdf')
                  pdf.close()
                  

                  如果您想排除页面,您可能会采取另一种方式,例如将页面复制到新的 pdf 中(您可以选择不复制的页面,然后 pdf.pages 对象的行为类似于列表)。

                  它仍在积极维护中,截至 2022 年 2 月,PyPDF2 和 pdfrw 似乎都不是这种情况。

                  我没有对它进行基准测试,所以我不知道它是否比其他解决方案更快或更慢。

                  就我而言,与 PyMuPDF 相比的一个优势是可以使用官方的 Ubuntu 软件包 (python3-pikepdf),根据它来打包我自己的软件是很实用的。

                  【讨论】:

                    猜你喜欢
                    • 2013-04-02
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-08-10
                    • 1970-01-01
                    • 2018-10-08
                    • 2018-03-30
                    • 2011-09-27
                    相关资源
                    最近更新 更多