2013-06-14 8 views
24

Wenn ich 1000+ PDF-Dateien haben müssen in einem pdf verschmolzen werden,pypdf Zusammenführen mehrerer PDF-Dateien in eine 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() 

Führen Sie den obigen Code, wenn input = PdfFileReader(file(filename500+, "rb")),

Eine Fehlermeldung: IOError: [Errno 24] Too many open files:

ich denke, dies ist ein Bug, wenn nicht, was soll ich tun?

Antwort

48

ich vor kurzem über dieses genau das gleiche Problem kam, so grub ich in PyPDF2, um zu sehen, was los ist und wie es zu lösen.

Hinweis: Ich gehe davon aus, dass filename eine wohlgeformte Datei Pfad Zeichenfolge ist. Angenommen, das gleiche für all meinen Code

Die kurze Antwort

Verwenden Sie die PdfFileMerger() Klasse anstelle der PdfFileWriter() Klasse. Ich habe versucht, die folgenden zur Verfügung zu stellen, wie eng Ihre Inhalte ähneln, wie ich kann:

from PyPDF2 import PdfFileMerger, PdfFileReader 

[...] 

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

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

Die lange Antwort

Die Art und Weisen Sie PdfFileReader und PdfFileWriter verwenden wird jede Datei offen zu halten, und schließlich verursacht Python IOError 24 zu generieren. Um genauer zu sein, wenn Sie eine Seite zu PdfFileWriter hinzufügen, fügen Sie Verweise auf die Seite in der geöffneten PdfFileReader (daher der angegebene IO-Fehler, wenn Sie die Datei schließen). Python erkennt die Datei, auf die noch verwiesen wird, und führt keine Speicherbereinigung/automatische Dateischließung durch, obwohl das Dateihandle wiederverwendet wird. Sie bleiben geöffnet, bis PdfFileWriter nicht mehr Zugriff auf sie benötigt, die in Ihrem Code output.write(outputStream) ist.

Um dies zu lösen, erstellen Sie Kopien im Speicher des Inhalts, und ermöglichen Sie die Datei geschlossen werden. Ich habe in meinen Abenteuern durch den PyPDF2-Code festgestellt, dass die Klasse PdfFileMerger() diese Funktionalität bereits besitzt. Anstatt das Rad neu zu erfinden, entschied ich mich dafür, es stattdessen zu verwenden. Ich habe jedoch gelernt, dass mein erster Blick auf PdfFileMerger nicht nahe genug war, und dass es unter bestimmten Bedingungen nur Kopien erstellt.

Meine ersten Versuche sahen wie folgt aus, und wurden in der gleichen IO resultierenden Probleme:

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

merger.write(output_file_path) 

am Code PyPDF2 Quelle Sehen, sehen wir, dass append() erfordert fileobj weitergegeben werden, und verwendet dann die merge() Funktion, übergibt die letzte Seite als neue Dateiposition. merge() führt Folgendes mit fileobj (bevor es mit PdfFileReader(fileobj) Eröffnung:

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 

Wir können sehen, dass die append() Option einen String akzeptiert, und wenn sie dies tun, nimmt es ein Dateipfad ist und erstellt eine Datei Objekt an dieser Stelle Das Endergebnis ist genau dasselbe, was wir zu vermeiden versuchen: Ein PdfFileReader() Objekt, das eine Datei geöffnet hat, bis die Datei schließlich geschrieben wird!

Wenn wir jedoch entweder ein Dateiobjekt der Dateipfadzeichenfolge oder ein PdfFileReader(siehe Edit 2) Objekt des Pfadstrings vor wird es in append() übergeben, es wird automatisch eine Kopie für uns als StringIO Objekt erstellen, so dass Python die Datei schließen kann.

Ich würde die einfachere merger.append(file(filename, 'rb')) empfehlen, wie andere berichtet haben, dass ein PdfFileReader Objekt im Speicher offen bleiben kann, auch nach dem Aufruf writer.close().

Hoffe das half!

EDIT: Ich nahm an, dass Sie PyPDF2, nicht PyPDF verwendeten. Wenn dies nicht der Fall ist, empfehle ich dringend, zu wechseln, da PyPDF nicht mehr vom Autor betreut wird, der Phaseit bei der Entwicklung von PyPDF2 seinen offiziellen Segen gibt.

Wenn Sie aus irgendeinem Grund nicht zu PyPDF2 (Lizenzierung, Systemeinschränkungen usw.) wechseln können, steht Ihnen PdfFileMerger nicht zur Verfügung. In diesem Fall können Sie den Code von PyPDF2s merge-Funktion (wie oben angegeben) erneut verwenden, um eine Kopie der Datei als StringIO-Objekt zu erstellen und diese in Ihrem Code anstelle des Dateiobjekts zu verwenden.

EDIT 2: vorherige Empfehlung von merger.append(PdfFileReader(file(filename, 'rb'))) mit geänderten basierend auf Kommentare (Danke @Agostino).

+0

Ich werde ehrlich sein; Ich habe die lange Antwort nicht gelesen. Die kurze Antwort war großartig. – BeReal82

+1

Ich bemerkte, dass ich einige der angefügten Dateien nicht löschen konnte, indem ich ein intermediäres 'PdfFileReader'-Objekt mit dem Aufruf' writer.append (PdfFileReader (Datei (Dateiname, 'rb')))) erstellte. Sie bleiben auch nach dem Aufruf von 'writer.close()' gesperrt. Der einfachere Aufruf 'merger.append (Datei (Dateiname, 'rb'))' scheint nicht das gleiche Problem zu haben. – Agostino

+1

Würde dies nicht zu einem Speicherproblem führen, wenn die Dateien zu groß sind? – Nishant

0

es ist vielleicht genau das, was es sagt, Sie sind o Pening zu vielen Dateien. Sie können explizit f=file(filename) ... f.close() in der Schleife verwenden oder die with-Anweisung verwenden. Damit wird jede geöffnete Datei ordnungsgemäß geschlossen.

0

Das Problem ist, dass Sie nur eine bestimmte Anzahl von Dateien zu jeder Zeit geöffnet haben dürfen. Es gibt Möglichkeiten, dies zu ändern (http://docs.python.org/3/library/resource.html#resource.getrlimit), aber ich glaube nicht, dass Sie das brauchen.

Was könnten Sie versuchen, um die Dateien in der for-Schleife endet:

input = PdfFileReader() 
output = PdfFileWriter() 
for file in filenames: 
    f = open(file, 'rb') 
    input = PdfFileReader(f) 
    # Some code 
    f.close() 
+2

Bei Verwendung von f.close(), exec output.write (outputStream), Prompt IO-Fehler. – daydaysay

1

Das Paket pdfrw liest jede Datei auf einmal, so dass das Problem von zu vielen geöffneten Dateien nicht auftritt. Here ist ein Beispiel für ein Verkettungsskript.

Der relevante Teil - übernimmt inputs eine Liste der Eingabedateinamen ist, und outfn ist eine Ausgabedateiname:

from pdfrw import PdfReader, PdfWriter 

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

Disclaimer: Ich der primäre pdfrw Autor bin.