33

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

input = PdfFileReader()
output = PdfFileWriter()
filename0000 ----- filename 1000
    input = PdfFileReader(file(filename, "rb"))
    pageCount = input.getNumPages()
    for iPage in range(0, pageCount):
        output.addPage(input.getPage(iPage))
outputStream = file("document-output.pdf", "wb")
output.write(outputStream)
outputStream.close()

执行上述代码,当input = PdfFileReader(file(filename500+, "rb")),

一条错误信息: IOError: [Errno 24] Too many open files:

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

4

5 回答 5

71

我最近遇到了同样的问题,所以我研究了 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")

长答案

您使用PdfFileReader并保持每个文件打开的方式,并最终导致PdfFileWriterPython 生成 IOError 24。更具体地说,当您向PdfFileWriter.PdfFileReader关闭文件时出错)。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)对象,它将自动为我们创建一个副本作为对象,允许 Python 关闭文件.append()StringIO

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

希望这有帮助!

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

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

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

于 2013-06-25T18:10:21.470 回答
3

pdfrw包一次性读取每个文件,不会出现打开文件太多的问题。 是一个示例连接脚本。

相关部分 - 假设inputs是输入文件名列表,并且outfn是输出文件名:

from pdfrw import PdfReader, PdfWriter

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

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

于 2017-04-02T00:00:53.853 回答
2

我写了这段代码来帮助回答:-

import sys
import os
import PyPDF2

merger = PyPDF2.PdfFileMerger()

#get PDFs files and path

path = sys.argv[1]
pdfs = sys.argv[2:]
os.chdir(path)


#iterate among the documents
for pdf in pdfs:
    try:
        #if doc exist then merge
        if os.path.exists(pdf):
            input = PyPDF2.PdfFileReader(open(pdf,'rb'))
            merger.append((input))
        else:
            print(f"problem with file {pdf}")

    except:
            print("cant merge !! sorry")
    else:
            print(f" {pdf} Merged !!! ")

merger.write("Merged_doc.pdf")

在此,我使用了 PyPDF2.PdfFileMerger 和 PyPDF2.PdfFileReader,而不是将文件名显式转换为文件对象

于 2019-11-07T06:00:02.993 回答
1

问题是您只能在任何给定时间打开一定数量的文件。有办法改变这个(http://docs.python.org/3/library/resource.html#resource.getrlimit),但我认为你不需要这个。

您可以尝试关闭 for 循环中的文件:

input = PdfFileReader()
output = PdfFileWriter()
for file in filenames:
   f = open(file, 'rb')
   input = PdfFileReader(f)
   # Some code
   f.close()
于 2013-06-14T09:33:32.197 回答
0

它可能正如它所说的那样,您正在打开许多文件。您可以f=file(filename) ... f.close()在循环中显式使用,或使用with语句。以便每个打开的文件都正确关闭。

于 2013-06-14T09:32:02.163 回答