Excel・文書業務

pypdf で PDF を結合し、ページ範囲を抽出する

複数の PDF を1つのファイルに結合し、指定した両端を含むページ範囲を2つ目のファイルとして抽出し、結果のページ数と順序を確認します。意図的に寸法を変えた空白ページの合成セットを使うため、結果を手作業でも確認できます。

目次を表示

この翻訳はAIで作成しました。コード、単位、数値は原文と併せて確認してください。各言語のネイティブ話者による校閲は、まだ完了していません。 English

対象読者元の文書を変更せずに PDF を結合したり、選択したページを抽出したりしたい人向けのガイドです。

準備するもの
  • Python 3.12 と、そのバージョンを起動できるターミナルコマンド。
  • python -m pip install pypdf で pypdf をインストール済みであること。
  • スクリプトが outputs の下にフォルダを作成できる作業フォルダ。
  • 元 PDF、結合 PDF、抽出 PDF を保存できる十分なディスク容量。

01結合と抽出のルールを定義する

この workflow では、3つの source PDFs を明示した順序で使用します: alpha.pdf, beta.pdf, gamma.pdf。それぞれの pages を1つの merged PDF に追加します。その後、通常の人間向けページ番号で最初のページを page 1 として、merged PDF の pages 2 through 5 を2つ目の output に抽出します。

Python sequences は zero-based indexes を使用しますが、この script では START_PAGE と END_PAGE を one-based inclusive page numbers として受け取ります。したがって pages 2 through 5 は Python slice positions 1 through 4 に対応し、merged_reader.pages[START_PAGE - 1:END_PAGE] として実装します。

023つの合成 PDF を作成する

以下の PDFs は合成データであり、この記事のために作成したものです。各 page は空白ですが、すべての page で width と height が異なります。これにより、別の PDF-generation package を使わなくても page order を独立して確認できます。

python
from pathlib import Path
from pypdf import PdfWriter

SOURCE_DIR = Path("outputs") / "pdf_merge_split_demo"
PDFS = {
    "alpha.pdf": [(100, 200), (110, 210)],
    "beta.pdf": [(120, 220)],
    "gamma.pdf": [(130, 230), (140, 240), (150, 250)],
}

SOURCE_DIR.parent.mkdir(parents=True, exist_ok=True)
SOURCE_DIR.mkdir()  # Stop if the synthetic source already exists.

for filename, page_sizes in PDFS.items():
    writer = PdfWriter()
    for width, height in page_sizes:
        writer.add_blank_page(width=width, height=height)
    target = SOURCE_DIR / filename
    with target.open("xb") as stream:
        writer.write(stream)
text
python create_pdf_demo.py
Source PDFPages順番ごとの page dimensions
alpha.pdf2100×200, 110×210
beta.pdf1120×220
gamma.pdf3130×230, 140×240, 150×250

3つの source files には合計 6 pages があります: 2 + 1 + 3。dimensions は PDF user-space units で表しています。ここでの目的は、各合成 page に識別可能な signature を与えることだけです。

03想定されるページ順を確認する

inputs は明示的に列挙しているため、merged PDF には最初に alpha の2 pages、その後 beta の page、最後に gamma の3 pages が入るはずです。したがって merged result は 6 pages になります。

Merged pageSource想定 dimensions
1alpha.pdf page 1100×200
2alpha.pdf page 2110×210
3beta.pdf page 1120×220
4gamma.pdf page 1130×230
5gamma.pdf page 2140×240
6gamma.pdf page 3150×250

したがって merged pages 2 through 5 を抽出すると、110×210, 120×220, 130×230, 140×240 の dimensions をこの順序で持つ正確に 4 pages が作成されるはずです。

04ファイルを結合し、pages 2 through 5 を抽出する

次の script を pdf_merge_split.py として保存してください。各 input file を検証し、この簡単な例では encrypted PDFs を拒否し、すべての pages を結合し、merged output を再度開いて検証してから、指定した page-range file を作成します。

python
from pathlib import Path
from pypdf import PdfReader, PdfWriter

SOURCE_DIR = Path("outputs") / "pdf_merge_split_demo"
INPUTS = ("alpha.pdf", "beta.pdf", "gamma.pdf")
OUTPUT_DIR = Path("outputs") / "pdf_merge_split_result"
MERGED = OUTPUT_DIR / "merged.pdf"
EXTRACTED = OUTPUT_DIR / "pages_2_to_5.pdf"
START_PAGE = 2
END_PAGE = 5


def page_size(page) -> tuple[int, int]:
    width = int(float(page.mediabox.width))
    height = int(float(page.mediabox.height))
    return width, height


def main() -> None:
    source_root = SOURCE_DIR.resolve(strict=True)
    if not source_root.is_dir():
        raise ValueError("SOURCE_DIR must be a directory.")
    if START_PAGE < 1 or END_PAGE < START_PAGE:
        raise ValueError("Invalid page range.")

    input_paths = [source_root / name for name in INPUTS]
    for path in input_paths:
        if not path.is_file():
            raise FileNotFoundError(f"Missing input PDF: {path}")

    OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_DIR.mkdir()  # Refuse to reuse an existing result folder.

    expected_sizes = []
    writer = PdfWriter()
    for path in input_paths:
        reader = PdfReader(path)
        if reader.is_encrypted:
            raise ValueError(f"Encrypted PDF is not supported here: {path.name}")
        for page in reader.pages:
            expected_sizes.append(page_size(page))
            writer.add_page(page)

    with MERGED.open("xb") as stream:
        writer.write(stream)

    merged_reader = PdfReader(MERGED)
    if merged_reader.is_encrypted:
        raise RuntimeError("Unexpected encrypted merged PDF.")
    merged_sizes = [page_size(page) for page in merged_reader.pages]
    if merged_sizes != expected_sizes:
        raise RuntimeError("Merged page count or order does not match the inputs.")

    if END_PAGE > len(merged_reader.pages):
        raise ValueError(
            f"Requested page {END_PAGE}, but merged PDF has only "
            f"{len(merged_reader.pages)} pages."
        )

    selected_pages = merged_reader.pages[START_PAGE - 1:END_PAGE]
    extracted_writer = PdfWriter()
    expected_extract_sizes = []
    for page in selected_pages:
        expected_extract_sizes.append(page_size(page))
        extracted_writer.add_page(page)

    with EXTRACTED.open("xb") as stream:
        extracted_writer.write(stream)

    extracted_reader = PdfReader(EXTRACTED)
    extracted_sizes = [page_size(page) for page in extracted_reader.pages]
    if extracted_sizes != expected_extract_sizes:
        raise RuntimeError("Extracted PDF does not match the selected pages.")

    print(f"Merged {len(INPUTS)} PDFs into {len(merged_reader.pages)} pages.")
    print(
        f"Extracted pages {START_PAGE}-{END_PAGE}: "
        f"{len(extracted_reader.pages)} pages."
    )
    print(f"Merged output: {MERGED.as_posix()}")
    print(f"Extracted output: {EXTRACTED.as_posix()}")


if __name__ == "__main__":
    main()
text
python pdf_merge_split.py

05想定される outputs と比較する

最初の output である merged.pdf には 6 pages が含まれるはずです。2つ目の output である pages_2_to_5.pdf には 4 pages が含まれるはずです。script が人間向け page numbers を対応する Python slice に変換するため、抽出範囲は両端を含みます。

Output想定 pages順番ごとの想定 dimensions
merged.pdf6100×200, 110×210, 120×220, 130×230, 140×240, 150×250
pages_2_to_5.pdf4110×210, 120×220, 130×230, 140×240

以下の想定 console text は、合成 inputs と code から手作業で導出したものです。execution log ではありません。

text
Merged 3 PDFs into 6 pages.
Extracted pages 2-5: 4 pages.
Merged output: outputs/pdf_merge_split_result/merged.pdf
Extracted output: outputs/pdf_merge_split_result/pages_2_to_5.pdf

06実際の PDF を使う前に結果を確認する

  1. merged.pdf を開き、6 pages あることを確認します。
  2. pages_2_to_5.pdf を開き、正確に 4 pages あることを確認します。
  3. page dimensions または別の visible page feature を確認し、抽出ファイル内の順序が 110×210, 120×220, 130×230, 140×240 であることを確認します。
  4. alpha.pdf, beta.pdf, gamma.pdf が source folder に変更されず残っていることを確認します。
  5. OUTPUT_DIR を変更せずに script を再実行します。以前の PDFs を置き換えるのではなく FileExistsError で停止するはずです。

実際の documents では、page content も目視で確認してください。counts が一致するだけでは、想定外の input order や、source PDF の page labels と physical page positions が異なる問題など、すべての問題を検出できません。

07よくあるエラーを確認する

症状確認すること
ModuleNotFoundError: No module named pypdfpython -m pip install pypdf を使い、同じ Python environment に pypdf をインストールします。
FileNotFoundErrorSOURCE_DIR, INPUTS, terminal の working directory を確認します。この例では先に synthetic setup script を実行してください。
FileExistsErrorresult folder がすでに存在します。以前の outputs を確認し、上書きではなく新しい folder を選択します。
Encrypted PDF is not supported here許可された decrypted copy を使用するか、documents に適した明示的な password-handling workflow を追加します。
Requested page is beyond the merged PDFmerged page count と one-based START_PAGE, END_PAGE values を確認します。
Merged or extracted verification mismatch生成 file を unverified として扱います。inputs を確認し、次の実行では新しい output folder を使用します。

OUTPUT_DIR 作成後に exception が発生すると、partial files が残る場合があります。script はそれらを自動削除しません。failed outputs は verified files と分けて保持してください。

08制限を理解する

この workflow が検証するのは physical page count と page dimensions であり、semantic content ではありません。rendered pixels, extracted text, annotations, forms, bookmarks, named destinations, attachments, signatures, accessibility structure は比較しません。

PDF page labels は physical page positions と異なる場合があります。document が roman numerals や custom labels を表示していても、pypdf は page objects を zero-based sequence position で扱います。この tutorial の START_PAGE と END_PAGE は printed page numbers ではなく physical sequence positions を指します。

複雑な PDFs には interactive forms, digital signatures, encrypted content, unusual object relationships など、より慎重な処理が必要な features が含まれる場合があります。digitally signed PDF を新しい document に merge しても、元の signature が新しい file 全体に対する署名として持っていた意味は保持されません。重要な records では、merged copy を original と同等として扱う前に、関連する PDF requirements を確認してください。

実行・検証の記録

2026-09-20 · 手作業で確認した例 · 対象: Python 3.12 · pypdf 必須 · 未実行

  • source page totals 2, 1, 3 を手作業で数え、merged pages が 6 になることを確認しました。
  • 想定 merged dimensions を 100x200, 110x210, 120x220, 130x230, 140x240, 150x250 と手作業で追跡しました。
  • 人間向けの両端を含む page range 2-5 を Python positions 1 through 4 に手作業で変換し、4 pages になることを確認しました。
  • 抽出される dimensions を 110x210, 120x220, 130x230, 140x240 と手作業で導出しました。
  • source PDFs が読み取り用に開かれ、outputs が別 folder に作成され、既存 output folder がある場合に実行が停止することをコードで確認しました。
  • page-count と page-dimension comparison logic を確認し、想定 console output を手作業で導出しました。
検証範囲の限界
  • この回答の作成者はコードを実行しておらず、PDF files の作成、結合、抽出、閲覧はここでは行っていません。
  • encrypted PDFs, forms, annotations, bookmarks, digital signatures, attachments, page labels, malformed PDFs はテストしていません。
  • page-dimension checks は合成例の順序を検証しますが、任意の実 PDF に対する semantic equivalence を証明するものではありません。
  • 公式ドキュメントの URL は既知のドキュメント所在地に基づいて記載していますが、ライブでは確認していません。

サイト全体の執筆・検証方針

参考資料

説明と例は独自に作成しました。関連する動作や概念は、以下の公式資料で確認できます。