保険約款PDFをMarkdown化するために、PDF分割・暗号化解除・AIでの抽出方法を検証する

保険約款PDFをMarkdown化するために、PDF分割・暗号化解除・AIでの抽出方法を検証する

保険証券PDFをMarkdown化できたので、次は自動車保険の普通保険約款・特約PDFもMarkdown化していきます。

保険証券は契約内容の一覧表に近く、ページ数も少ないため、GeminiやChatGPTで比較的処理しやすい文書でした。

しかし、保険約款はページ数が多く、条文・特約・免責条件などが大量に含まれます。

そのため、保険証券用のプロンプトをそのまま使うのではなく、PDFを分割し、1ファイルずつMarkdown化する方針にしました。

この記事では、保険約款PDFをMarkdown化するための技術的な手順だけを扱います。実際の約款本文全文、契約者情報、証券番号、住所、電話番号などは掲載しません。

保険証券用プロンプトは大容量PDFには向かない

保険証券は、以下のような特徴があります。

  • 数ページ程度
  • 情報が表形式
  • 証券番号、保険期間、補償内容など、項目がある程度決まっている
  • 構造化しやすい

そのため、保険証券では「全部構造化して出す」という方法が成立しました。

しかし、今回扱う保険約款のような全文抽出では、同じ方法は向いていません。

  • トークン上限に引っかかる
  • AIの出力が途中で切れる
  • 「以下略」や「…」が混入する可能性がある
  • 画像PDFの場合、OCR負荷が大きい

これはプロンプトの良し悪しというより、入力サイズと出力サイズの物理的な制限です。

そのため、PDFを分割し、1回あたりの処理量を小さくします。

基本方針

保険約款PDFのMarkdown化では、以下の方針にします。

  • PDFを分割する
  • 1回あたりの出力量を制限する
  • 構造はできるだけ維持する
  • 要約は禁止する
  • 条文番号を保持する
  • 最終的には条文単位でRAGに入れる

約款全文を一度に処理しようとせず、数ページずつMarkdown化していきます。

PDFは章ごとに分割しなくてよい

最初は、約款を章ごとに分割した方がよいのではないかと考えました。

しかし、RAGで使うことを考えると、章ごとに分割する必要はありません。

最終的に重要なのは、章ではなく以下の情報です。

  • 条文そのもの
  • 条文番号
  • 支払条件
  • 免責条件
  • 特約名
  • 参照関係

RAG検索では、「第何章か」よりも、「人身傷害」「通院」「後遺障害」「支払われない場合」などの本文中の意味情報の方が重要になります。

PDF分割は、意味構造のためではなく、AI処理の物理制限を避けるために行います。

分割単位の考え方

今回の用途では、以下のように考えます。

目的 適した単位
AI処理の都合 ページ数
法的・文書構造上の意味 条文
RAG検索 意味のあるテキスト断片
人間の確認 条文番号

つまり、PDFの処理時はページ数で分割し、RAG投入時には条文単位でチャンク化する方針です。

最短ルート

今回の最短ルートは以下です。

  1. PDFを数ページごとに分割する
  2. AIで全文Markdown化する
  3. 条文番号を保持する
  4. RAG投入時に条文単位でチャンク化する
  5. 保険証券の契約内容と照合する

今回はまず、PDF分割から行います。

Popplerをインストールする

PDFの分割にはPopplerを使います。

MacではHomebrewでインストールできます。

brew install poppler

今回の環境では、すでにインストール済みでした。

Warning: poppler 26.01.0 is already installed and up-to-date.

pdfseparate のバージョンを確認します。

pdfseparate -v

実行結果は以下です。

pdfseparate version 26.01.0

最初のPDF分割スクリプト

まず、PDFを10ページずつ分割するスクリプトを作成しました。

vim split_pdf.sh

内容は以下です。

#!/usr/bin/env bash
set -e

INPUT="auto_insurance_policy_terms.pdf"
PAGES_PER_FILE=10

mkdir -p backup pages out

# 元PDFをバックアップ
cp "$INPUT" backup/

# 1ページずつ分割
pdfseparate "$INPUT" pages/page_%03d.pdf

# まとめ直し
count=1
files=()

for f in pages/page_*.pdf; do
  files+=("$f")

  if (( ${#files[@]} == PAGES_PER_FILE )); then
    printf -v num "%03d" "$count"
    pdfunite "${files[@]}" "out/policy_${num}.pdf"
    files=()
    ((count++))
  fi
done

# 余りページ
if (( ${#files[@]} > 0 )); then
  printf -v num "%03d" "$count"
  pdfunite "${files[@]}" "out/policy_${num}.pdf"
fi

公開記事では、実際の保険会社名が入ったPDFファイル名は使わず、auto_insurance_policy_terms.pdf のような汎用名にしています。

実行権限を付けます。

chmod +x split_pdf.sh

実行します。

./split_pdf.sh

しかし、以下のエラーが出ました。

Unimplemented Feature: Could not merge encrypted files ('pages/page_001.pdf')

原因:PDFが暗号化扱いだった

エラーの原因は、元PDFまたは分割後PDFが暗号化PDFとして扱われていたことです。

Popplerの pdfunite は、暗号化PDFの結合に対応していない場合があります。

元PDFの状態を確認します。

pdfinfo "auto_insurance_policy_terms.pdf" | grep -i encrypted

結果は以下です。

Encrypted:       yes (print:yes copy:yes change:yes addNotes:yes algorithm:RC4)

Encrypted: yes になっていました。

パスワードなしで閲覧できるPDFでも、内部的には暗号化扱いになっていることがあります。

qpdfで復号する

暗号化扱いのPDFは、先に qpdf で復号してから分割します。

まず、qpdfをインストールします。

brew install qpdf

次に、PDFを復号します。

qpdf --decrypt "auto_insurance_policy_terms.pdf" "policy_decrypted.pdf"

エラーが出なければ成功です。

復号できたか確認します。

pdfinfo "policy_decrypted.pdf" | grep -i encrypted

結果は以下です。

Encrypted:       no

Encrypted: no になっていればOKです。

復号PDFを対象にして再実行する

split_pdf.sh の対象ファイルを、復号済みPDFに変更します。

INPUT="policy_decrypted.pdf"

再度実行します。

./split_pdf.sh

今度はエラーなく終了しました。

出力先の out フォルダに、10ページごとに分割されたPDFが作成されます。

10ページでは文字量が多かった

10ページごとの分割は成功しましたが、保険約款は文字量が多く、AIでMarkdown化するにはまだ重そうでした。

特に画像PDFはテキストPDFより処理が重くなりやすいです。

そこで、10ページ単位ではなく、3ページ単位に減らすことにしました。

10ページ分割結果を退避する

まず、すでに作成した outpages を退避します。

TS=$(date +"%Y%m%d_%H%M%S")
mv out "out_10p_$TS"
mv pages "pages_10p_$TS"

TS には、現在日時が YYYYMMDD_HHMMSS 形式で入ります。

これで、既存の10ページ分割結果を残したまま、新しく3ページ分割を作成できます。

3ページごとに分割する

split_pdf.sh を編集し、分割ページ数を3に変更します。

PAGES_PER_FILE=3

再度実行します。

./split_pdf.sh

作成されたPDFのページ数を確認します。

pdfinfo out/policy_001.pdf | grep Pages

結果は以下です。

Pages:           3

これで、3ページごとの分割PDFが作成できました。

約款全文Markdown化用プロンプト

次に、約款PDFをMarkdown化するためのプロンプトをファイルに保存します。

vim prompt_policy_fulltext.txt

内容は以下です。

あなたは文書アーカイブ作成専用アシスタントです。

以下のPDFは保険約款(画像PDF)です。
目的は「全文を欠落なくMarkdown化すること」です。

【最重要ルール】
- 要約は禁止
- 表現の言い換え禁止
- 文言の統合・省略禁止
- 解釈・補足・説明の追加禁止
- 原文に存在する文章のみを使用すること

【出力制御】
- 今回はPDF全体ではなく「このPDFファイルに含まれる範囲のみ」を処理してください
- トークン上限に達しそうな場合は、文章を途中で省略せず、
  必ず次の行のみを出力して終了してください

---CONTINUE---

【Markdownルール】
- 見出し(章・節・条)をMarkdown見出しに変換
- 第○条/第○項/第○号 の階層を保持
- 箇条書きはMarkdown形式に変換
- ページ番号や脚注はそのまま記載

【出力形式】
- Markdown本文のみを出力してください
- 前後の説明文・注意文・挨拶文は禁止です

ポイントは、要約を禁止し、原文の欠落をできるだけ防ぐことです。

また、トークン上限に達しそうな場合は ---CONTINUE--- で終わらせるようにしています。

出力先ディレクトリを作成する

Markdownの出力先を作成します。

mkdir -p md

Gemini CLIで試す

まず、Gemini CLIで policy_001.pdf をMarkdown化しようとしました。

gemini -y "
次の指示書を必ず最初に読み、その内容に厳密に従ってください。

指示書ファイル:
- prompt_policy_fulltext.txt

次のPDFファイルを処理してください。
- out/policy_001.pdf

指示書のルールに従い、
Markdown本文のみを出力してください。
" > md/policy_001.md

しかし、以下のようなエラーになりました。

YOLO mode is enabled. All tool calls will be automatically approved.
Error when talking to Gemini API
TerminalQuotaError: You have exhausted your daily quota on this model.

エラーの核心は以下です。

You exceeded your current quota
Quota exceeded for metric: generate_content_free_tier_input_token_count
Quota exceeded for metric: generate_content_free_tier_requests
model: gemini-2.5-pro

これはプロンプトやPDF分割の問題ではなく、無料枠のクォータが当日分なくなっている状態です。

Please retry のような文言が出ることもありますが、日次クォータ枯渇の場合、すぐに回復するとは限りません。

ChatGPTでも試す

次に、ChatGPTでも試しました。

Mac mini側で作業していたPDFを、MacBook Airへコピーします。

scp -r macmini:/Users/snowpool/insurance .

その後、policy_001.pdf をChatGPTへアップロードし、以下のプロンプトで処理しました。

あなたは文書アーカイブ作成専用アシスタントです。

このPDFは保険約款の一部(約3ページ)です。

目的は「全文を欠落なくMarkdown化すること」です。

【ルール】
- 要約しない
- 言い換えしない
- 解釈・補足を加えない
- 原文に存在する文章のみを使用する
- 文の順序を変更しない

【出力】
- 見出し(章・条)はMarkdown見出しに変換
- 第○条/第○項/第○号の構造を保持
- 箇条書きはMarkdownに変換
- ページ番号があれば残す

Markdown本文のみを出力してください。
説明文は禁止です。

出力されたMarkdownを、手元で保存します。

cd md
vim policy_001.md

同様に、policy_002.md も作成しました。

ChatGPTでは抽出が不十分な場合があった

ChatGPT Plusでも試しましたが、画像PDFの内容が十分に抽出できない場合がありました。

保険約款のような文字量の多い画像PDFでは、OCR精度や文書認識の影響がかなり大きくなります。

そのため、Gemini CLIではなく、Webブラウザ版のGeminiでも試すことにしました。

Web版Geminiで抽出する

Web版Geminiでは、分割PDFをアップロードし、同じプロンプトでMarkdown化します。

ただし、途中で出力が停止することがあります。

その場合は、表示された部分までコピーして保存し、続きが必要な場合は以下のように聞きます。

この続きの文章は?

続きが表示されたら、それをさらにコピーしてMarkdownファイルへ追記します。

この作業を繰り返して、分割PDFごとにMarkdownファイルを作成していきます。

当面の運用

当面は、以下の手順で進めます。

  1. 約款PDFを3ページごとに分割する
  2. 分割PDFをWeb版Geminiに読み込ませる
  3. 全文Markdown化プロンプトを実行する
  4. 途中で止まった場合は続きを出してもらう
  5. Markdownを md/policy_001.md のように保存する
  6. 内容を確認しながら次のPDFへ進む

手作業は残りますが、約款の中身を確認しながら進められるため、RAG用データの品質確認にもなります。

今回分かったこと

  • 保険証券用プロンプトは、保険約款のような大容量PDFには向かない
  • 保険約款PDFは分割して処理する必要がある
  • 章ごとではなく、ページ数で分割してよい
  • RAGでは最終的に条文単位でチャンク化する方が重要
  • 暗号化PDFは pdfunite で結合できないことがある
  • qpdf --decrypt で復号してから分割するとよい
  • Gemini CLIは無料枠クォータに引っかかることがある
  • 画像PDFではChatGPTでも抽出が不十分な場合がある
  • Web版Geminiで手動抽出する方法も使える

ハマりどころ

pdfuniteが暗号化PDFを結合できない

以下のエラーが出た場合は、PDFが暗号化扱いになっている可能性があります。

Unimplemented Feature: Could not merge encrypted files

pdfinfo で確認し、Encrypted: yes なら qpdf --decrypt を使います。

10ページ分割でも大きすぎる場合がある

保険約款は1ページあたりの文字量が多いため、10ページ単位でもAI処理には重い場合があります。

今回は3ページ単位にしました。

Gemini CLIの無料枠に注意

CLI経由で画像PDFを大量に処理すると、無料枠や日次クォータに引っかかることがあります。

日次クォータ枯渇の場合、短時間待っても回復しないことがあります。

Web版AIでは手作業が必要

Web版GeminiやChatGPTを使う場合、出力を直接ファイルに保存できないことがあります。

その場合は、表示されたMarkdownをコピーし、vim などで md/policy_001.md に保存します。

公開時の注意

この記事では、PDF分割やMarkdown化の技術手順のみを公開します。

以下は公開しない方針です。

  • 実際の約款本文全文
  • 変換後のMarkdown全文
  • 契約者情報
  • 証券番号
  • 住所・電話番号
  • 車両情報など個人に紐づく情報
  • 実際のPDFファイル名

技術手順とサンプルコマンドだけを公開し、実データはローカル環境で扱います。

次にやること

次は、分割PDFごとにMarkdown化を進めていきます。

  • policy_001.pdf から順番にMarkdown化する
  • md/policy_001.md のように保存する
  • 途中で止まった場合は続きを取得する
  • 条文番号が欠落していないか確認する
  • Markdownファイルを結合する
  • RAG投入用に条文単位でチャンク化する

保険証券と約款Markdownを組み合わせることで、「自分の契約で使える補償」と「約款上の支払条件・免責条件」をつなげられるようにしたいです。

まとめ

保険約款PDFをMarkdown化するために、PopplerによるPDF分割、qpdfによる暗号化解除、Gemini CLI・ChatGPT・Web版Geminiでの抽出を試しました。

保険約款のような大容量PDFは、一括処理ではなく、3ページ程度に分割して処理する方が安定しそうです。

また、PDFが暗号化扱いになっている場合は、pdfunite で結合できないことがあるため、先に qpdf --decrypt で復号する必要があります。

Gemini CLIでは無料枠クォータに引っかかりましたが、Web版Geminiを使い、途中で止まった場合は続きを出してもらう方法でMarkdown化を進めることにしました。

当面はこの方法で約款本文をMarkdown化し、最終的には条文単位でRAGに投入できる形へ整理していきます。

コメント

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