家計簿入力を自動化する

家計簿入力が面倒であるため、スキャナ を購入してレシートを読み込み、OCR した内容を家計簿 Web サービス Zaim に転送させようと思い立った。ざっくりとした方針としては以下の通り。

  1. スキャナでレシートを読み込む (画像は Google Drive に保存されるように設定しておく)
  2. Google Drive に保存されたレシート画像から Google Apps Script (GAS) を用いて OCR を行い、情報を読み取る
  3. 2 の段階で情報の読み取りに失敗したもの、あるいは読み取り結果が明らかに不自然であるものに関しては目視での確認が必要になる。が、いちいちファイルを参照しながら手動で修正するのは面倒であることから、怪しいレシート画像を LINE で送信し、対話型システムでの確認・修正を行う
  4. Zaim の API を叩き、取引明細に登録する

コード全体は こちらのレポジトリ にまとめてあるので、必要に応じて参照されたい。

準備

ディレクトリの作成

  • ScanSnap の自動振り分け機能 (読み取ったものが写真か通常の文書かレシートかを自動で判別してくれる) を利用し、レシートであると見做されたものが Google Drive 内の receipt というフォルダに保存されるように設定しておく。
  • receipt フォルダ内に以下のようにフォルダを作成しておく。
├── archive
├── doubt
└── fail

作業の流れとしては、スキャンされて receipt ディレクトリ直下に保存された画像ファイルに対して GAS による走査が行われ、OCR 結果に応じて archive, doubt, fail の 3 フォルダのいずれかに移動させるようになっている。それぞれのフォルダの役割は以下の通り。

  • archive: OCR が成功し、読み取った合計金額にも不自然さがないものを保存する
  • doubt: OCR 自体はできたものの、合計金額が読み取れないもの、あるいは合計金額がおかしいファイルを一時的に保存する。合計金額の不自然さの基準としては以下が挙げられる。

    • 読み取った金額が 100 円以下 or 10000 円以上 (通常こういう買い物はしないので)
    • 読み取った金額が 100 で割り切れるもの (お預かり金額を拾っていることがあるので)
  • fail: OCR に失敗するもの、あるいは OCR はできたものの何か文字が書いてあっただけでレシートではなかったものの結果を保存する

ログの保存

結果のログ保存用に Google SpreadSheet を新規作成する。「archive」「doubt」「fail」という3つのシートを用意し、それぞれのフォルダに入っているファイルの情報を格納しておく。シートの中身はこんな感じ。

f:id:I-was-a-Ki:20200301135120p:plain
archive シートの中身。Category, Genre は Zaim の設定に倣った
f:id:I-was-a-Ki:20200301135230p:plain
doubt シートの中身。確認が終わるまでの一時的な格納場所なので、通常は空
f:id:I-was-a-Ki:20200301135342p:plain
fail シートの中身。fileId のみを格納

doubt シートに溜まっているものに対して、対話型システムでの確認を行う。確認が済み次第、archive あるいは fail への振り分けが行われる。

各種 API を利用するための準備

  • Google Cloud Vision API の準備: OCR に用いる。こちら などに詳しい。レポジトリ では visionAPI.js に実装してある。
  • Zaim API の準備: こちら を参考にした。zaimAPI.js に実装してある。
  • LINE messaging API の準備: GAS からの利用方法については こちら を参照。自分から何らかのメッセージを bot に送信してみて、それをGAS の doPOST(e) で受け取り、e.source.userId を確認すれば自分のユーザ ID が確認できる。lineAPI.js に実装してある。

実装

  • receipt フォルダを監視し、新規ファイルがあった場合に OCR 処理を行う (processReceipt.js)。OCR 処理の詳細については 過去記事 を参照。このスクリプトに time-driven trigger を設定し、5 分おきに実行することとする。
  • OCR 処理の結果および先述の基準に従って、各フォルダへの移動およびシートへの記録が行われる。
  • 次に doubt シートを監視し、何か溜まっていたらチャットを開始して処理を開始するためのスクリプトを用意する (lineAPI.js での実装)。このスクリプトは 10 分おきに実行されるよう設定しておく。
  • bot との会話は以下のようである。 f:id:I-was-a-Ki:20200301135424p:plain これは誤って「お預り合計」 (400 円) を拾ってしまい、100 で割り切れたことから審査に引っかかったケースである。レシート画像が送られてくるので、ユーザーは表示されている金額から「変更します」「変更しません」のいずれかをタップする (ここでは LINE Template Message を利用した)。「変更しません」の場合はそのまま次のレシートの処理に移り、「変更します」の場合は金額の入力が促される。
  • 処理中のものについては、doubt シートの Flag 列に 1 を入れて分かるようにしておく。
  • 各ファイルの処理の結果、
    • 対話において金額が 0 が設定された場合 (レシートとして意味をなしていない場合)
      • ファイルを fail フォルダへ移動する
      • doubt シートから該当行を削除し、fail シートへ追加する
    • それ以外の場合
      • ファイルを archive フォルダへ移動する
      • doubt シートから該当行を削除し、archive シートへ追加する
      • Zaim への登録

が行われる。

感想など

半年ほど前に構築したものだが、現時点で大きな問題は発生していない。

今回の試みは、ScanSnap に連携した家計簿サービス

  • Zaim: 有料会員にならないとレシートを連続で読み込むことができない
  • Dr. Wallet: オペレータに目視で入力させるのは流石にどうかと思う
  • MoneyTree: レシートと何らかの取引明細を紐付けることはできるが、OCR はしてくれない

という有様だったことに端を発する。(主に節約を目的としているはずの) 家計簿管理にお金を使うのがなんだか勿体なく感じる人、あるいは単に家計簿入力が面倒な人は試してみると楽になるかもしれない。