📘

【RAG】金融コンペで奮闘!複雑なPDFから高精度テキスト抽出するPythonライブラリ比較

に公開

はじめに

こんにちは!先日、金融庁共催の「第3回金融データ活用チャレンジ」に参加しました。このコンペの課題は、各企業のアニュアルレポート(PDF)から情報を抽出し、質問に答えるRAG (Retrieval-Augmented Generation) システムを構築するというもの。RAGとは、外部の知識ベース(今回はPDF)を検索し、その情報をもとに回答を生成するAI技術です。

このコンペを通してRAGの肝となるPDFからのテキスト抽出について深く学ぶ機会があったので、知見を共有します。第一弾として、日本語の複雑なレイアウトを持つPDFの読み取りで試行錯誤した、各種Pythonライブラリの比較をお届けします!

なぜPDFリーダーの比較が必要なのか?

結論から言うと、日本語の複雑なPDFを100%完璧に読み取れる無料ライブラリは、私の調査ではありませんでした。 そのため、ライブラリごとの長所・短所を理解し、目的に応じて使い分けるか、複数のライブラリを組み合わせる、あるいは有料APIの利用や自作といった選択肢を検討する必要があります。

📝 今回のPDF読み取りの難所

コンペで提供されたアニュアルレポートには、単純なテキスト抽出を阻むいくつかの「壁」がありました。

  • 難所1:複雑な段組み
    • 本文が2段組や3段組になっており、単純に左上から読み込むと文章の順序がバラバラになる。
  • 難所2:識別しにくい表
    • 罫線がなかったり、複雑な構造をしていたりする表の中に、重要な数値データが含まれている。
  • 難所3:画像化されたテキスト
    • 文章や数値が、テキストデータではなく画像の一部として埋め込まれている。

これらの課題を、各ライブラリがどれだけうまく処理できるかを見ていきましょう。


環境と準備

今回はすべてGoogle Colaboratoryで作業を進めます。

PDFのページを画像として表示する

テキスト抽出の精度を確認する際、対象のPDFページをJupyter環境(Colab)で直接表示できると非常に便利です。以下のヘルパー関数を用意しておくと、作業がスムーズに進みます。

show_pdf_page.py
!pip install PyMuPDF -q

import pymupdf
import numpy as np
import matplotlib.pyplot as plt

def show_pdf_page_as_image(pdf_path, page_number, dpi=150):
    try:
        doc = pymupdf.open(pdf_path)
        # ページ番号は0から始まるため、-1する
        page = doc.load_page(page_number - 1)
        
        pix = page.get_pixmap(dpi=dpi)
        img = np.ndarray([pix.h, pix.w, 3], dtype=np.uint8, buffer=pix.samples_mv)
        
        plt.figure(figsize=(12, 18), dpi=dpi)
        plt.title(f"PDF: {pdf_path.split('/')[-1]} - Page: {page_number}")
        plt.imshow(img)
        plt.axis('off') # 軸を非表示
        plt.show()
        
    except Exception as e:
        print(f"エラーが発生しました: {e}")
    finally:
        if 'doc' in locals():
            doc.close()
PDF_PATH = '/content/drive/MyDrive/Colab Notebooks/your_path/target.pdf' # ご自身のPDFパスに変更してください
PAGE_NUM = 11
# 関数を呼び出してページを表示
show_pdf_page_as_image(PDF_PATH, PAGE_NUM)

(実行前に、ご自身のPDFファイルのパスを PDF_PATH に設定してください。)
実際の今回使用したページはこちら

📚 PDF抽出ライブラリ比較

それでは、本題のライブラリ比較です。今回は特に有望だった3つのライブラリを取り上げます。

1. PyMuPDF:高速・シンプルなテキスト抽出

まずは、高速かつ多機能で知られるPyMuPDF(fitz)です。

!pip install PyMuPDF -q

import fitz # PyMuPDFライブラリ

# PDFのパスとページ番号
PDF_PATH = '/content/drive/MyDrive/Colab Notebooks/your_path/target.pdf' # ご自身のPDFパスに変更してください
PAGE_NUM = 11

# PDFファイルを開く
doc = fitz.open(PDF_PATH)

# 指定したページを読み込む(ページ番号は0から始まるため-1する)
page = doc.load_page(PAGE_NUM - 1)

# ページ内のテキストを抽出する
text = page.get_text()

print(text)

doc.close()

実行結果

環境
社会性
Corporate direction
データ集
ガバナンス
目次
Nissan Motor Corporation
Sustainability data book 2024
CSOメッセージ
日産のサステナビリティ
010
サステナブル・ファイナンス・
フレームワーク
日産はコーポレートパーパス「人々の生活を豊かに。イノ
ベーションをドライブし続ける」の実現に向け、サステナビリ
ティを事業の中核として位置づけ、グローバルな事業活動を
通じて企業として成長し、社会が直面する諸課題の解決に貢
献することを目指しています。
... (中略) ...
*1 日産自動車・販売金融関連会社 サステナブル・ファイナンス・フレームワーク原本(英語)...
... (中略) ...
GSSCは年2回開
催し、各領域で活動を担う部署の責任者が参加します。各活
動は担当部署が責任を持って推進し、その進捗はコミッティ
で報告されます。2023年度は2回開催しました。
なお、環境課題についてはCSOと取締役 代表執行役社長
兼最高経営責任者が共同議長を務めるグローバル環境委

考察 🚀

  • 速度: 非常に高速です(Wall time: 144 ms)。大量のPDFを処理する際に大きなメリットになります。

  • 精度: シンプルなテキスト抽出は得意です。

  • 課題: 段組みのレイアウトを正しく解釈できていません。 本文を上から下に読み進めてしまい、ページ下部にあるべき注釈(*1, *2...)が本文の途中に割り込んでしまっています。単純な読み上げ順では、文脈が壊れてしまいますね。

2. pymupdf4llm:LLMのための高精度レイアウト解析

次に、PyMuPDFをベースに、LLMでの利用を想定して開発されたpymupdf4llmです。Markdown形式で出力してくれるのが最大の特徴です。

!pip install pymupdf4llm -q

import pymupdf4llm

# PDFのパス
PDF_PATH = '/content/drive/MyDrive/Colab Notebooks/your_path/target.pdf' # ご自身のPDFパスに変更してください

# PDFファイルからテキストをマークダウン形式で抽出する
# to_markdownはファイルパスを直接引数に取れます
md_text = pymupdf4llm.to_markdown(PDF_PATH)

print(md_text)

(特定のページだけを抽出したい場合は、pages=[PAGE_NUM-1]のように引数を追加します。)

実行結果

Nissan Motor Corporation Sustainability data book 2024

目次 Corporate direction 環境 社会性 ガバナンス データ集

CSO メッセージ 日産のサステナビリティ

010

サステナブル・ファイナンス・
フレームワーク

日産はコーポレートパーパス「人々の生活を豊かに。イノ
ベーションをドライブし続ける」の実現に向け、サステナビリ
ティを事業の中核として位置づけ、グローバルな事業活動を
通じて企業として成長し、社会が直面する諸課題の解決に貢
献することを目指しています。
... (中略) ...
サステナビリティ戦略の目標設定や具体的な活動の進捗
や課題については、チーフ サステナビリティ オフィサー
( CSO : Chief Sustainability Officer )が議長を務め
る グローバル・サステナビリティ・ステアリング・コミッティ
( GSSC : Global Sustainability Steering Committee )
で議論しています。
... (後略) ...

*1 日産自動車・販売金融関連会社 サステナブル・ファイナンス・フレームワーク原本(英語) [https://www.nissan-global.com/...](https://www.nissan-global.com/...)
参考和訳 [https://www.nissan-global.com/...](https://www.nissan-global.com/...)
*2 セカンドパーティ・オピニオン原本(英語) [https://www.nissan-global.com/...](https://www.nissan-global.com/...)
... (後略) ...

考察 🎯

  • 精度: 驚くほど高精度です。 複雑な段組みを正しく解釈し、自然な文章の順序でテキストを抽出できています。さらに、ページ下部の注釈も本文と明確に分離して末尾にまとめてくれました。URLも自動でリンク化されています。

  • 形式: LLM(GPTなど)が解釈しやすいMarkdown形式で出力されるため、RAGの前処理として非常に相性が良いです。

  • 課題: 唯一の欠点は処理速度です。PyMuPDFに比べて時間がかかります(Wall time: 3.19 s)。精度とトレードオフの関係ですね。

pymupdf4llmの豊富なパラメータ

to_markdown関数には非常に多くのオプションがあり、表の抽出戦略や画像の扱いなどを細かく制御できます。


# --- to_markdown関数の呼び出し ---
# 各パラメータは必要に応じてコメントを解除・編集してください
md_output = pymupdf4llm.to_markdown(
    # --- 基本設定 ---
    doc=pdf_path,                       # 処理対象のPDFファイルパス、またはPyMuPDFのDocumentオブジェクト
    pages=[page_num-1],                 # 処理するページ番号のリスト (1始まり)。Noneですべてのページ

    # --- 出力形式 ---
    page_chunks=False,                  # Trueにすると、ページ毎に分割された辞書のリストで結果を返す
    extract_words=False,                # Trueにすると、単語リストを追加で抽出 (`page_chunks=True`が必須)

    # --- 画像とグラフィックの処理 ---
    write_images=False,                 # Trueにすると、抽出した画像をファイルとして保存する
    embed_images=False,                 # Trueにすると、画像をBase64形式でマークダウンに直接埋め込む (write_imagesより優先)
    ignore_images=False,                # Trueにすると、ラスタ画像(JPEG, PNG等)を完全に無視する
    ignore_graphics=False,              # Trueにすると、ベクター画像(図形や線画)を無視する
    force_text=True,                    # Trueにすると、画像と重なっているテキストも強制的に抽出する
    dpi=150,                            # 画像を書き出す際の解像度 (dots per inch)
    image_path="",                      # 画像を保存するフォルダのパス
    image_format="png",                 # 保存する画像のフォーマット ('png', 'jpg'など)
    filename=None,                      # 画像のファイル名を指定 (メモリから読込時に便利)
    image_size_limit=0.05,              # ページの幅/高さに対する割合がこの値以下の画像を無視する (0.05 = 5%)
    graphics_limit=None,                # ベクター画像の数がこの値を超えたら処理を無視する (多すぎて重い場合に設定)

    # --- レイアウトと表の検出 ---
    margins=(0, 0, 0, 0),               # ページの余白を指定 (上, 左, 下, 右)。この内側のみを処理
    table_strategy="lines_strict",      # 表を検出する方法 ('lines', 'lines_strict'など)。Noneで表検出を無効化
    page_width=612,                     # EPUBなどリフロー形式の文書のページ幅を指定
    page_height=None,                   # リフロー形式の文書のページ高さを指定。Noneで無限長

    # --- 上級者向け・特殊設定 ---
    hdr_info=None,                      # ヘッダー検出のカスタムロジックを指定 (上級者向け)
    ignore_code=False,                  # Trueにすると、コードブロックの自動生成を無効にする
    show_progress=False,                # Trueにすると、処理中にコンソールへ進捗バーを表示する
    use_glyphs=False,                   # Trueにすると、文字の代わりに内部的なグリフ番号を使用する (特殊用途)
)

# --- 結果の表示 ---
# page_chunksがFalseなので、結果は単一のマークダウン文字列
print(md_output)

3. pdfminer.six:定番のテキスト抽出ツール

pdfplumberの元にもなっている、古くから使われているライブラリpdfminer.sixも試してみましょう。

!pip install pdfminer.six -q

from pdfminer.high_level import extract_text
from pdfminer.layout import LAParams

# PDFのパスとページ番号
PDF_PATH = '/content/drive/MyDrive/Colab Notebooks/your_path/target.pdf' # ご自身のPDFパスに変更してください
PAGE_NUM = 11

# レイアウト解析用のパラメータ
laparams = LAParams()

# pdfminerのページ指定は0から始まるため、-1する
page_numbers = [PAGE_NUM - 1]

# 指定したページからテキストを抽出
text = extract_text(
    PDF_PATH,
    page_numbers=page_numbers,
    laparams=laparams
)

print(text)

実行結果

CSOメッセージ

日産のサステナビリティ

010

サステナブル・ファイナンス・
フレームワーク

全額充当しました。*4  本フレームワークを通じて調達した資

金は、バッテリーを含む電動車両の開発や生産、EVエコシス

サステナビリティ推進のガバナンス

テム・スマートシティの実現に向けた技術開発やインフラ整

「人々の生活を豊かに。イノベーションをドライブし続ける」と

日産はコーポレートパーパス「 人々の 生 活を豊かに。イノ
... (中略) ...

考察 🤔

  • 精度: PyMuPDFと同様に、段組みの解釈に苦戦している様子が見られます。右の段落の内容が左の段落の途中に挿入されてしまっており、RAGの知識源として使うには前処理が必須になりそうです。

  • 速度: PyMuPDFよりは遅いですが、pymupdf4llmよりは高速です(Wall time: 733 ms)。

番外編

企業系のPDF(冊子のPDF化)の場合、1ページ目に表紙があり、2ページ目から見開きのデータが格納されていることがあります。
実際にはA4縦が10ページあるとしても、1PがA4縦、2PがA3横(A4縦が横に2P)となっている際に処理がしにくい場合には事前にページを分割することでうまくいくこともあります。

その際のコード記載しておきます。

import pymupdf # PyMuPDFライブラリをインポート

def split_pdf(pdf_path): # PDFファイルを処理する関数を定義

    src = pymupdf.open(pdf_path) # 分割したい元のPDFファイルを開く
    doc = pymupdf.open() # 保存用の新しい空のPDFドキュメントを作成

    for spage in src: # 元のPDFの全ページを1ページずつループ処理
        r = spage.rect # 現在のページのサイズ(矩形)を取得
        
        # ページの幅が高さより大きい場合(横長のページ)のみ処理
        if r.width > r.height:
            # --- 左半分を処理 ---
            # 元のページの半分の幅で新しいページを作成
            left_page = doc.new_page(-1, width=r.width / 2, height=r.height)
            # 元のページの左半分の領域を定義
            left_rect = pymupdf.Rect(0, 0, r.width / 2, r.height)
            # 新しいページに、元のページ内容を「左半分の領域(clip)で」切り取って表示
            left_page.show_pdf_page(left_page.rect, src, spage.number, clip=left_rect)

            # --- 右半分を処理 ---
            # 元のページの半分の幅で新しいページを作成
            right_page = doc.new_page(-1, width=r.width / 2, height=r.height)
            # 元のページの右半分の領域を定義
            right_rect = pymupdf.Rect(r.width / 2, 0, r.width, r.height)
            # 新しいページに、元のページ内容を「右半分の領域(clip)で」切り取って表示
            right_page.show_pdf_page(right_page.rect, src, spage.number, clip=right_rect)

            # ######################## 右開きの場合 ########################
            # もし右開きの本(漫画など)を想定する場合、
            # 上記の「左半分→右半分」の順ではなく、「右半分→左半分」の順で
            # doc.new_page() と .show_pdf_page() を実行する必要がある。
            # #############################################################

        else:
            # --- 縦長のページはそのまま処理 ---
            # 元のページと同じサイズの新しいページを作成
            page = doc.new_page(-1, width=r.width, height=r.height)
            # 新しいページに、元のページ内容をそのまま表示
            page.show_pdf_page(page.rect, src, spage.number)

    # 作成したドキュメントを "output.pdf" という名前で保存
    # garbage=3, deflate=True, clean=True はファイルを最適化・圧縮するためのオプション
    doc.save("output.pdf", garbage=3, deflate=True, clean=True)

    src.close() # 元のPDFファイルを閉じる
    doc.close() # 作成したPDFファイルを閉じる

まとめと結論

今回の検証結果をまとめます。

ライブラリ 抽出精度
(レイアウト解釈)
処理速度 特徴
PyMuPDF 速い シンプルで高速。簡単なPDF向き。
pymupdf4llm 非常に高い 遅い Markdown出力が強力。複雑なレイアウトに最適。
pdfminer.six 普通 定番ライブラリ。レイアウト解析は苦手な場合も。

今回の金融コンペのように、複雑なレイアウトのアニュアルレポートから正確な情報を抽出するという目的においては、処理時間に目をつぶっても pymupdf4llm を使うのが最善の選択だと感じました。抽出後のテキストがクリーンで、RAGの後続処理(チャンク分割や埋め込み)をスムーズに進められるメリットは非常に大きいです。

一方で、大量の単純なPDFを高速に処理したい場合は PyMuPDF が輝きます。

RAGの精度は、まさに「Garbage In, Garbage Out(ゴミを入れたらゴミしか出てこない)」。最初のデータ抽出の質がシステム全体の性能を決めるといっても過言ではありません。目的に合ったライブラリを選んで、精度の高いRAGシステムを構築しましょう!

この記事が、同じようにPDFの扱いに奮闘している方の助けになれば幸いです。

Discussion