保険約款PDFをMarkdown化するために、PDF分割・暗号化解除・AIでの全文抽出を試す

保険約款PDFをMarkdown化するために、PDF分割・暗号化解除・AIでの全文抽出を試す

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

保険証券は数ページで、契約内容の一覧表に近い文書でした。

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

そのため、保険証券と同じプロンプトで一括変換するのではなく、PDFを小さく分割し、1ファイルずつMarkdown化する方針にしました。

この記事では、保険約款PDFをMarkdown化するための技術的な手順のみを扱います。実際の約款本文、契約者情報、証券番号、住所、電話番号などは掲載しません。また、変換後のMarkdown全文も公開せず、ローカル環境でRAG用データとして利用します。

保険証券用プロンプトは約款PDFには向かない

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

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

そのため、「PDF全体を読み取って、決まった形式でMarkdownにする」という方法が成立しました。

しかし、保険約款は違います。

  • ページ数が多い
  • 条文が長い
  • 特約が多い
  • 画像PDFの場合、OCR負荷が大きい
  • 全文抽出ではトークン上限に引っかかりやすい

そのままAIに投げると、途中で省略されたり、出力が切れたり、「以下略」のような文言が混ざる可能性があります。

約款PDFは分割して処理する

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

  • PDFを小さく分割する
  • 1回あたりの出力量を制限する
  • 構造はできるだけ維持する
  • 要約は禁止する
  • 原文に存在しない説明は追加しない

最終的には、3ページ程度ずつ分割してMarkdown化する方針にしました。

章ごとに分割する必要はない

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

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

重要なのは章ではなく、以下です。

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

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

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

最終的な処理方針

今回の処理方針は以下です。

  1. 元PDFをバックアップする
  2. 暗号化されている場合は復号する
  3. PDFを3ページごとに分割する
  4. 分割PDFごとにMarkdown化する
  5. 条文番号を保持する
  6. RAG投入時に条文単位でチャンク化する

この流れなら、ページ数の多い約款PDFでも少しずつ処理できます。

注意:個人情報を含むPDFは扱いに注意する

保険証券や契約内容を含むPDFには、氏名、住所、電話番号、証券番号、車両情報などの個人情報が含まれる場合があります。

外部AIサービスへアップロードする場合は、個人情報や契約者情報が含まれていないか確認し、必要に応じてマスキングしたうえで利用します。

今回の記事では、実際の契約情報を含む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分割スクリプト

まず、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の状態を確認します。

pdfinfo "auto_insurance_policy_terms.pdf" | grep -i encrypted

結果は以下です。

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

Encrypted: yes になっているため、PDFは暗号化扱いです。

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

Popplerの pdfunite は暗号化PDFの結合に対応していないため、先に復号する必要があります。

qpdfをインストールする

PDFを復号するため、qpdf を使います。

brew install qpdf

インストール後、以下のコマンドで復号します。

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

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

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

pdfinfo "policy_decrypted.pdf" | grep -i encrypted

結果は以下です。

Encrypted:       no

Encrypted: no になったので、復号できています。

復号PDFを対象にして再分割する

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

INPUT="policy_decrypted.pdf"

再度実行します。

./split_pdf.sh

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

出力先の out フォルダに、分割されたPDFが作成されます。

10ページでは文字量が多すぎた

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

画像PDFの場合、OCR負荷や入力トークン量が大きくなります。

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

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

まず、すでに作成した10ページ単位の出力を退避します。

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

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

これにより、既存の outpages を日時付きフォルダとして退避できます。

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本文のみを出力してください
- 前後の説明文・注意文・挨拶文は禁止です

ポイントは、要約や補足を禁止し、原文の保持を最優先にしていることです。

ただし、変換後の約款全文Markdownは公開せず、ローカル環境でRAG用データとして利用します。

AIでMarkdown化を試す

出力先ディレクトリを作成します。

mkdir -p md

まず、分割した policy_001.pdf をAIで処理します。

Gemini CLIなどのコマンドラインツールを使う場合は、以下のような形で実行します。

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

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

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

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

このように標準出力をリダイレクトすれば、結果を md/policy_001.md に保存できます。

Gemini CLIのクォータエラー

大量のPDFを処理する場合、APIの無料枠や利用上限に引っかかることがあります。

今回も、以下のようなエラーが出ました。

TerminalQuotaError: You have exhausted your daily quota on this model.

エラーの要点は以下です。

Quota exceeded for metric: generate_content_free_tier_input_token_count
Quota exceeded for metric: generate_content_free_tier_requests

これはプロンプトやPDF分割の問題ではなく、API側の無料枠クォータを使い切ったことが原因です。

この場合、少し待ってもすぐには回復しないことがあります。翌日以降に再実行するか、処理対象をさらに小さくするか、別の処理方法を検討します。

Web版AIで手動処理する方法

コマンドラインでの処理が難しい場合は、Web版のAIサービスに分割PDFをアップロードしてMarkdown化する方法もあります。

その場合も、個人情報や契約者情報を含むPDFはアップロードしない、または必要に応じてマスキングしてから使います。

プロンプトは以下のような形にします。

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

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

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

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

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

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

出力されたMarkdownを、手元でファイルに保存します。

cd md
vim policy_001.md

途中で出力が止まった場合

Web版AIで処理する場合、出力が途中で止まることがあります。

その場合は、出力されたところまでコピーして保存し、続きが必要な場合は以下のように聞きます。

この続きの文章は?

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

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

当面の運用

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

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

手作業は残りますが、約款の中身を確認しながら進められるため、変換結果の品質確認には向いています。

今回分かったこと

今回の作業で分かったことは以下です。

  • 保険証券用プロンプトは、約款のような大容量PDFには向かない
  • 約款PDFは3ページ程度に分割した方が処理しやすい
  • 章ごとではなくページ数で分割してよい
  • RAGでは最終的に条文単位でチャンク化するのが重要
  • 暗号化PDFは pdfunite で結合できないことがある
  • qpdf --decrypt で復号してから分割するとよい
  • AI APIには無料枠や利用上限がある
  • Web版AIで手動抽出する方法も使える
  • 画像PDFはOCR品質の確認が重要

ハマりどころ

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

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

以下のようなエラーが出た場合は、暗号化PDFが原因です。

Unimplemented Feature: Could not merge encrypted files

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

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

画像PDFの約款は、1ページあたりの文字量が多いです。

10ページ単位ではまだ重く、途中で止まる可能性があります。

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

AI APIの無料枠に注意

CLIツール経由で大量のPDFを処理すると、無料枠のクォータに引っかかることがあります。

特に画像PDFは入力トークン量が大きくなりやすいため、短時間で上限に達しやすいです。

Markdownファイル保存は手作業になる場合がある

Web版AIを使う場合、出力を直接ファイル保存する仕組みがない場合があります。

その場合は、出力をコピーし、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による暗号化解除、AIでのMarkdown抽出を試しました。

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

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

CLI経由ではAPIの無料枠に引っかかる場合があるため、必要に応じてWeb版AIで手動抽出する方法も併用します。

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

コメント

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