pypdf 将多个pdf文件合并为一个pdf

新手上路,请多包涵

如果我有1000多个pdf文件需要合并成一个pdf,

 from PyPDF2 import PdfReader, PdfWriter

writer = PdfWriter()

for i in range(1000):
    filepath = f"my/pdfs/{i}.pdf"
    reader = PdfReader(open(filepath, "rb"))
    for page in reader.pages:
        writer.add_page(page)

with open("document-output.pdf", "wb") as fh:
    writer.write(fh)

执行以上代码,当 reader = PdfReader(open(filepath, "rb")) ,

报错信息: IOError: [Errno 24] Too many open files:

我认为这是一个错误,如果不是,我该怎么办?

原文由 daydaysay 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 522
2 个回答

我最近遇到了这个完全相同的问题,所以我深入研究了 PyPDF2 以了解发生了什么,以及如何解决它。

注意:我假设 filename 是格式正确的文件路径字符串。假设我所有的代码都一样

简短的回答

使用 PdfFileMerger() 类而不是 PdfFileWriter() 类。我已尝试提供以下内容,以尽可能接近您的内容:

 from PyPDF2 import PdfFileMerger, PdfFileReader

[...]

merger = PdfFileMerger()
for filename in filenames:
    merger.append(PdfFileReader(file(filename, 'rb')))

merger.write("document-output.pdf")

长答案

您使用 PdfFileReaderPdfFileWriter 的方式是保持每个文件打开,并最终导致 Python 生成 IOError 24。更具体地说,当您将页面添加到 PdfFileWriter ,您正在添加对打开页面的引用 PdfFileReader (因此,如果您关闭文件,则会出现 IO 错误)。 Python 检测到该文件仍被引用,并且尽管重新使用了文件句柄,但不执行任何垃圾收集/自动文件关闭。它们保持打开状态,直到 PdfFileWriter 不再需要访问它们,它位于 output.write(outputStream) 在您的代码中。

要解决此问题,请在内存中创建内容副本,并允许关闭文件。我在 PyPDF2 代码的冒险中注意到 PdfFileMerger() 类已经具有此功能,因此我没有重新发明轮子,而是选择使用它。不过,我了解到,我最初对 PdfFileMerger 的了解还不够接近,它只 在特定条件下 创建了副本。

我最初的尝试如下所示,并导致了相同的 IO 问题:

 merger = PdfFileMerger()
for filename in filenames:
    merger.append(filename)

merger.write(output_file_path)

查看 PyPDF2 源代码,我们看到 append() 需要 fileobj 被传递,然后使用 merge() 作为新页面传递的最后一个函数–文件位置。 merge() 执行以下操作 fileobj (在使用 PdfFileReader(fileobj) 打开它之前:

     if type(fileobj) in (str, unicode):
        fileobj = file(fileobj, 'rb')
        my_file = True
    elif type(fileobj) == file:
        fileobj.seek(0)
        filecontent = fileobj.read()
        fileobj = StringIO(filecontent)
        my_file = True
    elif type(fileobj) == PdfFileReader:
        orig_tell = fileobj.stream.tell()
        fileobj.stream.seek(0)
        filecontent = StringIO(fileobj.stream.read())
        fileobj.stream.seek(orig_tell)
        fileobj = filecontent
        my_file = True

我们可以看到 append() 选项确实接受一个字符串,并且在这样做时,假定它是一个文件路径并在该位置创建一个文件对象。最终结果与我们试图避免的完全相同。一个 PdfFileReader() 对象保持打开文件直到文件最终被写入!

但是,如果我们创建文件路径字符串的文件对象 PdfFileReader_(请参阅编辑 2)_路径字符串对象, 然后再将 其传递到 append() ,它将自动创建作为 StringIO 对象的副本,允许 Python 关闭文件。

我会推荐更简单的 merger.append(file(filename, 'rb')) ,因为其他人报告说 PdfFileReader 对象可能在内存中保持打开状态,即使在调用 writer.close()

希望这有帮助!

编辑: 我假设你使用的是 PyPDF2 ,而不是 PyPDF 。如果你不是,我强烈建议切换,因为 PyPDF 不再维护,作者在开发 PyPDF2 时给予 Phaseit 官方祝福。

如果出于某种原因您不能切换到 PyPDF2(许可、系统限制等), PdfFileMerger 将不可用。在这种情况下,您可以重新使用 PyPDF2 的 merge 函数(上面提供)中的代码来创建文件的副本作为 StringIO 对象,并在您的代码中使用它文件对象的。

编辑 2: 以前的使用建议 merger.append(PdfFileReader(file(filename, 'rb'))) 根据评论更改 (感谢@Agostino)

原文由 Rejected 发布,翻译遵循 CC BY-SA 3.0 许可协议

pdfrw 包一次读取每个文件,因此不会遇到打开文件过多的问题。 是一个示例串联脚本。

相关部分——假设 inputs 是输入文件名列表,而 outfn 是输出文件名:

 from pdfrw import PdfReader, PdfWriter

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

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

原文由 Patrick Maupin 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏