PyMuPDF: PythonでPDFや多様なドキュメントを自在に操るための詳細ガイド 🐍📄

プログラミング

PyMuPDFは、Pythonプログラミング言語で使用できる非常に強力なライブラリです。このライブラリの真価は、PDFファイルだけでなく、XPS、EPUB、さらには画像ファイルなど、多岐にわたるドキュメント形式を扱える点にあります。PyMuPDFは、もともとC言語で書かれた高性能なレンダリングエンジン「MuPDF」をPythonから簡単に利用できるようにしたものです(「Pythonバインディング」と呼ばれます)。これにより、Pythonの書きやすさと、MuPDFの高速な処理能力の両方の恩恵を受けることができます。😊

このライブラリは、Artifex Software社によって開発・維持されており、オープンソース(AGPLライセンス)と商用ライセンスの両方で提供されています。特に、ドキュメントからのデータ抽出、分析、変換、そして編集といった多様な操作を、高いパフォーマンスで実行できることが大きな特徴です。

少し歴史的な話をすると、PyMuPDFをインポートする際にはimport fitzという名前が使われることがあります。これは、MuPDFの初期のグラフィックライブラリ名「Fitz」に由来する名残です。最近のバージョンではimport pymupdfが推奨されていますが、後方互換性のためにfitzも利用可能です。

このブログ記事では、PyMuPDFの基本的な概念から、具体的な使い方、そして応用的な機能まで、幅広く解説していきます。PDF操作の自動化や、ドキュメントからの情報抽出など、あなたのプロジェクトに役立つ情報がきっと見つかるはずです。🚀

PyMuPDFの主な特徴 ✨

PyMuPDFが多くの開発者に選ばれる理由は、その多機能性と高性能さにあります。以下に主な特徴を挙げます。
  • 多様なファイル形式のサポート: PDFはもちろん、XPS、OpenXPS、CBZ (コミックブック形式)、CBR、FB2、EPUBといった電子書籍フォーマットにも対応しています。これにより、さまざまな種類のドキュメントを統一されたAPIで扱うことができます。
  • 高速な処理: C言語ベースのMuPDFエンジンを利用しているため、ドキュメントのレンダリング(表示)、テキストや画像の抽出、その他の操作が非常に高速です。大規模なファイルや大量のドキュメント処理にも適しています。
  • 豊富な機能:
    • テキスト抽出: プレーンテキストだけでなく、HTML、JSON、XML形式での抽出が可能。文字の位置、フォント情報(サイズ、色、名前)、書き込み方向などの詳細情報も取得できます。
    • 画像抽出: ドキュメント内の画像を抽出し、PNGやJPEGなどの形式で保存できます。
    • PDFの作成と編集: 新しいPDFの作成、ページの追加・削除・結合・回転、テキストや画像の挿入、注釈(ハイライト、コメントなど)の追加・編集・削除が可能です。
    • メタデータの操作: ドキュメントのタイトル、作成者、作成日などのメタデータを読み取り、編集することができます。XMLメタデータへのアクセスも可能です。
    • 目次(ブックマーク)の操作: ドキュメントの目次情報をリストとして取得し、編集することもできます。
    • レンダリング: ページを画像(PNG、PPMなど)やSVG(ベクター形式)に変換できます。ズーム、回転、クリッピング(切り抜き)などのオプションも指定可能です。
    • テキスト検索: 特定のテキスト文字列をページ内で検索し、その位置を取得できます。
    • OCR統合: Tesseract OCRエンジンと連携し、画像内のテキストやスキャンされたPDFの文字認識を行う機能が組み込まれています。(Tesseractの別途インストールが必要な場合があります)
  • クロスプラットフォーム: Windows, macOS, Linuxなど、主要なオペレーティングシステムで動作します。
  • ライセンス: オープンソースのAGPLライセンスと、商用利用向けのライセンスが用意されています。
  • 拡張パッケージ:
    • PyMuPDF4LLM: 大規模言語モデル(LLM)やRAG(Retrieval-Augmented Generation)向けに、抽出データをMarkdown形式などで出力する機能が強化されたバージョンです。(2024年頃から注目されています)
    • PyMuPDF Pro: 商用向けの拡張ライブラリで、Microsoft Officeファイル(Word, Excel, PowerPointなど)への対応や、より高度なテーブル抽出機能などが含まれます。
これらの特徴により、PyMuPDFはドキュメント処理に関する幅広いニーズに応えることができる強力なツールとなっています。

インストール方法 💻

PyMuPDFのインストールは非常に簡単です。Pythonのパッケージ管理ツールであるpipを使用します。Python 3.9以降が必要です(2025年初頭時点)。

ターミナルまたはコマンドプロンプトを開き、以下のコマンドを実行してください。

pip install --upgrade pymupdf

通常、これにより、お使いのOSとPythonバージョンに対応した「ホイール(wheel)」ファイルがダウンロードされ、スムーズにインストールが完了します。

仮想環境の利用推奨: プロジェクトごとにライブラリのバージョンを管理するために、Pythonの仮想環境(例: `venv`)内でインストールすることが強く推奨されます。

# Windowsの場合
python -m venv myenv
.\myenv\Scripts\activate
python -m pip install --upgrade pip
pip install pymupdf

# macOS / Linux の場合
python3 -m venv myenv
source myenv/bin/activate
python -m pip install --upgrade pip
pip install pymupdf

インストール時の注意点

  • ホイールが見つからない場合: もし適切なホイールファイルがPyPI(Python Package Index)に存在しない場合、pipはソースコード(sdist)からライブラリをビルドしようとします。この場合、C/C++のコンパイル環境が必要になります。
    • Windows: Visual Studio (特に2019が推奨されることがあります) が必要になる場合があります。
    • macOS: Xcode Command Line Tools が必要です。
    • Linux: `build-essential` や `python3-dev` などの開発ツールが必要です。
    通常、これらのツールがインストールされていれば、pipは自動的にMuPDFのソースコードをダウンロードし、ビルド・インストールを行います。
  • WindowsでのDLLエラー: インストール後、import pymupdf または import fitz を実行した際に ImportError: DLL load failed while importing _fitz のようなエラーが出ることがあります。これは、Microsoft Visual C++ 再頒布可能パッケージ (MSVCP140.dllなど) が見つからないか、バージョンが古い場合に発生することがあります。最新の再頒布可能パッケージをMicrosoftの公式サイトからダウンロードしてインストールすることで解決する場合があります。
  • 依存関係: PyMuPDF自体に必須の外部依存ライブラリはありませんが、フォントサブセット作成(fontToolsが必要)、OCR機能(Tesseract-OCRが必要)、特定のフォント利用(pymupdf-fonts)など、一部の機能は追加パッケージのインストールによって有効になります。これらの追加パッケージはPyMuPDFのインストール前後どちらでもインストール可能です。

PyMuPDFの基本的な操作を見ていきましょう。ここでは、PDFファイルを開き、ページ数を取得し、テキストや画像を抽出する簡単な例を示します。

ライブラリのインポート

まず、ライブラリをインポートします。前述の通り、pymupdf または fitz の名前でインポートできます。ここではfitzを使用します。

import fitz  # PyMuPDF ライブラリをインポート (pymupdf でも可)
import os

PDFファイルを開く

fitz.open() 関数を使ってPDFファイルを開きます。引数にファイルパスを指定します。ファイルパスを指定しない場合は、新しい空のPDFドキュメントがメモリ上に作成されます。

# サンプルPDFファイルへのパス (適宜変更してください)
pdf_path = "example.pdf"

# PDFファイルが存在するか確認
if not os.path.exists(pdf_path):
    print(f"エラー: ファイルが見つかりません - {pdf_path}")
    # ここで処理を中断するか、サンプルPDFを作成するなどの対応が必要
    # 例として、ここでは空のPDFを作成してみます
    doc = fitz.open() # 新しい空のPDFを作成
    page = doc.new_page()
    page.insert_text((50, 72), "This is a sample PDF.")
    doc.save("example.pdf")
    print("サンプルPDF 'example.pdf' を作成しました。")
    doc.close()
    doc = fitz.open(pdf_path) # 再度開く
else:
    try:
        doc = fitz.open(pdf_path)
        print(f"'{pdf_path}' を開きました。")
    except Exception as e:
        print(f"ファイルを開けませんでした: {e}")
        # エラー処理
        exit() # 例: プログラム終了

# メモリ上のバイトデータから開く場合
# with open(pdf_path, "rb") as f:
#     pdf_data = f.read()
# doc_mem = fitz.open(stream=pdf_data, filetype="pdf")

fitz.open()Document オブジェクトを返します。このオブジェクトを通してドキュメントに対する様々な操作を行います。

ドキュメント情報の取得

開いたドキュメントの基本的な情報を取得できます。

# ページ数を取得
page_count = doc.page_count
print(f"ページ数: {page_count}")

# メタデータ(タイトル、作成者など)を取得
metadata = doc.metadata
print("メタデータ:")
if metadata:
    for key, value in metadata.items():
        print(f"  {key}: {value}")
else:
    print("  メタデータはありません。")

# 目次(ブックマーク)を取得
toc = doc.get_toc()
print("目次:")
if toc:
    for item in toc:
        level, title, page_num = item[:3] # 基本的な情報を取得
        print(f"  {'  ' * (level - 1)}[{page_num}] {title}")
else:
    print("  目次はありません。")

get_toc() はリストのリストを返します。各内部リストは [階層レベル, タイトル, ページ番号, その他の情報...] という形式です。

ページの操作とテキスト抽出

特定のページにアクセスし、そこからテキストを抽出します。ページは0から始まるインデックスで指定します。

if page_count > 0:
    # 最初のページを取得 (インデックスは0)
    # page = doc.load_page(0)
    page = doc[0] # この形式でも取得可能

    # ページからテキストを抽出 (プレーンテキスト)
    text = page.get_text()
    print("\n最初のページのテキスト (一部):")
    print(text[:500] + "...") # 長い場合に備えて最初の500文字だけ表示

    # 他の形式でテキストを抽出
    # html_text = page.get_text("html")
    # json_text = page.get_text("json")
    # dict_text = page.get_text("dict") # 詳細情報を含む辞書
else:
    print("ドキュメントにページがありません。")

get_text() メソッドは、引数によって様々な形式(”plain”, “html”, “xml”, “json”, “dict”, “words”, “blocks”)でテキストを抽出できます。デフォルトは “plain” です。”dict” や “json” を使うと、文字の位置、フォント、色などの詳細な情報を取得できます。

画像の抽出

ページに含まれる画像を抽出することも簡単です。

if page_count > 0:
    page = doc[0] # 例として最初のページを使用

    # ページ内の画像情報を取得
    image_list = page.get_images(full=True)

    if image_list:
        print(f"\n最初のページには {len(image_list)} 個の画像が見つかりました。")
        # 最初の画像を抽出して保存してみる
        img_info = image_list[0]
        xref = img_info[0] # 画像の内部参照番号
        base_image = doc.extract_image(xref)
        image_bytes = base_image["image"] # 画像データ (bytes)
        image_ext = base_image["ext"] # 画像の拡張子 (例: "png", "jpeg")

        # 画像をファイルに保存
        output_image_path = f"extracted_image_p1_i1.{image_ext}"
        with open(output_image_path, "wb") as img_file:
            img_file.write(image_bytes)
        print(f"画像を '{output_image_path}' として保存しました。")
    else:
        print("\n最初のページに画像は見つかりませんでした。")

    # ドキュメント全体の画像を抽出する別の方法 (ページに表示されていなくても抽出可能)
    # for img_index, img in enumerate(doc.get_page_images(0)): # 最初のページの場合
    #     xref = img[0]
    #     ... (上記と同様の処理) ...

page.get_images(full=True) は、そのページで参照されている画像のリストを返します。doc.extract_image(xref) を使うと、画像のバイナリデータや拡張子などを取得できます。

ドキュメントを閉じる

操作が終わったら、ドキュメントを閉じることが推奨されます。

doc.close()
print("\nドキュメントを閉じました。")

または、with構文を使うと自動的に閉じられます。

try:
    with fitz.open(pdf_path) as doc:
        print(f"\n再度 '{pdf_path}' を開きました (with構文)。")
        page_count = doc.page_count
        print(f"ページ数: {page_count}")
        # ... ここで他の操作 ...
    print("ドキュメントは自動的に閉じられました。")
except Exception as e:
    print(f"ファイルを開けませんでした: {e}")

高度な機能と応用例 🛠️

PyMuPDFは基本的な操作に加えて、さらに高度な機能も提供しています。ここではいくつかの例を紹介します。

PDFの変更と保存

既存のPDFに変更を加えたり、新しいPDFを作成したりできます。

try:
    with fitz.open("example.pdf") as doc: # 既存のファイルを開く (なければ作成される)
        if doc.page_count > 0:
            page = doc[0] # 最初のページ

            # ページにテキストを追加
            text_to_add = "これはPyMuPDFによって追加されたテキストです!"
            insert_pos = fitz.Point(50, 100) # 挿入位置 (左下の座標)
            page.insert_text(insert_pos, text_to_add, fontsize=12, color=(1, 0, 0)) # 赤色で追加

            # ページに図形を描画 (矩形)
            rect_to_draw = fitz.Rect(50, 120, 250, 150) # 矩形の座標 (左上、右下)
            page.draw_rect(rect_to_draw, color=(0, 0, 1), fill=(0.8, 0.8, 1), width=1.5, dashes="[3 2]") # 青枠、薄い青で塗りつぶし、破線

            # 注釈(ハイライト)を追加
            highlight_rect = page.search_for("sample")[0] # "sample" というテキストを探す
            if highlight_rect:
                highlight = page.add_highlight_annot(highlight_rect)
                highlight.set_colors(stroke=(1, 1, 0)) # ハイライト色を黄色に
                highlight.update() # 変更を適用

            # 変更を保存 (新しいファイルとして)
            output_pdf_path = "example_modified.pdf"
            doc.save(output_pdf_path, garbage=4, deflate=True) # garbage=4で不要なオブジェクトを削除, deflateで圧縮
            print(f"変更を加えたPDFを '{output_pdf_path}' として保存しました。")

            # 上書き保存する場合 (注意: 元のファイルが変更されます)
            # doc.saveIncr()
            # print("変更を元のファイルに上書き保存しました。")
        else:
            print("ページがないため変更できませんでした。")

except Exception as e:
    print(f"PDFの変更中にエラーが発生しました: {e}")

save()メソッドには、ガベージコレクションのレベル(garbage)、圧縮(deflate)、線形化(linear)などのオプションを指定でき、ファイルサイズや読み込み速度を最適化できます。

ページの結合と分割

複数のPDFを結合したり、一つのPDFを複数のページに分割したりすることも可能です。

# PDF結合の例
pdf1_path = "example.pdf"
pdf2_path = "example_modified.pdf" # 先ほど作成したファイル
output_merged_path = "merged_document.pdf"

try:
    # 新しい空のPDFを作成
    merged_doc = fitz.open()

    # 結合したいPDFを開く
    doc1 = fitz.open(pdf1_path)
    doc2 = fitz.open(pdf2_path)

    # 最初のPDFのページを挿入
    merged_doc.insert_pdf(doc1)

    # 二番目のPDFのページを挿入
    merged_doc.insert_pdf(doc2)

    # 結合したPDFを保存
    merged_doc.save(output_merged_path)
    print(f"'{pdf1_path}' と '{pdf2_path}' を結合し、'{output_merged_path}' として保存しました。")

    # ドキュメントを閉じる
    doc1.close()
    doc2.close()
    merged_doc.close()

except Exception as e:
    print(f"PDFの結合中にエラーが発生しました: {e}")

# PDF分割の例 (各ページを個別のPDFとして保存)
input_split_path = "merged_document.pdf"

try:
    with fitz.open(input_split_path) as doc:
        print(f"'{input_split_path}' を分割します...")
        for i in range(doc.page_count):
            # 新しい単一ページのPDFを作成
            single_page_doc = fitz.open()
            single_page_doc.insert_pdf(doc, from_page=i, to_page=i)

            # 個別のファイルとして保存
            output_split_path = f"split_page_{i + 1}.pdf"
            single_page_doc.save(output_split_path)
            single_page_doc.close()
        print(f"{doc.page_count} 個のファイルに分割しました。")

except Exception as e:
    print(f"PDFの分割中にエラーが発生しました: {e}")

insert_pdf() メソッドを使うと、他のPDFドキュメントのページを簡単に挿入できます。ページの範囲指定や回転なども可能です。

テキスト検索とハイライト

特定のキーワードを検索し、その場所(矩形座標)を取得したり、ハイライトしたりできます。

search_term = "PyMuPDF"
highlight_output_path = "highlighted_search.pdf"

try:
    with fitz.open("example_modified.pdf") as doc:
        found_count = 0
        for page_num, page in enumerate(doc):
            # ページ内でテキストを検索
            found_rects = page.search_for(search_term) # Rectオブジェクトのリストを返す

            if found_rects:
                print(f"ページ {page_num + 1} で '{search_term}' が {len(found_rects)} 回見つかりました。")
                found_count += len(found_rects)
                # 見つかった箇所すべてにハイライト注釈を追加
                for rect in found_rects:
                    highlight = page.add_highlight_annot(rect)
                    highlight.update() # 注釈を確定

        if found_count > 0:
            doc.save(highlight_output_path)
            print(f"検索結果をハイライトしたPDFを '{highlight_output_path}' として保存しました。")
        else:
            print(f"'{search_term}' はドキュメント内で見つかりませんでした。")

except Exception as e:
    print(f"テキスト検索とハイライト中にエラーが発生しました: {e}")

ページを画像としてレンダリング

PDFのページを画像ファイル(PNGなど)として保存できます。解像度やクリッピング領域も指定可能です。

render_output_path = "rendered_page_1.png"
dpi = 300 # 解像度 (dots per inch)

try:
    with fitz.open("example_modified.pdf") as doc:
        if doc.page_count > 0:
            page = doc[0] # 最初のページ

            # ページをPixmapにレンダリング
            # matrix = fitz.Matrix(dpi / 72, dpi / 72) # スケーリング行列 (デフォルトは72dpi)
            pix = page.get_pixmap(dpi=dpi)

            # PixmapをPNGファイルとして保存
            pix.save(render_output_path)
            print(f"最初のページを解像度 {dpi} dpi で '{render_output_path}' として保存しました。")

            # SVG (ベクター形式) で保存する場合
            # svg_output_path = "rendered_page_1.svg"
            # svg_text = page.get_svg_image()
            # with open(svg_output_path, "w") as f:
            #     f.write(svg_text)
            # print(f"最初のページを '{svg_output_path}' として保存しました。")
        else:
            print("レンダリングするページがありません。")

except Exception as e:
    print(f"ページのレンダリング中にエラーが発生しました: {e}")

ユースケースと活用例 💡

PyMuPDFの多機能性と高速性により、様々な分野で活用されています。

  • データ抽出と分析:
    • 請求書、レポート、契約書などのPDFから特定の情報(日付、金額、項目名など)を自動抽出。
    • 論文や技術文書からテキストや図表を抽出し、データベース化や分析に利用。
    • 大量のPDFドキュメントからキーワードを検索し、関連情報を収集。
  • ドキュメント処理の自動化:
    • 複数のレポートPDFを結合して一つのファイルにまとめる。
    • 大規模なマニュアルPDFを章ごとに分割する。
    • 定型的なフォームPDFにデータを自動入力して保存する。
    • スキャンされた書類(画像PDF)にOCRを適用してテキスト検索可能にする。
    • 特定のページやセクションを削除または墨消し(リダクション)する。
  • Webアプリケーションやサービス:
    • ユーザーがアップロードしたPDFの内容を表示・検索する機能。
    • 動的に生成されたデータを元にPDFレポートを作成する機能。
    • PDFをWebページ上でプレビューするための画像変換。
  • 自然言語処理(NLP)と機械学習:
    • PDFからクリーンなテキストデータを抽出し、LLM(大規模言語モデル)の学習データやRAGシステムの前処理に使用。(PyMuPDF4LLMが特に有効)
    • ドキュメント内のテーブルデータを抽出し、構造化データとして利用。
  • デジタルアーカイブと管理:
    • 書類をスキャンしたPDFのメタデータを編集・整理。
    • 古いフォーマットのドキュメントをPDF/A(長期保存用フォーマット)に変換。

例えば、2023年頃からは、LLMを活用したシステム(RAGなど)が注目されており、その前処理段階でPDFから高品質なテキストや構造化データを抽出するためにPyMuPDF(特にPyMuPDF4LLM)が活用されるケースが増えています。

パフォーマンスと考慮事項 🤔

PyMuPDFはそのパフォーマンスの高さで知られていますが、いくつかの点を考慮するとより効果的に利用できます。

  • パフォーマンス: C言語ベースのMuPDFを利用しているため、多くの純粋なPython製PDFライブラリと比較して、特にレンダリングや大規模ファイルの処理において高速です。公式ドキュメントには他のライブラリとの性能比較結果も掲載されており、その優位性が示されています。
  • メモリ使用量: 大規模なPDFや多数のPDFを一度に処理する場合、メモリ使用量に注意が必要です。特にページを画像にレンダリングする際は、解像度(DPI)が高いほど多くのメモリを消費します。必要に応じて、ファイルを分割処理したり、garbageオプションを使ってメモリを解放したりする工夫が有効です。
  • テキスト抽出の品質: PDFの構造は複雑で、テキストの抽出順序が期待通りにならない、文字化けが発生する、画像内の文字が抽出できないといった問題が発生することがあります。PyMuPDFは様々な抽出モード(`plain`, `blocks`, `words`, `html`など)を提供しており、文書のレイアウトに応じて適切なモードを選択することが重要です。また、OCR機能の併用も有効な場合があります。
  • 複雑なPDFの扱い: フォームフィールド、JavaScript、特殊な注釈、暗号化などが施された複雑なPDFの場合、一部の機能が制限されたり、期待通りに動作しなかったりする可能性があります。
  • ライセンス: AGPLライセンスは、このライブラリを組み込んだソフトウェアをネットワーク経由で提供する場合などに、ソースコードの公開義務が生じる可能性があります。商用製品やサービスでの利用を検討する場合は、ライセンス条件をよく確認し、必要に応じて商用ライセンスの取得を検討してください。
  • バージョン: PyMuPDFおよび基盤となるMuPDFは活発に開発されており、頻繁にアップデートが行われています。最新バージョンでは新機能の追加やバグ修正が行われている一方、稀にAPIの変更が含まれる可能性もあります。利用するバージョンを固定するか、アップデート時に変更点を確認することが推奨されます。現在の最新安定バージョンはPyPIやGitHubで確認できます(2025年3月時点では1.25.x系統がリリースされています)。

まとめ 🏁

PyMuPDFは、PythonでPDFやその他の多様なドキュメント形式を扱うための、非常に強力で高速、かつ多機能なライブラリです。テキストや画像の抽出、ドキュメントの編集・作成、ページの操作、レンダリングなど、幅広いタスクに対応できます。

インストールも簡単で、基本的な使い方も直感的ですが、高度な機能やオプションも豊富に用意されており、複雑な要件にも応えることができます。特にパフォーマンスが要求される場面や、LLM連携など最新の技術トレンドにおいても、その価値を発揮します。

このガイドが、PyMuPDFを使い始めるきっかけとなり、あなたのプロジェクトにおけるドキュメント処理の効率化に貢献できれば幸いです。ぜひ、公式ドキュメントも参照しながら、PyMuPDFの可能性を探求してみてください! 🎉

コメント

タイトルとURLをコピーしました