Python, PyPDF2でPDFを結合・分割(ファイル全体・個別ページ)

PythonのサードパーティライブラリPyPDF2を使うと、複数のPDFファイル全体を結合したりページを抽出して結合したり、PDFファイルをページごとに複数のファイルに分割したりすることができます。

mstamy2/PyPDF2: A utility to read and write PDFs with Python

ここでは以下の項目について説明します。

PyPDF2のインストール 複数のPDFファイルの結合 単純に連結 途中に挿入 ページを抽出して結合 タプルでページ指定 PageRangeオブジェクトでページ指定 PdfFileReaderとPdfFileWriterを使用 指定したページで分割 メタデータの設定 パスワードが設定されたPDFファイルの処理 応用例: ディレクトリ内のPDFファイルをすべて結合 応用例: 1ページごとに個別ファイルに分割

サンプルで使用しているPDFファイルは以下のリンクから。暗号化されているファイルのパスワードはすべてpassword。

python-snippets/notebook/data/src/pdf

すべてのPDFファイルに対して動作を保証するものではない。

PyPDF2のインストール

PyPDF2は外部ライブラリに依存していません。pip(pip3)やcondaでインストールできます。

$ pip install PyPDF2

以下のサンプルコードで使用しているPyPDF2のバージョンは1.26.0。 クラスやメソッドなどの詳細は公式ドキュメントを参照。

PyPDF2 Documentation — PyPDF2 1.26.0 documentation

IssueやPull Requestが溜まっており活発に開発されているという状況ではないが、シンプルなPDFファイルの処理であれば問題ない。

mstamy2/PyPDF2: A utility to read and write PDFs with Python

複数のPDFファイルの結合

単純に連結

複数のPDFファイル全体(全ページ)を単純に順番に連結する場合は、

PdfFileMergerクラスのオブジェクトを生成 append()メソッドでファイルを追加 write()メソッドで書き出し close()で閉じる

という流れになります。PdfFileMergerクラスについての公式ドキュメントは以下。

The PdfFileMerger Class — PyPDF2 1.26.0 documentation

append()メソッドには元のファイルのパス、write()メソッドには出力するファイルのパスを指定します。

import PyPDF2

merger = PyPDF2.PdfFileMerger()

merger.append('data/src/pdf/sample1.pdf')
merger.append('data/src/pdf/sample2.pdf')
merger.append('data/src/pdf/sample3.pdf')

merger.write('data/temp/sample_merge.pdf')
merger.close()

途中に挿入

ファイルの途中に別のファイルを挿入する場合はmerge()メソッドを使います。第一引数positionに挿入するページ番号を0始まりで指定します。

merger = PyPDF2.PdfFileMerger()

merger.append('data/src/pdf/sample1.pdf')
merger.merge(2, 'data/src/pdf/sample2.pdf')
merger.merge(4, 'data/src/pdf/sample3.pdf')

merger.write('data/temp/sample_insert.pdf')
merger.close()

ページを抽出して結合

PdfFileMergerクラスのappend()メソッド、merge()メソッドの引数pagesを指定すると、すべてのページではなく任意のページのみを抽出して結合できます。 引数pagesでのページの指定にはタプルかPageRangeオブジェクトを使います。 また、PdfFileMergerクラスではなくPdfFileReaderクラスとPdfFileWriterクラスを使う方法もあります。

タプルでページ指定

タプルでページを指定する場合、(start, stop[, step])となります。 start, stop, stepの考え方はrange()と同じ。stopで指定したページは含まれないので気をつけてください。

range()と異なり引数を1つにすることはできません。

import PyPDF2

merger = PyPDF2.PdfFileMerger()

merger.append('data/src/pdf/sample1.pdf', pages=(0, 1))
merger.append('data/src/pdf/sample2.pdf', pages=(2, 4))
merger.merge(2, 'data/src/pdf/sample3.pdf', pages=(0, 3, 2))

merger.write('data/temp/sample_merge_page.pdf')
merger.close()

PageRangeオブジェクトでページ指定

PageRangeオブジェクトを使うとインデックスやスライスのようにページを指定できます。

PageRangeオブジェクトはコンストラクタPyPDF2.pagerange.PageRange()にインデックスやスライスを表す文字列を指定して生成します。

merger = PyPDF2.PdfFileMerger()

merger.append('data/src/pdf/sample1.pdf', pages=PyPDF2.pagerange.PageRange('-1'))
merger.append('data/src/pdf/sample2.pdf', pages=PyPDF2.pagerange.PageRange('2:'))
merger.merge(2, 'data/src/pdf/sample3.pdf', pages=PyPDF2.pagerange.PageRange('::-1'))

merger.write('data/temp/sample_merge_pagerange.pdf')
merger.close()

PdfFileReaderとPdfFileWriterを使用

PdfFileReaderクラスのgetPage()メソッドでページを選択し、PdfFileWriterクラスのaddPage()メソッドで追加します。 PdfFileWriterのwrite()メソッドはPdfFileMergerと異なりファイルパスの文字列では指定できないので気をつけてください。open()の第二引数を'wb'として書き込みモードのバイナリファイルとして開いたファイルオブジェクトを指定します。

reader1 = PyPDF2.PdfFileReader('data/src/pdf/sample1.pdf')
reader2 = PyPDF2.PdfFileReader('data/src/pdf/sample2.pdf')

writer = PyPDF2.PdfFileWriter()

writer.addPage(reader1.getPage(0))
writer.addPage(reader2.getPage(2))

with open('data/temp/sample_merge_wr.pdf', 'wb') as f:
    writer.write(f)

上述のタプルやPageRangeのように複数ページの範囲指定はできないが、単独ページを抽出して結合する場合はPdfFileReaderとPdfFileWriterのほうが簡単かつ効率的。

指定したページで分割

ページ分割のためのメソッドは用意されていないが、上述のように任意のページを指定して新たなファイルを生成することは可能なので、結果として複数のファイルに分割して保存できます。

import PyPDF2

merger = PyPDF2.PdfFileMerger()

merger.append('data/src/pdf/sample1.pdf', pages=PyPDF2.pagerange.PageRange(':2'))

merger.write('data/temp/sample_split1.pdf')
merger.close()

merger = PyPDF2.PdfFileMerger()

merger.append('data/src/pdf/sample1.pdf', pages=PyPDF2.pagerange.PageRange('2:'))

merger.write('data/temp/sample_split2.pdf')
merger.close()

1ページごとに個別ファイルに分割する例は後述します。

メタデータの設定

これまでの例では作成者やタイトルなどのメタデータについては考慮しておらず、生成されたPDFファイルのメタデータは空となります。 PdfFileMergerクラスのaddMetadata()メソッドでメタデータを設定できます。 元のファイルのメタデータをコピーしたい場合はPdfFileReaderクラスのdocumentInfo属性を使います。

merger = PyPDF2.PdfFileMerger()

merger.append('data/src/pdf/sample1.pdf')
merger.append('data/src/pdf/sample2.pdf')

d = PyPDF2.PdfFileReader('data/src/pdf/sample1.pdf').documentInfo
d = {k: d[k] for k in d.keys()}
d['/Title'] = 'merged file'

merger.addMetadata(d)

merger.write('data/temp/sample_merge_meta.pdf')
merger.close()

documentInfo属性をそのままaddMetadata()メソッドの引数に指定するとファイルによってはエラーになるため、ここでは辞書内包表記で新たな辞書を生成する(もっといい方法があるかもしれない)。

/Titleのように任意の項目を指定して追加・変更することもできます。 メタデータ処理についての詳細は以下の記事の例はPdfFileWriterクラスのaddMetadata()メソッドだが、PdfFileMergerクラスのaddMetadata()メソッドも使い方は同じ。

パスワードが設定されたPDFファイルの処理

パスワード付きの暗号化されたPDFファイルの場合、PdfFileMergerクラスのappend()メソッド、merge()メソッドでは結合できないため、パスワードを解除したファイルを作成する必要があります。 暗号化アルゴリズムがRC4の場合はPyPDF2を使ってPDFファイルのパスワードを解除できる(バージョン1.26.0時点でAESは未対応)。 PyPDF2を使ったPDFファイルのパスワード処理については

応用例: ディレクトリ内のPDFファイルをすべて結合

標準ライブラリのglobモジュールを使うと、任意のディレクトリ(フォルダ)内のパス一覧のリストを取得できます。

これを使うと以下のようにディレクトリ内のすべてのPDFファイルを一つに結合することができます。

import PyPDF2
import glob
import os

def merge_pdf_in_dir(dir_path, dst_path):
    l = glob.glob(os.path.join(dir_path, '*.pdf'))
    l.sort()

    merger = PyPDF2.PdfFileMerger()
    for p in l:
        if not PyPDF2.PdfFileReader(p).isEncrypted:
            merger.append(p)

    merger.write(dst_path)
    merger.close()

merge_pdf_in_dir('data/src/pdf', 'data/temp/sample_dir.pdf')

リストの順番通りにPDFファイルが結合されるので、ここではsort()メソッドでファイル名順に並べ替えてから結合しています。 なお、例としたディレクトリに暗号化されたファイルがあったためisEncryptedで場合分けしています。

応用例: 1ページごとに個別ファイルに分割

元のPDFファイルを1ページごとに個別ファイルとして保存する場合は、例えば以下のような関数を定義します。PdfFileMergerクラスではなくPdfFileReaderクラスとPdfFileWriterクラスを使っています。

import PyPDF2

def split_pdf_pages(src_path, dst_basepath):
    src_pdf = PyPDF2.PdfFileReader(src_path)
    for i in range(src_pdf.numPages):
        dst_pdf = PyPDF2.PdfFileWriter()
        dst_pdf.addPage(src_pdf.getPage(i))
        with open('{}_{}.pdf'.format(dst_basepath, i), 'wb') as f:
            dst_pdf.write(f)

split_pdf_pages('data/src/pdf/sample1.pdf', 'data/temp/sample1')

ここではformat()メソッドでファイル名に連番の番号をつけています。ゼロ埋めしたりすることもできます。

Last Updated: 6/26/2019, 10:34:03 PM