第2章 · 実例 1

100 個の請求書 PDF から金額を抽出する

第 04 章「処理を書く ── AIにPythonで書いてもらう」の主張を裏付ける。

章のどの主張に対応するか

100 個の請求書 PDF から金額を抽出する月次作業: 手作業で 4 時間。 Claude が書いた Python で 3 秒。翌月も同じスクリプトで 3 秒。4,800 倍の差。

(章本文「実例: 数字で見る」より)

実測 1.0 秒。100 件すべて抽出成功。詳細は results.md

やること

  1. 入力を作る: WeasyPrint で 100 件の請求書 PDF(各 A4 1 枚)を生成
    • 宛名、明細表、小計・消費税・合計、振込先、フッターまで
  2. 抽出する: pypdf で各 PDF からテキストを取り出し、正規表現で 請求番号・宛名・合計金額を取る
  3. 集計する: 全 100 件の合計、顧客別ランキング上位 10 社
  4. 検算する: 生成時の正解と、抽出結果の合計が一致するか確認

全部 make all で動く。

構成

example-1/
├── README.md
├── generate_invoices.py   ── 100 件の PDF を WeasyPrint で生成
├── extract_amounts.py     ── pypdf で金額を抽出 → CSV / JSON
├── Makefile
├── results.md
├── pdf/                   ── 入力 (100 ファイル, 約 24 MB)
└── out/
    ├── extracted.csv      ── 抽出結果(全 100 件、列: file, invoice_no, customer, total)
    ├── summary.json       ── 集計結果(合計、顧客別ランキング)
    └── run.log            ── 実行ログ

実行

pip install weasyprint pypdf
make clean && make all

なぜこれが「実例」になるのか

請求書 PDF の金額抽出は、典型的に手作業で残っている事務処理だ。

extract_amounts.py の核心は 30 行:

INVOICE_NO_RE = re.compile(r"INV-2026-(\d{4})")
CUSTOMER_RE   = re.compile(r"請求先[::]\s*(.+?)\s*御中")
TOTAL_RE      = re.compile(r"合計金額[::]?\s*([\d,]+)\s*円")

def parse_pdf(path):
    text = "\n".join(p.extract_text() or "" for p in PdfReader(str(path)).pages)
    return {
        "invoice_no": INVOICE_NO_RE.search(text).group(0),
        "customer":   CUSTOMER_RE.search(text).group(1),
        "total":      int(TOTAL_RE.search(text).group(1).replace(",", "")),
    }

これを Claude に「100 個の請求書 PDF から合計金額を抽出して、顧客別に集計して」 と頼めば書いてくれる。人間は意図を伝えるだけ。実装は AI に渡る。

そして翌月、新しい 100 件が届く。同じスクリプトを python extract_amounts.py で実行 ── また 1 秒で終わる。手作業なら毎月 4 時間ずつ消える。

これが章で言う「繰り返しの仕事が、一回限りの仕事になる」の具体形。


計測結果 — 第 04 章 example-1

実行環境: Linux 6.18 / pypdf 6.10 / WeasyPrint 68.1 / Python 3.x

抽出時間(主目的)

=== 100 個の請求書 PDF から金額抽出 ===
  処理時間: 1.025 秒  (100 ファイル)
  抽出成功: 100 / 100 件
  合計金額: 112,714,129 円
項目 数値
処理対象 100 PDF, 各 A4 1 枚
PDF 合計サイズ 約 24 MB
処理時間(pypdf による全文抽出 + 正規表現) 1.025 秒
抽出成功率 100/100 (100%)
抽出した合計金額 112,714,129 円
生成時の正解金額 112,714,129 円(完全一致)

章本文の主張「Python で 3 秒」よりも速かった(1 秒)。 手作業 4 時間 = 14,400 秒との比は 約 14,000 倍

顧客別ランキング(out/summary.json)

清水運送              10,112,216 円
吉田出版               7,710,720 円
木村電機               7,410,206 円
森精密                7,213,563 円
加藤建設               7,078,098 円
高橋食品               7,072,101 円
斎藤運輸               6,917,039 円
小林技研               6,838,507 円
田中株式会社           6,583,454 円
山田農園               5,829,126 円

抽出結果が CSV と JSON で残るので、翌月そのまま再利用できる。 来月も python extract_amounts.py で 1 秒。来年も同じ。

抽出後のデータ(out/extracted.csv 抜粋)

file,invoice_no,customer,total
INV-2026-0001.pdf,INV-2026-0001,鈴木商店,1054720
INV-2026-0002.pdf,INV-2026-0002,森精密,627000
INV-2026-0003.pdf,INV-2026-0003,清水運送,4297540
INV-2026-0004.pdf,INV-2026-0004,池田化学,1031206
...

CSV になれば、後段の処理は何でもできる。

# 顧客別合計を 1 行で
awk -F, 'NR>1 {a[$3]+=$4} END {for (c in a) print c, a[c]}' out/extracted.csv | sort -k2 -n -r

# 100 万円以上の請求だけ
awk -F, 'NR>1 && $4 > 1000000' out/extracted.csv | wc -l

エラーが出たら

extract_amounts.py を Claude が初回で完璧に書く保証はない。たとえば:

TypeError: extract_text() returned None for page 3

このエラーをそのまま Claude に貼ると、p.extract_text() or "" で None を空文字に置換するコードが返る。それで進む

章本文の言う「エラー文をそのまま Claude に貼る。Claude が直す。これで進む」 の具体形。

再現手順

pip install weasyprint pypdf
sudo apt install fonts-noto-cjk

make clean && make all

PDF 100 件の生成に 数秒、抽出に 1 秒。

ファイル一覧

out/

pdf/

第2章「処理を書く ── AIにPythonで書いてもらう」に戻る