【Python×Claude】請求書PDFをCSV化する方法|インボイス対応

この記事は約10分で読めます。

取引先から届く請求書PDFを開き、金額や登録番号を会計ソフト取込用のCSVへ手で転記していませんか。月末に数十枚が集中すると、入力に追われるうえに、桁違いや税率の取り違えといったミスも起きやすくなります。

この記事では、PythonからClaude APIを呼び出し、請求書PDFをそのまま読ませてCSVに変換する方法を解説します。ポイントは「構造化出力(決められたJSONの形で必ず返させる機能)」を使うことと、AIの読み取り結果をPythonで機械的に検算することです。

対象は、Pythonを少し触ったことがある経理・総務の担当者や、社内の業務改善を任されている管理職の方です。コードはコピーして使える形で載せています。

スポンサーリンク

Pythonで請求書PDFをCSV化する全体像とClaude APIを使う理由

請求書PDFの読み取りには、従来はOCR(画像の文字を読み取る技術)とルールの組み合わせが使われてきました。ただし取引先ごとにレイアウトが違うため、「どこに合計金額があるか」を一社ずつ設定する手間が残ります。

Claude APIはPDFを直接受け取り、ページ内の文字だけでなく表やレイアウトも踏まえて内容を解釈できます。公式ドキュメントでは、PDFは1ページごとにテキストと画像の両面から処理されると説明されています。そのため、レイアウトが異なる請求書でも同じ指示文で項目を取り出しやすいのが利点です。

今回作る仕組みの流れは次のとおりです。

  • 入力:フォルダに置いた請求書PDF(1ファイル1請求書を想定)
  • 抽出:Claude APIにPDFとJSONスキーマを渡し、項目をJSONで受け取る
  • 検算:Pythonで登録番号の形式、税額、合計金額の整合性をチェックする
  • 出力:CSVに1請求書1行で書き出し、要確認の行には理由を残す

AIは「読み取り担当」、Pythonは「検算担当」と役割を分けるのが設計の肝です。AIに計算や判定をすべて任せず、確定的に判断できる部分はプログラムで確認します。

インボイス項目の抽出設計:構造化出力のJSONスキーマ

まず、請求書から何を取り出すかを決めます。国税庁の資料では、適格請求書の記載事項は次の6点とされています。

  1. 適格請求書発行事業者の氏名または名称と登録番号
  2. 取引年月日
  3. 取引内容(軽減税率の対象品目である旨を含む)
  4. 税率ごとに区分して合計した対価の額と適用税率
  5. 税率ごとに区分した消費税額等
  6. 書類の交付を受ける事業者の氏名または名称

これを踏まえ、CSVの列を次のように設計します。仕訳に必要な支払期日なども加えています。

項目キー 内容 チェック方法
vendor_name 請求元の名称 空欄でないか
registration_number 登録番号 「T+数字13桁」の形式か
issue_date / due_date 請求日・支払期日 日付形式か、期日が請求日より後か
amount_10 / tax_10 10%対象の税抜額と消費税額 税額が税抜額の約10%か
amount_8 / tax_8 8%(軽減税率)対象の税抜額と消費税額 税額が税抜額の約8%か
total 請求金額(税込) 各税抜額と税額の合計と一致するか
note AIが読み取りに迷った点 空欄でなければ人が確認

Claude APIの構造化出力では、リクエストの output_config.format にJSONスキーマを指定します。執筆時点(2026年10月)の公式ドキュメントでは、以前のベータ用ヘッダーは不要とされています。なお、数値の最小値・最大値や文字数の制約はスキーマでは使えないため、そうした確認はPython側で行います。

Claude APIで請求書PDFを読み取るPythonコード

事前準備の手順

  1. Claude Console(API管理画面)でAPIキーを発行する
  2. キーを環境変数 ANTHROPIC_API_KEY に設定する(コードに直接書かない)
  3. ターミナルで pip install anthropic を実行して公式SDKを入れる
  4. 作業フォルダに invoices フォルダを作り、テスト用PDFを3〜5枚置く

抽出部分のコード

以下はPDFを1枚ずつAPIに送り、スキーマどおりのJSONを受け取る部分です。コード中のモデルIDは記述例です。利用時は公式ドキュメントのモデル一覧で最新のIDを確認し、置き換えてください。

import base64, csv, json, re, pathlib
import anthropic

MODEL = 'claude-sonnet-5-5'  # 公式のモデル一覧で最新IDを確認
client = anthropic.Anthropic()  # APIキーは環境変数から読み込む

NUM = {'type': 'integer'}
STR = {'type': 'string'}
PROPS = {
    'vendor_name': STR, 'registration_number': STR,
    'issue_date': STR, 'due_date': STR,
    'amount_10': NUM, 'tax_10': NUM,
    'amount_8': NUM, 'tax_8': NUM,
    'total': NUM, 'note': STR,
}
SCHEMA = {'type': 'object', 'properties': PROPS,
          'required': list(PROPS), 'additionalProperties': False}

PROMPT = '''添付は取引先からの請求書です。指定の項目を抽出してください。
- 日付はYYYY-MM-DD形式。記載がなければ空文字
- 金額は円単位の整数。該当する税率の取引がなければ0
- 登録番号は記載どおり転記。見当たらなければ空文字
- 推測で補った項目や読みにくい箇所はnoteに具体的に書く'''

def extract(pdf_path):
    data = base64.standard_b64encode(pdf_path.read_bytes()).decode('utf-8')
    res = client.messages.create(
        model=MODEL,
        max_tokens=1024,
        output_config={'format': {'type': 'json_schema', 'schema': SCHEMA}},
        messages=[{'role': 'user', 'content': [
            {'type': 'document', 'source': {
                'type': 'base64', 'media_type': 'application/pdf', 'data': data}},
            {'type': 'text', 'text': PROMPT},
        ]}],
    )
    text = next(b.text for b in res.content if b.type == 'text')
    return json.loads(text)

「見当たらなければ空文字」と明示しておくと、AIが値をでっち上げるリスクを下げられます。

抽出結果の自動チェックとCSV出力(ハルシネーション対策)

構造化出力で保証されるのは「JSONの形」までです。中身の金額が正しいかどうかは保証されません。そこで、Pythonで検算して問題のある行に理由を残します。

def check(d):
    issues = []
    if not re.fullmatch(r'T\d{13}', d['registration_number']):
        issues.append('登録番号の形式')
    for amt, tax, rate in [('amount_10', 'tax_10', 0.10), ('amount_8', 'tax_8', 0.08)]:
        if abs(d[amt] * rate - d[tax]) >= 1:
            issues.append(f'{tax}の税額')
    if d['amount_10'] + d['tax_10'] + d['amount_8'] + d['tax_8'] != d['total']:
        issues.append('合計不一致')
    if d['note']:
        issues.append('AIメモあり')
    return issues

rows = []
for pdf in sorted(pathlib.Path('invoices').glob('*.pdf')):
    d = extract(pdf)
    d['file'] = pdf.name
    d['check'] = ' / '.join(check(d)) or 'OK'
    rows.append(d)

with open('invoices.csv', 'w', newline='', encoding='utf-8-sig') as f:
    w = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
    w.writeheader()
    w.writerows(rows)

文字コードを utf-8-sig にしているのは、Excelで開いたときの文字化けを防ぐためです。会計ソフトの取込形式がShift_JIS指定の場合は、取込仕様に合わせて変更してください。

税額チェックは1円未満の差を許容しています。適格請求書では端数処理を税率ごとに1回行うルールのため、請求書単位なら通常1円未満の誤差に収まるはずです。ただし値引きの書き方などで例外は起こり得るので、エラー行は必ず人が原本と照合します。

運用前のチェックリスト

  • 過去の請求書10枚程度で試し、手入力の結果と全項目を突き合わせたか
  • 「check」列がOK以外の行を誰が確認するか決めたか
  • CSVをそのまま取り込まず、人の承認を経てから会計ソフトに登録する流れか
  • 処理済みPDFを別フォルダへ移し、二重処理を防ぐ仕組みがあるか

導入時の注意点・リスク:個人情報と社内ルール

請求書には取引先の口座情報や担当者名など、社外秘の情報が含まれます。API利用時のデータの扱いは、利用規約やプライバシーに関する公式情報を確認し、社内の情報管理規程に照らして判断してください。

APIキーは会社のお金で動く「鍵」です。コードやスプレッドシートに直接書かず、環境変数などで管理します。また、Consoleで利用上限を設定しておくと、想定外の大量実行による請求を防げます。料金はトークン量(文字数に近い単位)で決まり、PDFは1ページあたり数千トークン程度を使うと公式に記載されています。

最後に、AIの抽出結果は「下書き」として扱うことを社内で明文化しましょう。最終的な仕訳の承認者や、電子帳簿保存法に沿った原本PDFの保存方法は、従来どおりのルールで運用することが前提です。

よくある質問(FAQ)

スキャンした紙の請求書PDFでも読み取れますか?

公式ドキュメントでは、PDFの各ページは画像としても解析されると説明されています。そのため、文字情報を持たないスキャンPDFでも読み取れる場合があります。ただし、傾きや解像度の低さは精度を下げる原因です。ページを正しい向きにし、鮮明にスキャンしておくと安定します。

1枚あたりの費用はどれくらいかかりますか?

費用はモデルとページ数で変わります。公式ドキュメントでは、PDFは1ページあたりおおむね1,500〜3,000トークン程度のテキスト分に加え、画像としての処理分も加算されるとされています。正確な見積もりは、トークン数を数えるAPIで実際のPDFを測るのが確実です。

freeeやマネーフォワードに直接登録できますか?

各社の会計ソフトはCSV取込やAPIを提供しており、本記事のCSVを取込形式に合わせれば連携できます。ただし、いきなりAPIで自動登録するより、まずCSVを人が確認してから取り込む運用がおすすめです。精度が確認できてから、登録処理の自動化を検討しましょう。

まとめ:Pythonと請求書PDFの自動化は「AI抽出+機械検算」で

Claude APIのPDF入力と構造化出力を使えば、レイアウトが異なる請求書でも、決まった項目をJSONで取り出せます。そこにPythonの検算を組み合わせることで、登録番号の形式や税額・合計の不整合を自動で洗い出せます。

大切なのは、AIに任せきりにせず「読み取りはAI、検算はプログラム、最終判断は人」と役割を分けることです。まずは過去の請求書数枚で試し、手入力の結果と比べて精度を確かめるところから始めてみてください。

次のアクションとして、今月届いた請求書から5枚を選び、本記事のコードでCSV化してみましょう。うまく動いたら、Gmailからの自動取得や仕訳の異常検知と組み合わせると、月次業務全体の効率化につながります。

関連記事

参考リンク

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