Python, pypdfでPDFを結合・分割
Pythonのサードパーティライブラリpypdf(旧PyPDF2)を使うと、複数のPDFファイル全体を結合したりページを抽出して結合したり、PDFファイルをページごとに分割したりできる。
サンプルで使用しているPDFファイルは以下。暗号化されているファイルのパスワードはすべてpassword。
すべてのPDFファイルに対して動作を保証するものではない。
pypdfのインストール
pypdfは外部ライブラリに依存しておらず、pip(pip3)でインストール可能。AES方式の暗号化・復号を利用する場合はpypdf[crypto]とする。
$ pip install pypdf
$ pip install pypdf[crypto]
以下のサンプルコードで使用しているpypdfのバージョンは5.5.0。
以前はPyPDF2という名前だったが、2023年にpypdfに改められた。
複数のPDFファイルの結合
単純に連結
複数のPDFファイル全体(全ページ)を単純に順番に連結する手順は以下の通り。
PdfWriterクラスのインスタンスを生成append()メソッドでファイルを追加write()メソッドで書き出し
PdfWriterの詳細は以下の公式ドキュメントを参照。
append()メソッドには元のファイルのパス、write()メソッドには出力するファイルのパスを指定する。
import pypdf
print(pypdf.__version__)
# 5.5.0
writer = pypdf.PdfWriter()
writer.append('data/src/pdf/sample1.pdf')
writer.append('data/src/pdf/sample2.pdf')
writer.append('data/src/pdf/sample3.pdf')
writer.write('data/temp/sample_merge.pdf')
途中に挿入
ファイルの途中に別のファイルを挿入する場合はmerge()メソッドを使う。
第一引数positionに挿入するページ番号を0始まりで指定する。連続挿入時は、直前に挿入された状態を考慮してページ番号を指定する必要がある点に注意。
writer = pypdf.PdfWriter()
writer.append('data/src/pdf/sample1.pdf')
writer.merge(2, 'data/src/pdf/sample2.pdf')
writer.merge(4, 'data/src/pdf/sample3.pdf')
writer.write('data/temp/sample_insert.pdf')
PDFファイルのページを抽出して結合
PdfWriterのappend(), merge()メソッドの引数pagesを指定すると、任意のページのみを抽出して結合できる。引数pagesでのページの指定にはタプルかPageRangeオブジェクトを使う。
PdfReaderでページを取得して追加する方法もある。
タプルでページ指定
タプルでページを指定する場合、(start, stop[, step])とする。stepは省略可能だがstopは省略不可。range(start, stop[, step])に対応するページが抽出される。stopで指定したページは含まれないので注意。
- 関連記事: Pythonのrange関数の使い方
writer = pypdf.PdfWriter()
writer.append('data/src/pdf/sample1.pdf', pages=(0, 1))
writer.append('data/src/pdf/sample2.pdf', pages=(2, 4))
writer.merge(2, 'data/src/pdf/sample3.pdf', pages=(0, 3, 2))
writer.write('data/temp/sample_merge_page.pdf')
PageRangeオブジェクトでページ指定
PageRangeオブジェクトを使うとインデックスやスライスのようにページを指定できる。
PageRangeオブジェクトはコンストラクタpypdf.PageRange()にインデックスやスライスを表す文字列を指定して生成する。
writer = pypdf.PdfWriter()
writer.append('data/src/pdf/sample1.pdf', pages=pypdf.PageRange('-1'))
writer.append('data/src/pdf/sample2.pdf', pages=pypdf.PageRange('2:'))
writer.merge(2, 'data/src/pdf/sample3.pdf', pages=pypdf.PageRange('::-1'))
writer.write('data/temp/sample_merge_pagerange.pdf')
PdfWriterで単ページを取得
PdfReaderオブジェクトのpages[]でページを選択し、PdfWriterオブジェクトのadd_page()メソッドで追加、write()で保存する。
reader1 = pypdf.PdfReader('data/src/pdf/sample1.pdf')
reader2 = pypdf.PdfReader('data/src/pdf/sample2.pdf')
writer = pypdf.PdfWriter()
writer.add_page(reader1.pages[0])
writer.add_page(reader2.pages[2])
writer.write('data/temp/sample_merge_wr.pdf')
pages[]でのページ選択はインデックス指定のみでスライスは不可。上述のタプルやPageRangeのように複数ページの範囲指定はできないが、単独ページを抽出して結合する場合はPdfReaderとPdfWriterのほうが簡単かつ効率的。
PDFファイルを指定したページで分割
ページ分割のためのメソッドは用意されていないが、上述のように任意のページを指定して新たなファイルを生成できるので、結果として複数のファイルに分割して保存可能。
writer = pypdf.PdfWriter()
writer.append('data/src/pdf/sample1.pdf', pages=pypdf.PageRange(':2'))
writer.write('data/temp/sample_split1.pdf')
writer = pypdf.PdfWriter()
writer.append('data/src/pdf/sample1.pdf', pages=pypdf.PageRange('2:'))
writer.write('data/temp/sample_split2.pdf')
1ページごとに個別ファイルに分割する例は後述。
メタデータの設定
これまでの例では作成者やタイトルなどのメタデータについては考慮しておらず、生成されたPDFファイルのメタデータは空となる。
PdfWriterのadd_metadata()メソッドでメタデータを設定できる。元のファイルのメタデータをコピーしたい場合はPdfReaderのmetadata属性を使う。
writer = pypdf.PdfWriter()
writer.append('data/src/pdf/sample1.pdf')
writer.append('data/src/pdf/sample2.pdf')
writer.add_metadata(pypdf.PdfReader('data/src/pdf/sample1.pdf').metadata)
writer.add_metadata({'/Title': 'merged file'})
writer.write('data/temp/sample_merge_meta.pdf')
メタデータの任意の項目を個別に追加・変更することも可能。上の例では/Titleを変更している。メタデータ処理についての詳細は以下の記事を参照。
パスワードが設定されたPDFファイルの処理
パスワード付きの暗号化されたPDFファイルの場合、PdfWriterのappend(), merge()メソッドでは結合できないため、パスワードを解除したファイルを作成する必要がある。
pypdfを使ったPDFファイルのパスワード処理については以下の記事を参照。
応用例: ディレクトリ内のPDFファイルをすべて結合
標準ライブラリのglobモジュールを使うと、任意のディレクトリ(フォルダ)内のパス一覧のリストを取得できる。
これを使うと以下のようにディレクトリ内のすべてのPDFファイルを一つに結合できる。
import glob
import os
def merge_pdf_in_dir(dir_path, dst_path):
l = glob.glob(os.path.join(dir_path, '*.pdf'))
l.sort()
writer = pypdf.PdfWriter()
for p in l:
if not pypdf.PdfReader(p).is_encrypted:
writer.append(p)
writer.write(dst_path)
merge_pdf_in_dir('data/src/pdf', 'data/temp/sample_dir.pdf')
ここではsort()メソッドでファイル名順に並べ替えてから結合している。
なお、例としたディレクトリに暗号化されたファイルがあったためis_encrypted属性で場合分けしている。
ここでは該当ディレクトリ直下のPDFファイルを選択しているが、glob()の引数によって抽出するファイルの制御が可能。glob()についての詳細は上記の関連記事を参照。
応用例: 1ページごとに個別ファイルに分割
元のPDFファイルを1ページごとに個別ファイルとして保存する場合は、例えば以下のような関数を定義する。
def split_pdf_pages(src_path, dst_basepath):
src_pdf = pypdf.PdfReader(src_path)
for i, page in enumerate(src_pdf.pages):
dst_pdf = pypdf.PdfWriter()
dst_pdf.add_page(page)
dst_pdf.write(f'{dst_basepath}_{i}.pdf')
split_pdf_pages('data/src/pdf/sample1.pdf', 'data/temp/sample1')
この例では、enumerate()でページ番号を取得し、f文字列で連番付きのファイル名を生成している。