田中力磨 › 一人塾のGAS自動化 実録 › レシートOCR家計簿

レシートを撮るだけで家計簿が埋まる——GeminiのOCRと楽天メール取込

レシートを撮るだけで家計簿の入力が終わる仕組みを、GeminiのOCRとGmail自動取り込みで作った。実際に動いているコードと、重複計上とデータ消失を防ぐために足した工夫を記録する。

2026-09-14 公開 / 滋賀県彦根市の一人塾「総合学習教室ブリッジ」で実際に動いている仕組みの記録 / Google Apps Script(GAS)

何を自動化したか

家計簿の入力で一番面倒なのは、レシートを見ながら日付・店名・金額・品目ごとの費目を手で打ち込む作業だと思う。うちで運用している「ウキウキ家計簿」(GASで作った個人用Webアプリ)では、レシートやスクリーンショットをスマホで撮るだけでこの入力を済ませ、さらに楽天カード・楽天ペイの利用明細メールを自動で取り込んで、何も撮らなくても記録が埋まっていくようにした。

撮影1回あたりの入力時間を計測してはいないため実測値は無いが、体感では「レシートを見ながら日付・店名・金額・費目を1つずつ選ぶ」作業がほぼ無くなり、撮って内容を確認して保存するだけになった。メール取り込みの分は完全に無操作で、キャッシュレス払いの記録は何もしなくても翌日には家計簿に載っている。

仕組み(データの流れ)

「塾 GAS 自動化」というより家計簿寄りの話だが、しくみ自体は「Google Apps Script スプレッドシート 同期」の文脈とも重なる部分が多い。レシート撮影とメール取り込みの2経路があり、最終的には同じ1つのデータファイルに合流する。

経路読み取り元実行場所
レシート撮影スマホのカメラ・アルバム画像ブラウザ内(クライアントJS)からGemini APIへ直接送信
楽天カード明細Gmail「カード利用のお知らせ」(確定版・速報版)GAS(budgetFetchRakutenCard 等)が定期的にGmailを検索
楽天ペイ明細Gmail「楽天ペイお支払い完了」GAS(budgetFetchRakutenPay)が定期的にGmailを検索

レシート撮影は、画像を長辺1600pxまで縮小してからGemini(gemini-2.5-flash)へ直接投げ、「日付・店名・合計・品目ごとの費目」をJSONで返してもらう。サーバー(GAS)を経由させず、ブラウザから直接APIを呼ぶ構成にしているため、画像をいったんどこかにアップロードする手間が無い。

メール取り込みのほうは、楽天カードには「速報版」(利用直後に届くが店名が無い)と「確定版」(数日遅れで店名つき)の2種類のメールがあり、速報版を仮レコードとして先に記録し、確定版が届いたら店名つきの記録に置き換える2段構えにしている。これにより、家計簿への反映が数日遅れる不満と、店名が分からない不便さの両方を避けている。楽天ペイも別ルートで届くため、同じ買い物が「ペイの完了メール」と「カードの利用明細」の両方に載る問題があり、同じ利用日・同額のペイ記録が既にある場合はカード側の取り込みを飛ばす、という重複排除を挟んでいる。

データの保存先はサーバー側の1つのJSONファイル(Google ドライブ)で、iPhone・Mac のどちらから開いても同じファイルを読み書きする。ここに「①レシート撮影」「②メール自動取り込み(定期実行)」「③手入力」が同時に書き込みに来るため、保存のたびに単純上書きはせず、現状のファイルとマージしてから書く方式にしている(詳細は次章)。

コード(要点)

レシート撮影は完全にクライアント側(ブラウザのJavaScript)で完結する。画像を縮小し、プロンプトと一緒にGemini APIへ投げるだけの構成。

function callOcr(b64, mime) {
  var exCats = DB.categories.filter(function(c){ return c.kind==='expense'; })
    .map(function(c){ return c.name; });
  var prompt = 'あなたは日本のレシートや支払い画面のスクリーンショットを読み取る家計簿アシスタントです。'
    + '画像から支払い1件を読み取り、JSONだけを返してください。\n'
    + '形式: {"date":"YYYY-MM-DD","store":"店名","total":合計金額の数値,'
    + '"items":[{"name":"品名","price":数値,"category":"費目"}]}\n'
    + '費目は次の中から各品目に最も近いものを1つ選ぶ: ' + exCats.join('、') + '。';

  var url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent'
    + '?key=' + encodeURIComponent(DB.settings.geminiKey);
  return fetch(url, {
    method: 'POST', headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
      contents: [{ parts: [{ text: prompt }, { inline_data: { mime_type: mime, data: b64 } }] }],
      generationConfig: { temperature: 0.1, maxOutputTokens: 4096 }
    })
  }).then(function(r){ return r.json(); }).then(function(j){
    var txt = j.candidates[0].content.parts.map(function(p){ return p.text || ''; }).join('');
    var m = txt.match(/\{[\s\S]*\}/);
    return JSON.parse(m[0]);
  });
}

APIキーは本人のGoogleアカウントに保存され、Gemini公式サイトの無料枠で取得したものを使う想定なので、コード自体にキーは書かない。GAS側のメール取り込みは、Gmail検索でヒットしたメール本文を正規表現でパースするだけのシンプルな構成になっている(本文の細かいパース処理はここでは省く)。

function budgetFetchRakutenPay(afterYmd) {
  var q = 'from:(楽天ペイの通知アドレス) subject:(楽天ペイお支払い完了)';
  q += afterYmd ? (' after:' + afterYmd) : ' newer_than:1y';
  var threads = GmailApp.search(q, 0, 200);
  var out = [];
  threads.forEach(function(t){
    t.getMessages().forEach(function(m){
      var rec = parseRakutenPayBody_(m.getPlainBody() || '');
      if (rec) out.push(rec);
    });
  });
  return out;
}

ハマった点と回避策

導入手順

  1. Google AI StudioでGemini APIキーを無料枠で取得する。
  2. 家計簿アプリの設定画面でAPIキーを入力する(本人のGoogleアカウントに保存され、端末間で共有される)。
  3. 楽天カード・楽天ペイのメール取り込みを使う場合は、GAS側のスクリプトプロパティは特に必要無く、Web アプリを公開した本人のGmailをそのまま検索する構成にする(複数人分をまとめたい場合は、家族のメールアドレスを設定項目として保持しておく)。
  4. データ保存用にGoogle ドライブへJSON形式のファイルを1つ用意し、読み込み・保存の2つの関数(DriveApp.getFilesByName で検索して読む/マージしてから書く)をGAS側に用意する。
  5. 保存関数は必ず「現状とのマージ」を経由させる。複数の書き込み元がある場合、単純な setContent による上書きは避ける。
  6. 日次バックアップとして、保存ファイルの日付つきコピーを一定世代数だけ残しておくと、事故が起きたときに貼り戻して復旧できる。

よくある質問

Q. Gemini APIの利用は有料ですか?

無料枠の範囲で個人の家計簿用途には十分足りている。詳しい料金体系はGoogle公式のページを確認してほしい。

Q. レシートの品目を1つずつ費目分けしてくれますか?

する。スーパーのレシートのように食品と日用品が混ざっている場合は、品目ごとにAIが費目を割り当て、金額の大きい費目を代表として全体のカテゴリにも反映する。違っていればその場で選び直せる。

Q. メールの書式が変わったら壊れませんか?

正規表現でのパースなので書式変更には弱い。実際にコロン記号が全角・半角どちらでも来ることがあったため、両方を受け付けるようにするなど、頑丈さは実際に届いたメールを見ながら少しずつ足している。完全に無音で止まらないよう、取り込み失敗はエラーとして拾い、件数と合わせて確認できるようにしておくとよい。

この仕組みを、もっと詳しく