【2025年版】PyMuPDF入門:高速PDF処理と日本語テキスト抽出の最前線

なぜPythonでPDFを扱うのか?

開発者の皆さん、日々の業務でPDFファイルと格闘していませんか?請求書、報告書、契約書、あるいは大量の論文データ。これらPDFから必要な情報を手作業で抽出し、集計する作業は、時間と労力を大きく消費します。特に、日本語を含むPDFの場合、文字化けやレイアウト崩れといった問題に直面し、その苦労は倍増することもしばしばです。

「もっと効率的にPDFを扱いたい」「日本語PDFから正確にテキストを抽出したい」――そう願うあなたのために、Pythonは強力な解決策を提供します。しかし、数多あるPDFライブラリの中から、どれを選べば良いのか、そしてどう使いこなせば良いのか、迷ってしまうこともあるでしょう。

本記事は、そんなあなたの悩みを解決するための一歩となるでしょう。PythonのPDF処理ライブラリの中でも、特に高速性日本語テキスト抽出の精度に定評のあるPyMuPDFに焦点を当て、その導入から実践的なテキスト抽出までを徹底解説します。この記事を読み終える頃には、あなたはPyMuPDFを使いこなし、PDFデータ活用の新たな扉を開く自信を手にしているはずです。


対象読者

  • Pythonを使ってPDFの操作を自動化したい開発者
  • 特に、日本語を含むPDFからのテキスト抽出に課題を感じているエンジニア
  • PDF処理のパフォーマンス向上に関心がある方
  • PyMuPDFの基本的な使い方を学びたい方

動作検証環境

この記事で紹介するPythonを使ったPDF動作は、以下の環境で検証しています。

  • OS : macOS Tahoe Version 26.0
  • ハードウェア : MacBook Air 2024 M3 24GB
  • uv : 0.8.22 (ade2bdbd2 2025-09-23)
  • python : 3.13.7
  • PyMuPDF : 1.26.4
    • pymupdf-stubs : 1.26.1.post1
  • ReportLab : 4.4.4
    • charset-normalizer : 3.4.3
    • pillow : 11.3.0

目次

  1. PyMuPDFとは?:特徴とインストール
    • PyMuPDFの主な特徴
    • インストール方法
  2. PyMuPDFの基本操作と高速性
    • PDFファイルのオープンとページアクセス
    • PyMuPDFが高速な理由
  3. 高精度なテキスト抽出
    • ページごとのテキスト抽出
    • 日本語テキスト抽出の優位性
    • テキスト抽出時のオプション(座標情報、ブロック情報)
  4. 実践例:大量のPDFからの情報収集
  5. まとめ:PyMuPDFでPDFデータ活用を加速する
    • 今日からできる、はじめの一歩
    • エンゲージメントの促進
  6. 参考資料
  7. 免責事項

1. PyMuPDFとは?:特徴とインストール

PyMuPDFは、MuPDFという軽量かつ高速なPDFレンダリングライブラリのPythonバインディングです。以前はfitzという別名でも知られimport fitzとして利用されていましたが、バージョン1.24.3以降はimport pymupdfの使用が推奨されています。


PyMuPDFの主な特徴

  • 圧倒的な高速性: C言語で書かれたMuPDFをバックエンドに持つため、Pythonベースの他のライブラリと比較して非常に高速な処理が可能です。大量のPDFファイルを扱う際にその真価を発揮します。
  • 高精度なテキスト抽出: 特に日本語を含む多言語のテキスト抽出において、文字化けが少なく、正確な結果を得やすいのが特徴です。テキストの座標情報やブロック情報も詳細に取得できます。
  • 多機能: テキスト抽出だけでなく、PDFのレンダリング(画像変換)、画像抽出、簡単な編集、注釈操作など、幅広い機能を提供します。
  • 軽量: ライブラリ自体が軽量であり、依存関係も少ないため、導入が容易です。

インストール方法

PyMuPDFのインストールはpipコマンド一つで完了します。

pip install PyMuPDF

2. PyMuPDFの基本操作と高速性

まずは、PyMuPDFを使ってPDFファイルを開き、基本的な情報を取得する方法を見ていきましょう。


PDFファイルのオープンとページアクセス

PDFファイルを開くにはpymupdf.open()を使用します。これはコンテキストマネージャとして使うのが安全です。

import pymupdf

try:
    with pymupdf.open("sample.pdf") as doc:
        if doc.name:
            print(f"ファイル名: {doc.name}")
        else:
            print("ファイル名がありません")
        print(f"ページ数: {doc.page_count}")

        # 最初のページにアクセス
        page = doc[0]
        print(f"最初のページの幅: {page.rect.width}, 高さ: {page.rect.height}")

except FileNotFoundError:
    print("エラー: sample.pdf が見つかりません。既存のPDFファイルを指定してください。")
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")

上記のコードでは、doc.page_countでページ数を取得し、doc[0]で最初のページオブジェクトにアクセスしています。ページオブジェクトからは、そのページのサイズなどの情報を取得できます。


[!NOTE]サンプルPDFファイルを作成するコード(サンプルが用意できない場合に使ってください):
実行したディレクトリにsample.pdfが作成されます。
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
import os


def create_sample_pdf(directory: str):
    """Creates a simple PDF in the specified directory."""
    file_path = os.path.join(directory, "sample.pdf")
    c = canvas.Canvas(file_path, pagesize=letter)
    width, height = letter
    c.drawString(72, height - 72, "This is a valid PDF document.")
    c.showPage()
    c.save()
    print(f"Created {file_path}")


if __name__ == "__main__":
    # Create the PDF inside the 'mymupdf' directory
    create_sample_pdf(".")

PyMuPDFが高速な理由

PyMuPDFが他のPython PDFライブラリと比較して高速である主な理由は、その内部実装にあります。

  • C言語ベースのバックエンド: PyMuPDFは、C言語で書かれた高性能なPDFレンダリングライブラリであるMuPDFのPythonバインディングです。Pythonのコードから直接C言語の高速な処理を呼び出すため、純粋なPythonで書かれたライブラリよりもはるかに高速に動作します。
  • 効率的なメモリ管理: MuPDFはメモリ効率も非常に優れており、大規模なPDFファイルや大量のPDFを処理する際にも、メモリ使用量を抑えながら高速な処理を実現します。
  • 最適化されたレンダリングエンジン: PDFの解析、レンダリング、テキスト抽出といったコア機能が高度に最適化されており、複雑なPDF構造でも迅速に処理できます。
[!著者の経験談]
以前、数千ページに及ぶPDFから特定の情報を抽出するプロジェクトに携わった際、他のPython PDFライブラリでは処理に数時間かかっていたものが、PyMuPDFに切り替えたところ、大幅に短縮した経験があります。その高速性には本当に驚かされました。

3. 高精度なテキスト抽出

PyMuPDFの最大の魅力の一つは、その高精度なテキスト抽出能力です。特に日本語のようなマルチバイト文字を含むPDFからの抽出において、その優位性を発揮します。


ページごとのテキスト抽出

最も基本的なテキスト抽出は、ページオブジェクトのget_text()メソッドを使用します。

import pymupdf
    with pymupdf.open("sample_japanese.pdf") as doc: # 日本語を含むPDFを想定
        for page_num in range(doc.page_count):
            page = doc[page_num]
            text = page.get_text()
            print(f"--- ページ {page_num + 1} のテキスト ---")
except pymupdf.FileNotFoundError:
    print("エラー: sample_japanese.pdf が見つかりません。日本語を含む既存のPDFファイルを指定してください。")
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")

日本語テキスト抽出の優位性

多くのPDFライブラリが日本語テキストの抽出で文字化けや欠落を起こしやすい中、PyMuPDFは高い精度で日本語を抽出できます。これは、MuPDFが内部的に多言語対応に優れているためです。

[!NOTE]
完全に文字化けをゼロにすることは難しい場合もありますが、PyMuPDFは他のライブラリと比較して、その発生頻度と程度を抑えることができます。

テキスト抽出時のオプション(座標情報、ブロック情報)

get_text()メソッドには、抽出形式を指定するoutputパラメータがあります。これにより、単なるテキストだけでなく、テキストの座標情報やブロック情報など、より詳細な構造化されたデータを取得できます。

主なoutput形式:

  • "text" (デフォルト): プレーンテキスト
  • "html": HTML形式でテキストとレイアウト情報
  • "json": JSON形式でテキストと詳細な構造情報(ブロック、ライン、スパン、文字ごとの座標など)
  • "dict": JSON形式と同様の情報をPython辞書で取得
  • "rawdict": より低レベルな詳細情報を含む辞書

ここでは、"json"形式を使って、テキストの座標情報を取得する例を見てみましょう。

import pymupdf
from typing import TypedDict, cast


# テキストのスパン(文字列単位)を表す TypedDict
class Span(TypedDict):
    text: str      # スパン内の文字列
    font: str      # フォント名
    size: float    # フォントサイズ(ポイント)
    bbox: tuple[float, float, float, float]  # スパンの境界ボックス (x0, y0, x1, y1)


# 行(Line)を表す TypedDict。行は複数のスパンから構成される
class Line(TypedDict):
    bbox: tuple[float, float, float, float]  # 行全体の境界ボックス
    spans: list[Span]                        # 行内のスパン一覧


# ブロック(Block)を表す TypedDict。テキストブロックは複数の行からなる
class Block(TypedDict):
    type: int                               # 0 はテキストブロック、他は画像等
    bbox: tuple[float, float, float, float] # ブロック全体の境界ボックス
    lines: list[Line]                       # ブロック内の行一覧


# ページ全体のテキスト情報を表す TypedDict
class PageData(TypedDict):
    blocks: list[Block]  # ページ内のブロック一覧


try:
    # PDFを開く(ファイル名は例として日本語テキストが含まれるもの)
    with pymupdf.open("japanese_text_example.pdf") as doc:
        page = doc[0]  # 最初のページを取得

        # ページからテキスト情報を辞書形式で取得し、PageData型にキャスト
        data: PageData = cast(PageData, page.get_text("dict"))  # pyright: ignore [reportUnknownMemberType]

        print("--- 最初のページのテキストブロック情報 ---")
        for block in data["blocks"]:
            # type 0 はテキストブロック
            if block["type"] == 0:
                print(f"  ブロックBBox: {block['bbox']}")
                for line in block["lines"]:
                    print(f"    ラインBBox: {line['bbox']}")
                    for span in line["spans"]:
                        print(
                            f"      テキスト: '{span['text']}', フォント: {span['font']}, サイズ: {span['size']}, BBox: {span['bbox']}"
                        )

except RuntimeError as e:
    if "cannot open" in str(e):
        print(
            "エラー: japanese_text_example.pdf が見つかりません。日本語を含む既存のPDFファイルを指定してください。"
        )
    else:
        raise
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")

この詳細な情報を使えば、特定の領域にあるテキストだけを抽出したり、テキストの配置に基づいてデータを構造化したりといった高度な処理が可能になります。

[!TIP]
"json""dict"形式で取得できるbbox(bounding box)情報は、テキストがPDF上のどこに位置しているかを示す座標です。これにより、例えば「特定の表のセル内のテキストだけを抽出する」といった、より精緻なデータ抽出が可能になります。

4. 実践例:大量のPDFからの情報収集

ここでは、複数のPDFファイルから特定のキーワードを含む行を抽出し、そのファイル名とページ番号をレポートする簡単な実践例を紹介します。

import pymupdf
import os
from typing import TypedDict
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont


# 検索結果の型定義(検索したキーワードが見つかった場所を保持する)
class SearchResult(TypedDict):
    filename: str  # ファイル名
    page: int  # ページ番号(1 から始まる)
    line_number: int  # 行番号(1 から始まる)
    content: str  # 検索行のテキスト


def search_keyword_in_pdfs(pdf_directory: str, keyword: str) -> list[SearchResult]:
    """
    指定したディレクトリ内の PDF を走査し、キーワードを含む行を検索する。
    :param pdf_directory: PDF が格納されているディレクトリパス
    :param keyword: 検索したい文字列
    :return: SearchResult のリスト(見つからなければ空リスト)
    """
    results: list[SearchResult] = []
    # ディレクトリ内の全ファイルを列挙
    for filename in os.listdir(pdf_directory):
        # PDF ファイルのみ対象にする(拡張子は大文字小文字区別なし)
        if filename.lower().endswith(".pdf"):
            filepath = os.path.join(pdf_directory, filename)
            try:
                # pymupdf で PDF を開く
                with pymupdf.open(filepath) as doc:
                    # 各ページを走査
                    for page_num in range(doc.page_count):
                        page = doc[page_num]
                        # ページのテキストを取得(文字列として返る)
                        text = page.get_text("text")
                        # 行単位で分割して検索
                        for line_num, line in enumerate(text.splitlines()):
                            if keyword in line:
                                # キーワードを含む行を結果リストへ追加
                                results.append(
                                    {
                                        "filename": filename,
                                        "page": page_num + 1,  # ページ番号は 1 始まり
                                        "line_number": line_num
                                        + 1,  # 行番号は 1 始まり
                                        "content": line.strip(),  # 前後の空白を除去
                                    }
                                )
            except Exception as e:
                # 何らかのエラーが起きた場合はメッセージを表示
                print(f"ファイル {filename} の処理中にエラーが発生しました: {e}")
    return results


def create_pdf_with_text(file_path: str, text_lines: list[str]):
    """
    与えられたテキスト行を PDF として保存する。
    :param file_path: 出力ファイルパス
    :param text_lines: 1 行ずつの文字列リスト
    """
    font_path = "ipaexg.ttf"  # プロジェクトルートからの相対パス
    # フォントを登録(フォントファイルが存在しない場合は例外になる)
    pdfmetrics.registerFont(TTFont("IPAexGothic", font_path))
    # PDF 生成
    c = canvas.Canvas(file_path, pagesize=letter)
    c.setFont("IPAexGothic", 12)
    _, height = letter
    y = height - 72  # 上端から 1 インチ下げた位置に最初の行を描画
    for line in text_lines:
        c.drawString(72, y, line)  # 左端から 1 インチの位置に描画
        y -= 14  # 行間を調整
    c.save()


# 使用例(実際には PDF ファイルが格納されているディレクトリを指定してください)
pdf_dir = "./sample_pdfs"  # 例: カレントディレクトリに sample_pdfs フォルダを作成し、PDF を配置
search_term = "重要"  # 検索したいキーワード

# ダミーの PDF ファイルを作成(テスト用)
if not os.path.exists(pdf_dir):
    os.makedirs(pdf_dir)

create_pdf_with_text(
    os.path.join(pdf_dir, "report_a.pdf"),
    ["これは重要なレポートAです。", "詳細はこちら。"],
)
create_pdf_with_text(
    os.path.join(pdf_dir, "report_b.pdf"),
    ["別のレポートB。", "特に重要な情報はありません。"],
)
create_pdf_with_text(
    os.path.join(pdf_dir, "report_c.pdf"), ["重要なデータが含まれるレポートC。"]
)

# 検索を実行
found_items = search_keyword_in_pdfs(pdf_dir, search_term)

if found_items:
    print(f"\nキーワード '{search_term}' が見つかりました:")
    for item in found_items:
        print(
            f"  ファイル: {item['filename']}, ページ: {item['page']}, 行: {item['line_number']}, 内容: {item['content']}"
        )
else:
    print(f"\nキーワード '{search_term}' は見つかりませんでした。")

# ダミーファイルを削除(クリーンアップ)
for filename in os.listdir(pdf_dir):
    os.remove(os.path.join(pdf_dir, filename))
os.rmdir(pdf_dir)
[!NOTE]
上記のコード例では、pymupdf.open()がテキストファイルを直接開くことはできないため、ダミーのPDFファイルを作成する部分をコメントアウトしています。実際にこのコードを試す際は、pdf_dirに既存のPDFファイルを配置するか、ReportLabなどのライブラリでPDFファイルを生成して使用してください。

5. まとめ:PyMuPDFでPDFデータ活用を加速する

本記事では、Pythonの強力なPDFライブラリであるPyMuPDFの基本的な使い方から、その高速性、そして特に日本語テキスト抽出における高精度な能力について詳しく解説しました。

PyMuPDFは、大量のPDF処理や、日本語を含む複雑なPDFからのデータ抽出といった、多くの開発者が直面する課題に対して、非常に効果的なソリューションを提供します。単なるテキスト抽出に留まらず、座標情報やブロック情報を活用することで、より構造化されたデータの取得も可能です。

今日からできる、はじめの一歩

  1. PyMuPDFをインストール: まずはpip install PyMuPDFで環境を整えましょう。
  2. 手元のPDFで試す: 実際に手元にあるPDFファイル(特に日本語を含むもの)を使って、get_text()メソッドを試してみてください。その高速性と精度に驚くはずです。
  3. 詳細な抽出に挑戦: get_text("json")を使って、テキストの座標情報やブロック情報を取得し、特定の領域からのデータ抽出を試してみましょう。

このシリーズでは、今後もPython PDFライブラリの深掘りを続けていきます。次回は、PyMuPDFを使ったPDFからの画像抽出や変換、そして簡単な編集テクニックについて解説する予定です。

https://www.visionnurture.com/python_pdf_for_beginner_009

この記事があなたのPDF自動化の助けになったなら幸いです。ぜひ、あなたのプロジェクトでのPyMuPDF活用事例や、この記事に関するご意見・ご質問をコメント欄でお聞かせください。また、X(旧Twitter)で感想をシェアしていただけると嬉しいです!


参考資料


免責事項

本記事は情報提供のみを目的としており、記載された情報の正確性、完全性、有用性を保証するものではありません。コードの利用は自己責任で行ってください。ライブラリのライセンスについては、ご自身でご確認ください。


SNSでもご購読できます。

コメントを残す

*