0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

f26: レシートの文字起こし ( googleレンズ )

0
Last updated at Posted at 2026-08-22

.

前回の記事

f25: Local LLM で レシートの文字起こしテスト
https://qiita.com/cxfgp/items/f557954856ec67b744a1

の派生メモです

レシートの文字起こしの手法として、
Local LLM などで試行錯誤はしておりますが、
ひとまず手っ取り早く実現できる方法を試しましたので、メモです

簡単にいうと、google サービス連動
( google lens + gmail + gas ( google apps script ) + spreadsheet )
で、文字起こししたデータから
必要部分をスプレッドシートに書き込みます


[ 00. メニュー ]

01. キッチン

02. レシピ

03. 仕込み

04. 調理

05. デザート


[ 01. キッチン ]

  • googleレンズ が使えるアンドロイド端末
    (iOSでも可能と思われますが未検証です)
  • googleドライブ (gas) 環境
  • よくあるコンビニやスーパーなどで受け取る紙の縦長レシート

[ 02. レシピ ]

「手動」 (スマホ内)

01. 紙のレシートをスマホで撮影

02. 撮影した画像を「共有」で「googleレンズ」に渡す
※ googleレンズという独立した名称ではなく、
 「google アプリ」がその画像を受け取って
 レンズの解析機能を起動させる仕組み

01b.jpg

02b.jpg

03. 画像を全選択後、画像についての指示として「文字起こし」を依頼

  • 依頼文:「改行付きで、コピペ用のボタン付きで、枠(コードブロック)に入れて文字起こしして」

03b.jpg

04. 生成された文字起こしデータを「コピー」し
「共有」で自分宛にメール送信

04b.jpg

05b.jpg

「自動」 (gas 上)

05. gas のトリガーを設定して自動実行させる

  • 日間隔、時間隔、分間隔など、お好みに合わせて設定
  • 例(時間隔、毎時の場合): Time-driven / Hour timer / Every hour

06.png

06. 受信メールを監視し、条件にヒットしたらスプレッドシートにインサート

  • 例:指定の店舗名がある場合などに、レシートデータの必要な部分を取得して書き込み

07.png


[ 03. 仕込み ]

スプレッドシートへの書き込み条件

条件(1)

以下の条件をすべて満たす場合に処理が実行されます
(処理完了後、そのメールは既読になります)

  • From: 自分宛て
  • Subject: a1
  • 状態: 未読

条件(2)

テストとして「デニーズ」のレシート取り込みを想定

  • レシートに「デニーズ」が含まれる場合、
    以下の2行を取得・整形して1行にまとめ、
    スプレッドシートにインサート
  • [2026年 8月16日(日) 19:31 00183-1]
  • [合 計 ¥2,794]
function processLensEmail() {

  // 特定の条件のメール
  // (from:me かつ 件名が「a1」かつ 未読)を検索
  var searchQuery = 'from:watashi@gmail.com subject:"a1" is:unread';
  var threads = GmailApp.search(searchQuery);
  
  if (threads.length === 0) {
    return; // 新着メールがなければ処理を終了
  }
  
  // スプレッドシートの指定
  var spreadsheet = SpreadsheetApp.openById('12345ABCDE'); // URLにあるシートID
  var sheet = spreadsheet.getSheetByName('sheet1'); // ワークシート名
  
  // 見つかったメールを1件ずつ処理
  for (var i = 0; i < threads.length; i++) {
    var messages = threads[i].getMessages();
    
    for (var j = 0; j < messages.length; j++) {
      var message = messages[j];
      
      if (message.isUnread()) {
        var rawTextData = message.getPlainBody(); // メール本文を取得
        
        // 【前処理】見えない改行、タブ、連続する空白をすべて半角スペース1つに置換し、前後の余白をトリム
        var textData = rawTextData.replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim();
        
        // タイムスタンプ用の現在日時を取得(日付と時刻を2つのセルに分ける)
        var now = new Date();
        var tsDate = Utilities.formatDate(now, Session.getScriptTimeZone(), 'yyyy/MM/dd');
        var tsTime = Utilities.formatDate(now, Session.getScriptTimeZone(), 'HH:mm:ss');


        // レシートに「デニーズ」が含まれている場合に処理
        if (textData.indexOf('デニーズ') !== -1) {
          
          var formattedDate = "";
          var formattedTime = "";
          var storeName = "dennys";
          var amount = "";

          // 日付と時刻の抽出
          // 2026年 8月16日(日) 19:31 にマッチ(月日の前の空白も考慮)
          var dennysDateTimePattern = /(\d{4})\s*(\d{1,2})\s*(\d{1,2})(?:\s*\([金木水火月土日]\))?\s*(\d{1,2}:\d{2})/;
          var dennysDateTimeMatch = textData.match(dennysDateTimePattern);

          if (dennysDateTimeMatch) {
            var dennysParts = Array.prototype.slice.call(dennysDateTimeMatch);
            dennysParts.shift();
            var year = dennysParts.shift();
            var monthStr = dennysParts.shift();
            var dayStr = dennysParts.shift();
            var rawTime = dennysParts.shift();

            // 月と日を2桁に揃える
            var month = ('0' + monthStr.trim()).slice(-2);
            var day = ('0' + dayStr.trim()).slice(-2);
            
            formattedDate = year + '/' + month + '/' + day;
            formattedTime = rawTime.trim();
          }

          // 合計金額の抽出
          // 「合 計 ¥2,794」のように、合と計の間にスペースがあってもマッチするよう設定
          var dennysPricePattern = /合\s*計.*?[¥¥]\s*([\d,]+)/;
          var dennysPriceMatch = textData.match(dennysPricePattern);

          if (dennysPriceMatch) {
            var dennysPriceParts = Array.prototype.slice.call(dennysPriceMatch);
            amount = dennysPriceParts.pop().replace(/,/g, "");
          }

          // スプレッドシートに1行としてインサート
          if (formattedDate && formattedTime && amount) {
            sheet.appendRow([tsDate, tsTime, formattedDate, formattedTime, storeName, amount]);
          } else {
            sheet.appendRow([tsDate, tsTime, "デバッグ:" + formattedDate, "デバッグ:" + formattedTime, "dennys 解析失敗", "金額:" + amount + " / 本文:" + textData]);
          }
        }


        // 処理済みのメールを既読にする
        message.markRead();

      }

    }

  }

}


[ 04. 調理 ]

自分宛てにメール送信すれば、あとは、gas のトリガーが、
メールを定期チェックして処理してくれますので、
スプレッドシートへの書き込み待ちとなります

ただし、gas のトリガーは、たまに失敗する場合があるのと、
あまり短時間 (5分など) の設定にしておくと
処理が不安定になる場合がありますのでご注意ください

例えば、レシートをまとめて処理したい場合は、
複数メール送信をしておいて、
受信ボックスに入ったことを確認したら、
手動で実行して処理し、普段は、日間隔
Time-driven / Day timer / 1am to 2am など
にしておく、でもいいのかなと思います


[ 05. デザート ]

( レシートの処理条件をどうするかはありますが )
「撮影・文字起こし・メール送信」までの一連の手動操作は、
どのみち撮影する手間に付随するため、ついで感覚で行えます

オンライン前提ですが、googleレンズ の識字率は高品位です
汎用のマルチモーダルモデル、AI-OCR、どちらかということではなく、
両方を「同時に」稼働させてシステム全体で画像を処理しているそうです
(1枚の画像に対して複数のAIが同時に役割を分担している)
(今のところ一度も間違い確認なし)
おそるべし

注意点:google に、レシートに記載されている
「お店情報」や、伏せ字があったりしますが「一部の決済関連情報」等が
取り込まれますので、その点気になる方はご注意ください

( 今回の手法ではありませんが、

  • スマホで撮影
  • PCに撮影画像をローカル連携
  • その PC で Local LLM 処理

が、外部を介さない優位点になります )

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?