web-dev-qa-db-ja.com

pypdf複数のpdfファイルを1つのpdfにマージする

1000以上のpdfファイルを1つの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:_

これはバグだと思います。そうでない場合はどうすればいいですか?

28
daydaysay

最近、まったく同じ問題に出くわしたので、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はまだ参照されているファイルを検出し、ファイルハンドルを再利用してもガベージコレクション/自動ファイルクローズを行いません。これらは、コードのoutput.write(outputStream)にあるPdfFileWriterにアクセスする必要がなくなるまで開いたままになります。

これを解決するには、コンテンツのメモリにコピーを作成し、ファイルを閉じます。 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()をお勧めします。

これが役に立てば幸いです!

編集:PyPDFではなく_PyPDF2_を使用していると仮定しました。そうでない場合は、PyPDF2の開発において著者がPhaseitに公式の祝福を与えているため、PyPDFが維持されなくなるため、切り替えを強くお勧めします。

何らかの理由でPyPDF2にスワップできない場合(ライセンス、システム制限など)、PdfFileMergerは利用できません。そのような状況では、PyPDF2のmerge関数(上記で提供)からのコードを再利用して、ファイルのコピーをStringIOオブジェクトとして作成し、それをコードでファイルオブジェクト。

編集2:merger.append(PdfFileReader(file(filename, 'rb')))の使用に関する以前の推奨事項は、コメントに基づいて変更されました(@ Agostinoに感謝)

59
Rejected

Pdfrwパッケージは各ファイルを一度にすべて読み取るため、開いているファイルが多すぎるという問題はありません。 ここ は連結スクリプトの例です。

関連部分-inputsが入力ファイル名のリストであり、outfnが出力ファイル名であると仮定します。

from pdfrw import PdfReader, PdfWriter

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

免責事項:私は主なpdfrw著者です。

3
Patrick Maupin

問題は、常に特定の数のファイルを開くことしか許可されていないことです。これを変更する方法があります( 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()
1
sgillis

多分それは、あなたが多くのファイルを開いているということです。ループでf=file(filename) ... f.close()を明示的に使用するか、withステートメントを使用できます。そのため、開いている各ファイルが適切に閉じられます。

0
flyingfoxlee