Python, PyPDF2でPDFを結合・分割(ファイル全体・個別ページ)
PythonのサードパーティライブラリPyPDF2を使うと、複数のPDFファイル全体を結合したりページを抽出して結合したり、PDFファイルをページごとに複数のファイルに分割したりすることができる。
ここでは以下の項目について説明する。
- PyPDF2のインストール
- 複数のPDFファイルの結合
- 単純に連結
- 途中に挿入
- ページを抽出して結合
- タプルでページ指定
PageRange
オブジェクトでページ指定PdfFileReader
とPdfFileWriter
を使用
- 指定したページで分割
- メタデータの設定
- パスワードが設定されたPDFファイルの処理
- 応用例: ディレクトリ内のPDFファイルをすべて結合
- 応用例: 1ページごとに個別ファイルに分割
サンプルで使用しているPDFファイルは以下のリンクから。暗号化されているファイルのパスワードはすべてpassword
。
すべてのPDFファイルに対して動作を保証するものではない。
PyPDF2のインストール
PyPDF2は外部ライブラリに依存していない。pip
(pip3
)やconda
でインストール可能。
$ pip install PyPDF2
以下のサンプルコードで使用しているPyPDF2のバージョンは1.26.0
。
クラスやメソッドなどの詳細は公式ドキュメントを参照。
IssueやPull Requestが溜まっており活発に開発されているという状況ではないが、シンプルなPDFファイルの処理であれば問題ない。
複数のPDFファイルの結合
単純に連結
複数のPDFファイル全体(全ページ)を単純に順番に連結する場合は、
PdfFileMerger
クラスのオブジェクトを生成append()
メソッドでファイルを追加write()
メソッドで書き出しclose()
で閉じる
という流れ。PdfFileMerger
クラスについての公式ドキュメントは以下。
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
で指定したページは含まれないので注意。
- 関連記事: Pythonのrange関数の使い方
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
で場合分けしている。
ここでは該当ディレクトリ直下のPDFファイルを選択しているが、glob()
の引数によって抽出するファイルの制御が可能。glob()
についての詳細は上記の関連記事を参照。
応用例: 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()
メソッドでファイル名に連番の番号をつけている。ゼロ埋めしたりすることも可能。以下の記事を参照。