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

Modified: | Tags: Python, PDF

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

ここでは以下の内容について説明する。

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

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

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

pypdfのインストール

pypdfは外部ライブラリに依存しておらず、pippip3)でインストール可能。AES方式の復号を利用する場合は下のようにpypdf[crypto]とする。

$ pip install pypdf
$ pip install pypdf[crypto]

以下のサンプルコードで使用しているpypdfのバージョンは3.7.1

以前はPyPDF2という名前だったが、2023年にpypdfに改められた。

複数のPDFファイルの結合

単純に連結

複数のPDFファイル全体(全ページ)を単純に順番に連結する流れは以下の通り。

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

PdfMergerについての公式ドキュメントは以下。

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

import pypdf

print(pypdf.__version__)
# 3.7.1

merger = pypdf.PdfMerger()

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 = pypdf.PdfMerger()

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()

PDFファイルのページを抽出して結合

PdfMergerappend(), merge()メソッドの引数pagesを指定すると、任意のページのみを抽出して結合できる。引数pagesでのページの指定にはタプルかPageRangeオブジェクトを使う。

PdfMergerではなくPdfReaderPdfWriterを使う方法もある。

タプルでページ指定

タプルでページを指定する場合、(start, stop[, step])とする。stepは省略可能だがstopは省略不可。start, stop, stepの考え方はrange()と同じ。stopで指定したページは含まれないので注意。

merger = pypdf.PdfMerger()

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オブジェクトはコンストラクタpypdf.PageRange()にインデックスやスライスを表す文字列を指定して生成する。

merger = pypdf.PdfMerger()

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

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

PdfReaderと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のように複数ページの範囲指定はできないが、単独ページを抽出して結合する場合はPdfReaderPdfWriterのほうが簡単かつ効率的。

PDFファイルを指定したページで分割

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

merger = pypdf.PdfMerger()
merger.append('data/src/pdf/sample1.pdf', pages=pypdf.PageRange(':2'))
merger.write('data/temp/sample_split1.pdf')
merger.close()

merger = pypdf.PdfMerger()
merger.append('data/src/pdf/sample1.pdf', pages=pypdf.PageRange('2:'))
merger.write('data/temp/sample_split2.pdf')
merger.close()

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

メタデータの設定

これまでの例では作成者やタイトルなどのメタデータについては考慮しておらず、生成されたPDFファイルのメタデータは空となる。

PdfMergeradd_metadata()メソッドでメタデータを設定できる。元のファイルのメタデータをコピーしたい場合はPdfReadermetadata属性を使う。

merger = pypdf.PdfMerger()

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

d = pypdf.PdfReader('data/src/pdf/sample1.pdf').metadata
d = {k: d[k] for k in d.keys()}
d['/Title'] = 'merged file'

merger.add_metadata(d)

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

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

任意の項目を指定して追加・変更することも可能。上の例では/Titleを変更している。

メタデータ処理についての詳細は以下の記事を参照。以下の記事の例ではPdfWriteradd_metadata()メソッドを使っているが、PdfMergeradd_metadata()メソッドも使い方は同じ。

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

パスワード付きの暗号化されたPDFファイルの場合、PdfMergerappend(), 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()

    merger = pypdf.PdfMerger()
    for p in l:
        if not pypdf.PdfReader(p).is_encrypted:
            merger.append(p)

    merger.write(dst_path)
    merger.close()

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

ここではsort()メソッドでファイル名順に並べ替えてから結合している。

なお、例としたディレクトリに暗号化されたファイルがあったためis_encrypted属性で場合分けしている。

ここでは該当ディレクトリ直下のPDFファイルを選択しているが、glob()の引数によって抽出するファイルの制御が可能。glob()についての詳細は上記の関連記事を参照。

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

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

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文字列でファイル名に連番をつけている。

関連カテゴリー

関連記事