個人事業主の皆さん、こんな気持ちになったことないですか?
☑️ 「領収書の紙の山…もう限界」
☑️ 「電帳法の“正解”がわからない」
☑️ 「自動化したいけど IT はちょっと…」
その“もやもや”わかります
毎年1〜3月になると領収書の山と徹夜祭り。
なんてこと、ありませんか?
特に 電子帳簿保存法(電帳法) の改正後は「何をどう保存すれば…」で頭が真っ白。
皆さん、正しく電帳法の対応ができていますか?
「ドキッ」としたそこの貴方!笑
今回、Google ドライブ × Cloud Vision API × Apps Script で
〈入れるだけ → 自動整理 → 完了メール〉が届く仕組みを作成、
無料ツールだけで完結できる運用を作ってみました。
(正確にはAPI利用料が若干かかりますが微々たるものです)
この記事で得られるもの
- 電帳法の本質が10分でわかる
- コピペで動く自動化コード(全文掲載)
- 領収書の保管など面倒な作業からの解放
IT が苦手でも貼るだけで動く—それが本記事のゴールです。
電子帳簿保存法(電帳法)概要
正式名称は「電子計算機を使用して作成する帳簿書類の保存方法等の特例に関する法律」です(長い!)。ですので、電子帳簿保存法(電帳法)と呼ばれます。
対象となる事業者
区分 | 対応義務 |
---|---|
法人(株式会社・合同会社など) | 必須 |
個人事業主(白色・青色申告) | 必須 |
売上規模や従業員数に関係なく、ほぼすべての事業者が対象です
3つの保存方式
保存方式 | 具体例 | 主な要件 |
---|---|---|
① 電子帳簿保存 | 会計ソフトで作成した仕訳帳・総勘定元帳 | ・訂正・削除履歴 ・検索機能 ・操作説明書 |
② スキャナ保存 | 紙領収書を PDF 化して保存 | ・受領後7営業日以内にスキャン ・解像度200dpi 以上 ・タイムスタンプ |
③ 電子取引データ保存 | PDF 請求書、EC の注文メール | ・タイムスタンプ or 改ざん防止策 ・検索性(取引日/金額/取引先) |
2024年改正ポイント
- 電子取引データの電子保存が義務化:PDF を印刷して紙保存は NG
- 猶予措置(宥恕期間)が終了(2023 年 12 月で終了)
- 違反時のペナルティ強化:青色申告 65 万円控除の取消しリスク
遵守しない場合のリスク
- 青色申告特典の取消し
- 税務調査で経費・仕入を 否認 される可能性
- 最悪の場合 重加算税 や 延滞税 を課される
個人事業主がやるべきこと
- 受領形態を判定:紙?電子?でフローを分ける
- ファイル名ルールを統一:
YYYY-MM-DD_取引先_内容_金額.pdf
など - 改ざん防止措置 を決める:Google ドライブの版管理 or タイムスタンプサービス
- 検索要件 を満たす整理方法を用意:フォルダ+ファイル名 or 会計ソフト
会計ソフトを使ってちゃんと領収書を管理していれば基本的には問題ないはずですが、個人事業主だと、自分で管理をしていたり、年に1回青色申告の時にだけ会計ソフトを触ります、という人もいらっしゃるかと思います。そんな方にこそ、実践してほしい方法を今からお伝えします。
領収書が「紙か電子か」で判断。
- 電子なら 電子のまま 改ざん防止+検索機能付きで保存。
- 早いうちに 自動化フロー を作れば、確定申告前に慌てずに済む。
電帳法を「味方」にする3つの視点
目的を“紙レス化”と勘違いしない
そもそも電帳法は紙をなくすことが目的ではありません。目的は以下になります。
- 改ざん防止(タイムスタンプ or 版管理)
- 検索性(日付・金額・取引先で即ヒット)
“決済方法”ではなく“受領形態”で分岐
受領形態 | 保存義務 |
---|---|
紙領収書 | 紙保存 OK |
PDFなど電子での領収書 | 電子保存義務 |
65万控除は“ご褒美”ではなく“担保”
違反で控除剥奪→年 30 万円超の追徴もありえるそうですよ。。
個人事業主がハマる 5 大トラップ
以下によくある「やらかし」をトラップとしてまとめました。
トラップ | 違反内容 |
---|---|
PDF を印刷しバインダー保存 | 電子取引保存違反 |
ファイル名ルールなし | 検索要件違反 |
受領後 7 営業日超でスキャン | スキャナ保存要件違反 |
SaaS 領収書をダウンロードし忘れ | 経費否認 |
タイムスタンプって?何それ?美味しいの? | ・・・ |
知らない間に違反してたってことにならないように手を打ちましょう!
自動化戦略
採用ツールは無料 3 兄弟
有料ツールでも良いなら会計ソフトを使うのが良いです。
でも基本、無料で済ませたいですよね。
ということで以下のツールを使います。
役割 | ツール |
---|---|
保管・版管理 | Google Drive |
OCR(領収書の読み取り) | Cloud Vision API |
自動処理 | Google Apps Script |
処理の流れ
できるだけ簡素化したいということで、以下の流れを考えました。
- Google Driveの所定のフォルダに領収書を格納
- Google Apps Script(GAS)が定期巡回し、Google Spredsheetに自動でログ付けした上で領収書のファイルをルールに基づいた適正な形式でリネーム
領収書の読み取り自体はシンプルに以下の流れで処理を行います。
- PDF→文字列抽出
- 文字列が無い→画像 OCR
- 日付/金額/会社名 を抽出
Google Apps Script 概要
Google が提供する“クラウド上の JavaScript 実行環境”です。
Google Apps Scriptの頭文字をとってGASと呼びます。
- ブラウザだけで開発・実行でき、PC に何もインストール不要
- Gmail / Google スプレッドシート / Drive などを 簡単に自動操作 できる
GAS で出来ること一例
GASを使えばこんなことが簡単にできます。
用途 | 具体例 |
---|---|
自動メール送信 | スプレッドシートの期限リストを毎朝メール |
他サービス連携 | Slack に Drive 更新通知を送る |
スクリプトの作り方
- Google ドライブ を開く
+新規
→その他
→Google Apps Script
をクリック無題のプロジェクト
が表示されれば OK
コード.gs
が最初のスクリプトファイルになります。function myFunction(){ }
がデフォルト関数です。
この辺りの細かい話は今回はわからなくても大丈夫です。
Google Cloud Vision API キー取得・設定マニュアル
GASを利用する準備ができたらCloud Vision APIを取得しましょう。
事前に用意するもの
項目 | メモ |
---|---|
Google アカウント | Gmail で OK |
クレジットカード登録 | 無料枠でも Google Cloud 初回登録に必要(課金は超過時のみ:無料枠(1,000 count/月)を超えたら課金されますが、大量に処理しない限り無課金でいけるはずです) |
Google Cloud Console にログイン
- ブラウザで https://console.cloud.google.com/ へアクセス
- 右上が自分のアカウント名になっているか確認
Cloud Vision API 専用プロジェクトを作成
- 左上 「プロジェクトを選択」 ▶ 「新しいプロジェクト」
- プロジェクト名:
vision-receipt-ocr
(自由に名前をつけて下さい) - 場所はそのまま “組織なし” で OKです
- 「作成」 ▶ 数秒で完了
“誤課金を防ぐ”ために OCR 専用プロジェクト を分けておくと便利です!
API を有効化
- 新しく作ったプロジェクトを選択した状態で
左メニュー ▸「API とサービス」▸「ライブラリ」 - 検索窓に Cloud
Vision API
と入力 - 「Cloud Vision API」 をクリック ▶ 「有効にする」
Cloud Vision APIのところに価格表があるので参考にして下さい。
API キーを発行
- 左メニュー ▸「API とサービス」▸「認証情報」
- 上部の+認証情報を作成 ▶ 「API キー」
- ポップアップに キー文字列 が表示されるのでコピーして残しておく
セキュリティ制限(強く推奨)
万が一のことを考えて、APIに制限をかけることをお勧めします。
左メニュー ▸「API とサービス」▸「認証情報」で出てくるAPIキー部分で新たに作ったAPIキーの名前部分をクリックして以下の設定をします。
ステップ | 操作 | 理由 |
---|---|---|
API の制限 | 「キーを制限」を選び、Cloud Vision API だけにしましょう | 他 API への悪用防止 |
アプリケーションの制限 | 「ウェブサイト」を選び、「script.google.com/* 」を入力 | GAS からの呼び出しのみに限定 |
無料枠と料金の目安
以下のサイトに料金の詳細が記載されています。

無料枠 | 超過料金 (参考) |
---|---|
1,000 ユニット / 月 | $1.50 / 1,000 ユニット |
普通の個人事業主なら多くて月 300〜500 枚程度かと思いますので 完全無料 で収まるケースが多いと考えられます。心配であれば、「課金アラート ($1 で通知)」 を設定しておくと安心です。
Google Spreadsheetの準備
領収書のログ付け用のGoogle spreadsheetを準備して下さい。
ファイルには「台帳」と「ログ」というシートを事前に作成しておいて下さい。
台帳
台帳には実際の領収書の情報を自動で書き込みしていきます。
1行目に以下の項目名を記載しておいて下さい。
取引日 | 取引先名 | 内容 | 金額(円) | ファイル名 |
ログ
ログのシートには実行記録を付けていきます。
エラー情報もこちらで確認可能です。
1行目には以下の項目名を記載しておいて下さい。
記録日時 | ファイル名 | 結果 |
貼るだけで動く! 完全版スクリプト
最終準備
フォルダのIDとspreadsheetのIDを確認します。
フォルダIDは格納するドライブのURLの以下の部分です。
https://drive.google.com/drive/folders/xxxxxx
spreadsheetのIDはシートのURLの以下の部分です。
https://docs.google.com/spreadsheets/d/xxxxxx/edit
実行!
今までの準備ができたら、GASに以下のスクリプトを貼り付けて実行して下さい。
- フォルダ ID/シート ID/API キー/メール先をご自身のものに変更して下さい
processReceiptsSmartDebug
を実行します- 初回は手動で「実行」を押して実行して下さい。承認が必要となります
- 次回以降はGAS側で設定するタイマーで作動します
// ===== 設定値 =====
const FOLDER_ID = 'xxxxxx'; // ←領収書フォルダIDに変更して下さい
const SHEET_ID = 'xxxxxx'; // ←スプレッドシートIDに変更して下さい
const API_KEY = 'xxxxx'; // ←Cloud Vision APIキーに変更して下さい
const ALLOWED_EXTENSIONS = ['pdf', 'jpg', 'jpeg', 'png'];
const REPORT_EMAIL = 'your@email.com'; // ←送信先メールアドレスに変更してください
// ===== メイン処理 =====
function processReceiptsSmartDebug() {
const folder = DriveApp.getFolderById(FOLDER_ID);
const ledgerSheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('台帳');
const logSheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('ログ');
const existingFilenames = ledgerSheet.getDataRange().getValues().map(row => row[4]);
const files = folder.getFiles();
let fileProcessed = false;
let updates = [];
while (files.hasNext()) {
const file = files.next();
const name = file.getName();
const ext = name.split('.').pop().toLowerCase();
Logger.log(`チェック中:${name}`);
if (!ALLOWED_EXTENSIONS.includes(ext)) {
logSheet.appendRow([new Date(), name, 'スキップ:非対応拡張子']);
continue;
}
if (name.startsWith('[済]')) {
logSheet.appendRow([new Date(), name, 'スキップ:処理済み']);
continue;
}
let text = '';
if (ext === 'pdf') {
text = tryDirectTextExtraction(file);
Logger.log(`直接抽出結果(${name}): ${text?.substring(0, 100) || 'なし'}`);
if (!text || text.trim().startsWith('%PDF') || text.length < 30) {
Logger.log(`OCRにフォールバック(${name})`);
text = performOCR(file);
Logger.log(`OCR抽出結果(${name}): ${text?.substring(0, 100) || 'なし'}`);
}
} else {
text = performOCR(file);
Logger.log(`OCR抽出結果(${name}): ${text?.substring(0, 100) || 'なし'}`);
}
if (!text || text.length < 10) {
logSheet.appendRow([new Date(), name, 'エラー:テキスト抽出できず']);
continue;
}
const extracted = extractInfoFromText(text);
Logger.log(`抽出結果(${name}): ${JSON.stringify(extracted)}`);
if (!extracted) {
logSheet.appendRow([new Date(), name, 'エラー:情報抽出できず']);
continue;
}
const { date, vendor, detail, amount } = extracted;
const newFileName = `[済]_${date}_${vendor}_${detail}_${amount}.${ext}`;
if (existingFilenames.includes(newFileName)) {
logSheet.appendRow([new Date(), name, 'スキップ:重複ファイル名']);
continue;
}
file.setName(newFileName);
const record = [date, vendor, detail, amount, newFileName];
ledgerSheet.appendRow(record);
logSheet.appendRow([new Date(), newFileName, '保存成功']);
updates.push(record);
fileProcessed = true;
}
if (!fileProcessed) {
logSheet.appendRow([new Date(), 'ファイルなし', '新規ファイルなし・処理対象なし']);
}
sendUpdateReport(updates);
}
// ===== テキスト抽出:PDF直接 or OCR =====
function tryDirectTextExtraction(file) {
try {
const text = file.getBlob().getDataAsString('UTF-8');
return text;
} catch (e) {
Logger.log('直接テキスト抽出エラー:' + e.message);
return '';
}
}
function performOCR(file) {
try {
const blob = file.getBlob();
const base64 = Utilities.base64Encode(blob.getBytes());
const visionUrl = `https://vision.googleapis.com/v1/images:annotate?key=${API_KEY}`;
const payload = {
requests: [{
image: { content: base64 },
features: [{ type: 'DOCUMENT_TEXT_DETECTION' }]
}]
};
const options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload)
};
const response = UrlFetchApp.fetch(visionUrl, options);
const json = JSON.parse(response.getContentText());
return json.responses[0].fullTextAnnotation?.text || '';
} catch (e) {
Logger.log('OCR失敗:' + e.message);
return '';
}
}
// ===== テキストから情報抽出(強化済み)=====
function extractInfoFromText(text) {
const dateMatch = text.match(/(令和\d+年\s*)?\d{4}年\s*\d{1,2}月\s*\d{1,2}日|\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/);
const amountMatch = text.match(/(?:¥|¥)?\s?(\d{1,3}(?:,\d{3})*|\d+)\s?(?:円)?/);
const vendorMatch = text.match(/(株式会社\s?\S+|\S+会社)/);
const detail = "領収書";
if (!dateMatch || !amountMatch || !vendorMatch) return null;
const date = formatDateForFile(dateMatch[0]);
const vendor = vendorMatch[0].replace(/\s/g, '').substring(0, 15);
const amount = amountMatch[1].replace(/[^\d]/g, '');
return { date, vendor, detail, amount };
}
function formatDateForFile(rawDate) {
const cleaned = rawDate.replace(/[年月日.\-]/g, '/').replace(/\s/g, '');
const d = new Date(cleaned);
const yyyy = d.getFullYear();
const mm = ('0' + (d.getMonth() + 1)).slice(-2);
const dd = ('0' + d.getDate()).slice(-2);
return `${yyyy}-${mm}-${dd}`;
}
// ===== メール送信 =====
function sendUpdateReport(records) {
if (records.length === 0) return;
const body = records.map(r =>
`取引日: ${r[0]}\n取引先: ${r[1]}\n内容: ${r[2]}\n金額: ${r[3]}円\nファイル名: ${r[4]}\n`
).join('\n---\n');
MailApp.sendEmail({
to: REPORT_EMAIL,
subject: "【領収書OCR】処理完了レポート",
body: `以下のファイルが処理され、台帳に追加されました:\n\n${body}`
});
}
これで完成です。
あとは定期的に実行できるように設定しましょう。
GAS スクリプトを定期実行(トリガー)する方法
GAS のトリガーとは?
指定した条件(時間・イベント)で自動的に関数を呼び出す仕組みです。
サーバーや PC を立ち上げておく必要はなく、Google のクラウドが 365 日回してくれます。
代表的なトリガー種類
種類 | 例 | 使いどころ |
---|---|---|
時間主導型 | 毎日 21:00、5 分おき | 領収書フォルダの巡回処理 |
フォーム送信 | Google フォーム回答時 | アンケート自動メール |
スプレッドシート変更 | セル編集時 | 在庫警告メール |
今回は 時間主導型 を使います。
時間トリガー設定手順
例えば、毎晩21時に実行したいとすると以下のように設定します。
- Apps Script エディタを開き、左側ツールバーの時計アイコン(「トリガー」) をクリック
- +トリガーを追加 を押す
- ポップアップで下記を選択 項目 設定値 実行する関数
processReceiptsSmartDebug
(メイン関数名) デプロイ ソース 「Head」(最新バージョン)
イベントのソース 時間主導型
時間ベースのタイプ 「日付ベースのタイマー」
時刻の指定 21:00 (例) - 保存
→ Google アカウント権限許可
→ 完了
これだけ!
以後毎晩 21:00 になると決められたプログラムを実行します。
今回のスクリプトであれば、
1) 所定のフォルダを巡回 → 2) 対象のファイルをOCR → 3) 台帳更新/ログ付け → 4) メール通知
が自動で実行されます。
保存フォルダに領収書のjpgファイルを適当に置いておくだけで勝手に処理してくれます!
*読み取りは完璧にはできない可能性があるので、処理完了メールが見たら間違いがないか確認し、適宜修正しましょう。
*必要に応じて税理士さんにご相談ください。
以上、電帳法の完全攻略法でした!