株式会社renue
AI導入・DXの悩みをプロに相談してみませんか?
AIやDXに関する悩みがありましたら、お気軽にrenueの無料相談をご利用ください。 renueのAI支援実績、コンサルティングの方針や進め方をご紹介します。
PDF構造化データ抽出AIとは、マニュアル・技術文書・帳票・カタログなど非構造化PDFから、AIが表・リスト・エラーコードなどの情報を自動的に構造化JSON/CSV形式で抽出するシステムである。2026年現在、LLMとpdfplumberなどの従来型ライブラリを組み合わせたハイブリッドアプローチが主流となっている。本記事では、renueが自社実装しているPDF構造化抽出パイプラインの知見をもとに、本番品質の実装パターンを解説する。
PDF構造化抽出AIが解決する課題
従来のPDF抽出ライブラリ(pdfplumber、PyMuPDF等)だけでは解決できない課題が多い。LLMを組み合わせることで以下の課題を解決できる。
| 課題 | 従来ライブラリ | AI(LLM) |
|---|---|---|
| レイアウト認識 | 単純な座標ベース | 文脈を理解 |
| 表の抽出 | セル境界が明確なら可 | 不規則な表も理解 |
| 多段組レイアウト | 順序がバラバラに | 読み順を正しく認識 |
| 欠損フィールドの補完 | 不可 | 周辺文脈から推測可能 |
| 正規化・統一 | 手動ルール必須 | AIが自動で統一 |
| スキーマ変換 | 構造定義が必要 | 動的スキーマに対応 |
本番品質の抽出パイプラインに必要な5ステージ
Stage 1: PDF前処理と生テキスト抽出
最初のステージではpdfplumberやPyMuPDFで生テキストとメタデータを抽出する。LLMに直接PDFを渡すより、前処理で構造を把握した方がトークン効率が良い。
pdfplumberの推奨オプション
- page.extract_text(): ページ単位でテキスト抽出
- page.extract_tables(): 表を二次元配列として抽出
- page.chars: 文字単位の座標情報(レイアウト分析用)
- page.lines: 罫線情報(表の境界検出用)
- page.images: 画像オブジェクトの位置・属性情報を取得。OCRに使う画像内容の復元は別途処理する(pdfplumber公式説明)
前処理で対応すべき典型的な問題
- 文字間の謎のスペース: 「エ ラ ー コ ー ド」のようなベクター描画由来の空白
- 多段組の読み順: 左右2カラムが混ざる
- ヘッダー・フッターの混入: 本文と区別できない
- 全角・半角の混在: 同じ値が違う形式で書かれる
Stage 2: 正規化とパターンマッチング
LLMに投げる前に、確実に抽出できる情報(エラーコード、型番、日付など)は正規表現で抽出しておく。LLMのコストを削減しつつ精度を上げる。
正規化関数の実装例
例えば「エラーコード」のような半構造化データを抽出する場合、以下のような正規化を行う。説明用の最小例として全角・半角をNFKCで統一し、既知の項目名の空白だけを除く。本文中の英単語間の空白や改行は残し、変換前の文字列も検証用に保存する。
import re
import unicodedata
def normalize_text(text: str) -> str:
text = unicodedata.normalize("NFKC", text)
text = re.sub(r"エ\s*ラ\s*ー\s*コ\s*ー\s*ド", "エラーコード", text)
return "\n".join(re.sub(r"[ \t]+", " ", line).strip() for line in text.splitlines())参照:PythonのUnicode正規化。NFKCは互換文字も変換するため、文書ごとに適用可否を確認する。
複数パターンでの抽出
PDFは書き方が統一されていないため、複数の正規表現パターンを順番に試す。次は項目名付きのコードを優先し、なければEで始まる3〜4桁の形式を探す説明用の例である。実際のコード体系に合わせてパターンを定義し、見つからない場合は未抽出として扱う。
def extract_code(text: str) -> str | None:
text = normalize_text(text)
patterns = [
r"エラーコード\s*[::]?\s*([A-Z0-9]+(?:-[A-Z0-9]+)*)",
r"\b(E\d{3,4})\b",
]
for pattern in patterns:
match = re.search(pattern, text)
if match:
return match.group(1)
return None参照:Pythonの正規表現。
このアプローチの強みは、確実に抽出できるフィールドはパターンマッチングで処理し、LLMコストを節約できる点である。
Stage 3: LLMによる構造化抽出
パターンマッチングでは取れない情報(症状・原因・対処法の説明文、文脈依存の分類等)はLLMに任せる。
Pydanticスキーマによる構造化
LangChainはPydanticスキーマによる構造化出力をサポートしている。例えば以下の型を定義し、前処理済みのPDFテキストとともに抽出条件を渡す。形式の検証に通ることと、原本の事実に一致することは別に確認する。
from typing import Literal
from pydantic import BaseModel, Field, ConfigDict
class ErrorRecord(BaseModel):
model_config = ConfigDict(strict=True)
code: str = Field(min_length=1)
causes: list[str] = Field(default_factory=list)
recoverable: bool | None = None
severity: Literal["Error", "Warning", "Info"] | None = None参照:LangChainの構造化出力。
プロンプト設計
LLMに渡すプロンプトは以下の要素を含める。
- 抽出対象の定義(何を抜き出すか)
- 出力スキーマ(Pydanticクラスから自動生成)
- 具体例(Few-shot)
- 前処理済みのテキスト
- エッジケースの扱い方
Stage 4: 欠損フィールドの補完
LLMが抽出した結果には欠損フィールドが生じる。本番運用では必須フィールドを定義し、欠損時の処理を決める。任意フィールドの空欄はデフォルト値で表現できるが、必須の事実が未抽出の場合は推測で埋めず、検証エラーとして原本確認へ回す。
必須キーとデフォルト値の定義
上の例では`code`は必須で、省略や空文字をエラーにする。`causes`の既定値は空配列、`recoverable`と`severity`は`None`とする。これらは未抽出・未確認を表す値であり、「原因が存在しない」「復旧できない」「重要度が低い」という事実の判定には使わない。空配列は`default_factory=list`でレコードごとに作る。
文字列リストの正規化
AIの出力は同じ情報を異なる形式で返すことがある。例えば`causes`が文字列の場合と配列の場合の両方をサポートする必要がある。文字列は1要素の配列へ変換し、区切り文字の意味が不明なまま分割しない。
def normalize_causes(value):
if value is None:
return []
if isinstance(value, str):
return [value.strip()] if value.strip() else []
return value
ブール値の正規化
AIが「yes」「可能」「true」など様々な形で返すブール値を統一する。以下は復旧可否を表す`recoverable`の例で、対応表にない文字列は確認エラーにする。`None`は未確認であり`False`とは区別する。
def normalize_recoverable(value):
if value is None or isinstance(value, bool):
return value
if isinstance(value, str):
key = unicodedata.normalize("NFKC", value).strip().casefold()
mapping = {"true": True, "yes": True, "可能": True,
"false": False, "no": False, "不可": False}
if key in mapping:
return mapping[key]
raise ValueError("復旧可否を確認してください")
重要度の正規化
重要度は`Error`/`Warning`/`Info`の3値に統一する。「警告」など意味が対応する表記だけを変換し、「異常」「故障」のように語だけでは重要度が決まらない値は、文書側の定義を確認する。
def normalize_severity(value):
if value is None:
return None
if isinstance(value, str):
key = value.strip().casefold()
mapping = {"error": "Error", "エラー": "Error",
"warning": "Warning", "警告": "Warning",
"info": "Info", "情報": "Info"}
if key in mapping:
return mapping[key]
raise ValueError("文書の重要度定義を確認してください")最後に各項目を正規化して`ErrorRecord.model_validate()`に渡す。形式エラーと未抽出項目を記録し、原本に照らして確定する。
raw = {"code": "E001", "causes": "過熱", "recoverable": "可能", "severity": "警告"}
record = ErrorRecord.model_validate({
"code": raw.get("code"),
"causes": normalize_causes(raw.get("causes")),
"recoverable": normalize_recoverable(raw.get("recoverable")),
"severity": normalize_severity(raw.get("severity")),
})
Stage 5: 差分ハイライトと手動検証
AIの抽出結果は完璧ではない。本番運用では必ず人間の検証工程を設ける。一般的な実装では「差分ハイライト」で効率化している。
差分ハイライトの仕組み
- PDF原本をブラウザで表示
- AIが抽出した箇所を色付けでハイライト
- 抽出内容を右側のパネルに表示
- 検証者はワンクリックで「OK」「NG」「修正」を選択
- NGの場合は修正理由を記録して学習データに追加
この仕組みにより、1ページあたり数秒で検証が完了する。手動で全てを入力するのに比べて10倍以上の速度改善が可能である。
コスト管理 — LLMの爆発的コストを抑える
大量のPDFをLLMで処理すると、コストが爆発する。本番運用では以下の対策が必要である。
レスポンスキャッシュ
同じPDFページに対する抽出結果はキャッシュする。PDFのハッシュ値をキーにしてRedisやファイルキャッシュに保存する。
トークン上限の監視
1ファイルあたり、1日あたり、1ユーザーあたりのトークン使用量を監視し、上限を超えたら処理を中断する。一般的な実装では利用量を制御する仕組みでこれを実装している。
段階的モデル切替
- Stage 1: 安価なモデル(GPT-4o-mini等)で一次抽出
- Stage 2: 信頼度が低い場合のみ高性能モデル(GPT-4o、Claude Opus等)で再処理
- Stage 3: それでも信頼度が低い場合は人間レビューへ
この3段階により、平均コストを大幅に削減できる。
モデル選定 — 2026年時点のベストプラクティス
| モデル | 得意領域 | 特徴 |
|---|---|---|
| Gemini 2.0 Flash | 表・レイアウト理解 | コスパ良好、マルチモーダル対応 |
| Claude Opus | 複雑な文脈理解 | 日本語精度高い、高コスト |
| GPT-4o | 汎用・Pydantic統合 | LangChain連携が充実 |
| Adobe PDF Extract | プロフェッショナル抽出 | NVIDIAとの協業で進化中 |
| pdfplumber | 正確な座標ベース抽出 | OSS・コスト0 |
実装では複数モデルを組み合わせるハイブリッドアプローチが推奨される。例えば「pdfplumberで生抽出 → Gemini 2.0 Flashで一次構造化 → Claude Opusで高精度補完」のような3段階構成が効果的である。
業界別の適用パターン
| 業界 | 主な対象PDF | 抽出したい構造化データ |
|---|---|---|
| 製造業 | 取扱説明書、エラーコード集、整備マニュアル | 型番/エラー/原因/対処法 |
| 建設業 | 設計仕様書、見積書、積算書 | 数量/単価/合計 |
| 金融業 | 決算書、目論見書、契約書 | 勘定科目/金額/条項 |
| 医療業 | 添付文書、診療ガイドライン | 薬剤名/用法/副作用 |
| 法務 | 契約書、判例、法令 | 条項/当事者/日付 |
| 小売業 | カタログ、商品一覧 | 商品名/価格/スペック |
一般化した導入例
技術文書からエラー情報や仕様項目を抽出するケースでは、PDFからテキストを取得し、表記揺れを正規化した上で、LLMによる構造化と人手確認を組み合わせます。特定企業のシステム名、顧客名、内部構成は公開せず、課題・設計原則・検証方法だけを共有します。
公開可能な技術要素
- PDF解析ライブラリによるテキスト抽出
- スキーマ検証による出力形式の統一
- キャッシュと利用上限によるコスト管理
- 差分表示と人による最終確認
実際の導入では、対象文書やセキュリティ要件に応じて構成を調整します。
導入時のよくある失敗パターン
- LLMだけで全て処理しようとする: 単純な正規表現で取れる情報までLLMに任せてコスト爆発
- 人間レビューを省略する: 誤抽出がそのまま業務データとして蓄積
- スキーマを定義しない: 毎回出力形式がブレて後工程が壊れる
- キャッシュを実装しない: 同じPDFを何度も処理してコスト爆発
- Pydantic検証を省略: LLMが不正な型を返したときにシステムが壊れる
- レイアウト理解を軽視: 多段組PDFで読み順がバラバラに
- モデルを固定: コスト/精度のバランスが取れない
あわせて読みたい
サービス・採用に関する公開情報は、renue公式サイトをご確認ください。




