pymupdfによるPDFデータ抽出ハンズオン

はじめに

お仕事で、生成AIを用いてPDF(非構造化データ)から構造化データの抽出に取り組んでいます。

従来のAI OCRでは、表組みや複雑なレイアウトを持つPDFからの情報抽出に課題がありました。そこで、Pythonのライブラリ「pymupdf」を導入し、生成AIが参照するヒントを増やすことで、抽出精度を向上させられるのではないかと考えました。

具体的には、「pymupdf」 でPDFからテキストのレイアウト情報を取得し、それを生成AIに提供することで、より文脈に即した回答を得られるのではないかと思ったので、まずは「pymupdf」導入・検証のためのハンズオンをしていきたいと思います。

pymupdfとは?

PDF、XPS、EPUB、CBZなど、様々なドキュメント形式を利用することが出来るようになるPythonライブラリ。

内部では、C言語で書かれた高速なレンダリングエンジンであるMuPDFを使用しており、高速な処理が特徴。
テキスト抽出だけでなく、画像の抽出、PDFの編集、ページの結合・分割、注釈の追加など、多岐に渡る機能がある。

ハンズオン

項番 バージョン番号
1 Python 3.11.11

1.環境構築

プロジェクトごとの依存関係を分離するため、仮想環境を作成

  • ProjectNameは、任意の名前を設定してください。
ProjectName = {任意のプロジェクト名}
mkdir ${ProjectName}
cd ${ProjectName}
python3 -m venv .venv
source .venv/bin/activate

2.ライブラリインストール

プロジェクトで使用するライブラリのインストール

2.1.requirements.txtの中身

pymupdf

2.2.インストール

ライブラリのインストール

pip install -r requirements.txt

3.app.pyの中身

PDFファイル読み込みアプリケーションのコード

  • output.txt で、読み込み内容を出力
  • pdf_file = "myresume.pdf"は、読み込みたいPDFファイルを読者で用意(※今回私は、自身の履歴書を利用)
import fitz
import json

def extract_text_from_pdf(pdf_path):
    """PDFファイルからテキストを抽出する関数 (dictを使用、詳細レイアウト処理)"""
    try:
        doc = fitz.open(pdf_path)
        all_text = ""

        for page_num, page in enumerate(doc):
            print(f"--- ページ {page_num + 1} の処理 ---")

            text_dict = page.get_text("dict")
            blocks = text_dict["blocks"]

            # ブロックをy座標でソート
            blocks.sort(key=lambda block: block["bbox"][1])

            for block in blocks:
                lines = block["lines"]

                # 行をy座標でソート
                lines.sort(key=lambda line: line["bbox"][1])

                for line in lines:
                    spans = line["spans"]

                    # スパンをx座標でソート
                    spans.sort(key=lambda span: span["bbox"][0])

                    line_text = ""
                    for span in spans:
                        line_text += span["text"]

                    all_text += line_text + "\n"

        return all_text.strip()

    except FileNotFoundError:
        print(f"エラー: PDFファイル '{pdf_path}' が見つかりません。")
        return None
    except fitz.FileDataError as e:
        print(f"エラー: PDFファイルが不正です: {e}")
        return None
    except Exception as e:
        print(f"エラー: PDF処理中に予期せぬエラーが発生しました: {e}")
        return None

if __name__ == "__main__":
    pdf_file = "myresume.pdf"
    extracted_text = extract_text_from_pdf(pdf_file)

    if extracted_text:
        with open("output.txt", "w", encoding="utf-8") as f:
            f.write(extracted_text)
        print("テキストを output.txt に保存しました。")
        # print("--- 抽出されたテキスト ---") # 必要に応じてコンソール出力
        # print(extracted_text)
    else:
        print("テキストを抽出できませんでした。")

4.出力(一部抜粋)

①PDFデータ

file

①実際に抽出出来た部分

基本情報
フリガナ
イナムラ テッペイ
稲村 鉄平
氏名

②PDFデータ

file

②実際に抽出出来た部分

現在所属するチームでは、DevOpsによるチーム文化醸成のための勉強会を主導していま
す。
その他に、所属チーム以外の会議にも参加して、他チームへの技術的提案の実施。

おわりに

得られた知見

  • get_text("dict") を使うことで、テキストのレイアウト情報を取得し、ある程度レイアウトを維持した状態でテキストを抽出できることが分かる。

今後の課題

  • 記号(■、●、・)は何処まで制度が出るのかの確認。
  • 抽出したテキストとレイアウト情報をJSON形式で生成AIに渡してプロンプトに組み込むことで、より文脈に即した回答を得られるか検証する。
Last modified: 2025-01-27

Author