保険約款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投入時には条文単位でチャンク化する方針です。
最短ルート
今回の最短ルートは以下です。
- PDFを数ページごとに分割する
- AIで全文Markdown化する
- 条文番号を保持する
- RAG投入時に条文単位でチャンク化する
- 保険証券の契約内容と照合する
今回はまず、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ページ分割結果を退避する
まず、すでに作成した out と pages を退避します。
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ファイルを作成していきます。
当面の運用
当面は、以下の手順で進めます。
- 約款PDFを3ページごとに分割する
- 分割PDFをWeb版Geminiに読み込ませる
- 全文Markdown化プロンプトを実行する
- 途中で止まった場合は続きを出してもらう
- Markdownを
md/policy_001.mdのように保存する - 内容を確認しながら次の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に投入できる形へ整理していきます。

コメント