「毎月届く請求書を、手入力でExcelや会計ソフトに転記している」・・・そんな作業に悩まされていませんか?
確かに、Windowsの標準機能や無料のOCRソフトで文字を読み取ることは可能です。しかし、読み取ったテキストを結局は1枚ずつコピー&ペーストし、Excelに整理し直す作業は、非常に手間がかかります。また、転記ミスのリスクも避けられません。
そこで今回は、Google Workspaceを活用することで、請求書をGoogle Driveに保存するだけで一括処理できる自動化の方法をご紹介します。
使うのは、Google DriveのOCR機能と、Google Apps Script(GAS)です。Google Driveに画像ファイルやPDFファイルを保存しておけば、ボタン一つで複数枚の請求書からデータを抽出し、Googleスプレッドシートにデータ化・転記できます。GASのサンプルコードも掲載していますので、ぜひご参考になさってください。
「1枚ずつ手動でコピペする手間」から解放されて、経理業務の時間を劇的に短縮しましょう。
目次
Google Apps Script(GAS)で自動化
多くのOCRツールは、画像からテキストを抽出する「一過性の作業」で終わります。しかし、Google Apps Script(GAS)を使うことで、データの「整理」「抽出」「自動転記」「後処理」までを一連の流れとして自動化できます。
| 機能 | OCRのみ | Google Drive OCR + Google Apps Script(GAS) |
|---|---|---|
| 処理枚数 | 1枚ずつ手動で読み取り | フォルダ内の全ファイルを一括処理 |
| アウトプット | テキストデータ(手動でコピペが必要) | スプレッドシートへの自動転記 |
| データ整理 | 人間の手作業による転記・抽出 | 正規表現等による特定データ(金額・日付)の自動抽出 |
| 後処理 | なし | 処理済みファイルを自動で別フォルダに移動 |
処理の概要
自動化の流れ
「請求書の画像(複数) → ワンクリック → スプレッドシートのデータ」という自動化の流れは、次のとおりです。
・Google Apps Script(GAS)で、請求書の数だけ以下の処理を繰返す
1. GASを使ってOCR結果を取得
2. 正規表現等で「金額」「日付」など必要な情報を抽出
3. スプレッドシートの所定の列に自動入力
4. 処理済みファイルを自動移動する
機能ごとのGoogle Apps Script(GAS)コード
使用する機能ごとにGASのコードをご紹介します。
ステップ1. Google Drive OCRでテキスト化する
Google Drive に保存された画像をGoogleドキュメントに変換し、そのテキストを抽出します。
これで紙や画像データからも「読み取り可能なテキスト」として扱えるようになります。
Apps Scriptからこの機能を呼び出すコード例がこちらです。
/**
* Google Drive OCRで画像ファイルをテキスト化する
*/
function ocrInvoice(file) {
// ファイルをOCR処理します(Googleドキュメントに変換)
const resource = {
title: file.getName(),
mimeType: MimeType.GOOGLE_DOCS
};
const options = {
ocr: true,
ocrLanguage: 'ja'
};
const doc = Drive.Files.create(
resource, // 新しいドキュメントのメタデータ
file.getBlob(), // 元の画像ファイルの中身 (Blob)
options // OCR設定
);
const doc = Drive.Files.copy(resource, file.getId(), options);
// 変換したGoogleドキュメントからテキストを取得します
const text = DocumentApp.openById(doc.id).getBody().getText();
// テキスト情報を返却します
return text;
}
ステップ2. 正規表現で「金額」と「日付」を抽出
OCRで得られたテキストから、請求書でよく使われるパターンを探していきます。たとえば、次のようなルールです。
- 金額 → 「¥」「¥」「,」「数字」を含む表記
- 日付 → 「YYYY/MM/DD」「YYYY年MM月DD日」など
Apps Scriptでのサンプルコードは以下の通りです。
function extractInvoiceData(text) {
var data = {};
// 金額を抽出(例: ¥123,456)
var amountMatch = text.match(/¥\s?[\d,]+/);
if (amountMatch) {
data.amount = amountMatch[0].replace(/[¥,\s]/g, '');
}
// 日付を抽出(例: 2025/09/02 や 2025年9月2日)
var dateMatch = text.match(/(\d{4}[\/年]\d{1,2}[\/月]\d{1,2})/);
if (dateMatch) {
data.date = dateMatch[0]
.replace(/[年月]/g, '/')
.replace(/[日]/, '');
}
return data;
}
たとえば「ご請求金額:¥123,456」「請求日:2025年9月2日」といったOCR結果から、次のような形でデータ化できます。
- 金額 → 123456
- 日付 → 2025/9/2
ステップ3. スプレッドシートに自動転記
最後に、この抽出データをGoogleスプレッドシートへ転記します。毎回の作業を自動で記録できるようになります。
function saveToSheet(data) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('請求書一覧');
sheet.appendRow([new Date(), data.date, data.amount]);
}このようにすれば、 処理日付(ログ用)、OCRした日付(請求日)、 金額 が1行ずつ蓄積されていきます。
実際に試してみよう
次のようなフォーマットの異なる2枚の請求書を処理してみましょう。


Google Apps Script(GAS)サンプルコード
今回ご紹介するサンプルコードでは、Google ドライブに保存した請求書の画像をまとめて処理します。
指定したフォルダ内の画像ファイルを OCR にかけてテキスト化し、結果をスプレッドシートに自動で書き出します。
さらに、処理が完了した画像ファイルは「処理済みフォルダ」へ自動的に移動するので、どのファイルが終わっているのか一目で分かります。
請求書の明細情報(項目・数量・単価・金額など)もスプレッドシートに整理されて書き込まれるため、そのまま管理や確認に活用できます。
/**
* 各種設定
*/
const SPREADSHEET_ID = "YOUR_SPREADSHEET_ID"; //スプレッドシートのID
const SHEET_NAME = "YOUR_SHEET_NAME"; // 請求書情報を書き込むスプレッドシートのシート名
const SOURCE_FOLDER_ID = "YOUR_FOLDER_ID"; // 請求書画像を置くフォルダのフォルダID
const DOC_FOLDER_ID = "YOUR_FOLDER_ID2 or ''";//GoogleDocファイルを出力するフォルダのID、空欄時はファイルは保存しない
const PROCESSED_FOLDER_ID = "YOUR_FOLDER_ID3 or ''";//処理済みの請求書画像を移動させるフォルダのID、空欄時は移動しない
const SUPPORTED_MIME_TYPES = [MimeType.PNG, MimeType.JPEG, MimeType.GIF, MimeType.BMP, MimeType.PDF];
/**
* カスタムメニューを追加
*/
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("請求書取込")
.addItem("▼ 実行", "ocrInvoiceToSheet")
.addToUi();
}
/**
* 請求書画像をOCR処理し、情報を抽出してスプレッドシートに書き込む
*/
function ocrInvoiceToSheet() {
const sourceFolder = DriveApp.getFolderById(SOURCE_FOLDER_ID);
for (let mimeType of SUPPORTED_MIME_TYPES) {
const files = sourceFolder.getFilesByType(mimeType);
while (files.hasNext()) {
const file = files.next();
// OCR処理して、ドキュメントに変換、テキスト情報を取得
const text = ocrInvoice(file);
// 取得したテキスト情報を解析(正規表現等)して各項目の情報を抜き出す
const data = extractInvoiceData(text);
// シートに追記
saveToSheet(data);
// 後処理:処理が完了した画像ファイルを「processed」フォルダに移動します。
if (PROCESSED_FOLDER_ID) {
const processedFolder = DriveApp.getFolderById(PROCESSED_FOLDER_ID);
file.moveTo(processedFolder)
}
}
}
}
/**
* Google Drive OCRで画像ファイルをテキスト化する
*/
function ocrInvoice(file) {
// ファイルをOCR処理します(Googleドキュメントに変換)
// ☆Drive Api v2 の場合
// const resource = {
// title: file.getName()
// };
// const options = {
// "ocr": true,
// "ocrLanguage": "ja",
// };
// const doc = Drive.Files.copy(resource, file.getId(), options);
// ☆Drive Api v3 の場合
const resource = {
title: file.getName(),
mimeType: MimeType.GOOGLE_DOCS
};
const options = {
ocr: true,
ocrLanguage: 'ja'
};
const doc = Drive.Files.create(
resource, // 新しいドキュメントのメタデータ
file.getBlob(), // 元の画像ファイルの中身 (Blob)
options // OCR設定
);
// 変換したGoogleドキュメントからテキストを取得します
const text = DocumentApp.openById(doc.id).getBody().getText();
// 後処理:作成したGoogleドキュメントを移動または削除します
if ( DOC_FOLDER_ID ) {
const docFolder = DriveApp.getFolderById(DOC_FOLDER_ID);
// docをDOC_FOLDER_IDのフォルダに移動します。
DriveApp.getFileById(doc.id).moveTo(docFolder);
} else {
// docを削除します
DriveApp.getFileById(doc.id).setTrashed(true);
}
// テキスト情報を返却します
return text;
}
/**
* テキストから必要情報を抽出する
* 請求書レイアウト毎に、処理関数を呼び出す
*/
function extractInvoiceData(text) {
// 請求書のレイアウトごとに処理をする
const companyPatterns = {
pattern1 : ['株式会社テストカンパニー', '株式会社A', 'Bサービス'],
pattern2 : ['株式会社 デモテスト', 'C商事'],
};
var p = 'pattern1';
var isFound = false;
for (const [pattern, companys] of Object.entries(companyPatterns)) {
for (const company of companys) {
if (text.includes(company)) {
p = pattern;
isFound = true;
break;
}
}
if (isFound) {
break;
}
}
// 実行する関数を呼び出す
const functionName = 'extractInvoiceData_' + p;
var handler = this[functionName];
if (typeof handler !== 'function') {
handler = extractInvoiceData_pattern1;
}
return handler(text);
}
/**
* 正規表現などを使い必要情報を抽出
* 株式会社テストカンパニー 用
*/
function extractInvoiceData_pattern1(text) {
var data = {};
// 1. 宛名の社名を抽出
// 「ご担当:」の前にある社名を取得
var recipientCompanyMatch = text.match(/^(.*?)\nご担当:/m);
if (recipientCompanyMatch) {
data.recipientCompany = recipientCompanyMatch[1].trim();
}
// 2. 宛名の担当者を抽出
// 「ご担当:」と「様」の間にある名前を取得
var recipientNameMatch = text.match(/ご担当:(.*?)様/);
if (recipientNameMatch) {
data.recipientName = recipientNameMatch[1].trim();
}
// 3. 合計金額を抽出
var amountMatch = text.match(/¥\s?[\d,]+/);
if (amountMatch) {
data.amount = amountMatch[0].replace(/[¥,\s]/g, '');
}
// 4. 請求日付を抽出
var dateMatch = text.match(/請求日:\s*(\d{4}[年\/\-]\d{1,2}[月\/\-]\d{1,2}日?)/);
if (dateMatch) {
data.date = dateMatch[1]
.replace(/[\u200b\s]/g, '') // ゼロ幅スペースや空白を除去
.replace(/[年月]/g, '/')
.replace(/[日]/g, '');
}
// 6. 請求元の社名を抽出
const claimantMatch = text.match(/^(.*)\n〒/m);
if (claimantMatch) {
data.claimant = claimantMatch[1];
}
// 7. 小計・消費税を抽出
const subtotalMatch = text.match(/小計\s*\n.*\n(¥[\d,]+)/);
if (subtotalMatch) {
data.subtotal = subtotalMatch[1].replace(/[¥,\s]/g, '');
}
const taxMatch = text.match(/消費税額\s*\n.*\n(¥[\d,]+)/);
if (taxMatch) {
data.tax = taxMatch[1].replace(/[¥,\s]/g, '');
}
// 8. 明細情報を抽出
// 明細表の開始と終了を特定し、その間のテキストを処理
var lines = text.split('\n');
var startLineIndex = -1;
var endLineIndex = -1;
var startLineIndex2 = -1;
var endLineIndex2 = -1;
// 明細表の開始行(No., 項目)を特定
for (var i = 0; i < lines.length; i++) {
if (lines[i] && lines[i].includes('No.') && lines[i].includes('項目')) {
startLineIndex = i + 1;
break;
}
}
// 明細表の終了行(No., 項目)を特定
for (var i = startLineIndex; i < lines.length; i++) {
if (lines[i] && lines[i].includes('数量')) {
endLineIndex = i-1;
break;
}
}
// 明細表の開始行(数量, 単価, 金額)を特定
for (var i = startLineIndex; i < lines.length; i++) {
if (lines[i] && lines[i+1] && lines[i+2]
&& lines[i].includes('数量') && lines[i+1].includes('単価') && lines[i+2].includes('金額')) {
startLineIndex2 = i+2 +1;
break;
}
}
// 明細表の終了行(数量, 単価, 金額)を特定
for (var i = startLineIndex2; i < lines.length; i++) {
if (lines[i] && lines[i].includes('小計')) {
endLineIndex2 = i-1;
break;
}
}
data.details = [];
if (startLineIndex !== -1 && endLineIndex !== -1) {
for (var i = startLineIndex; i < endLineIndex; i++) {
var no = lines[i].trim();
i++;
if (!lines[i]) {
break;
}
var item = lines[i].trim();
if (/^\d+$/.test(item)) {
break;
}
data.details.push({
no: no,
item: item,
quantity: "",
unitPrice: "",
price: "",
});
}
}
if (startLineIndex2 !== -1 && endLineIndex2 !== -1) {
var detailsRow = 0;
for (var i = startLineIndex2; i < endLineIndex2; i++) {
var quantity = lines[i].trim();
i++;
if (!lines[i]) {
break;
}
var unitPrice = lines[i].trim().replace(/[¥,\s]/g, '');
i++;
if (!lines[i]) {
break;
}
var price = lines[i].trim().replace(/[¥,\s]/g, '');
if ( !data.details[detailsRow] ) {
data.details.push({
no: "",
item: "",
quantity: "",
unitPrice: "",
price: "",
});
}
data.details[detailsRow].quantity = quantity;
data.details[detailsRow].unitPrice = unitPrice;
data.details[detailsRow].price = price;
detailsRow++;
}
}
return data;
}
/**
* 正規表現などを使い必要情報を抽出
* 株式会社デモテスト 用
*/
function extractInvoiceData_pattern2(text) {
var data = {};
// 1. 宛名の社名を抽出
// 「ご担当:」の前にある社名を取得
var recipientCompanyMatch = text.match(/^(.*?)\n御中/m);
if (recipientCompanyMatch) {
data.recipientCompany = recipientCompanyMatch[1].trim();
}
// 2. 宛名の担当者を抽出
var recipientNameMatch = text.match(/^(.*?)\n平素は格別のご高配を賜り厚く御礼申し上げます。/m);
if (recipientNameMatch) {
data.recipientName = recipientNameMatch[1].trim();
}
// 3. 合計金額を抽出
var amountMatch = text.match(/¥\s?[\d,]+/);
if (amountMatch) {
data.amount = amountMatch[0].replace(/[¥,\s]/g, '');
}
// 4. 請求日付を抽出
var dateMatch = text.match(/令和\d+年\d+月\d+日/);
if (dateMatch) {
var date = warekiToDateAny(dateMatch[0]);
if (date) {
data.date = Utilities.formatDate(date, "Asia/Tokyo", "yyyy/MM/dd")
}
}
// 5. 請求元の社名を抽出
const regex = new RegExp(
'※お振込手数料は御社ご負担にてお願い致します。'.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +
'\\n' +
'([^\\n]*)' +
'\\n'
);
var claimantMatch = text.match(regex);
if (claimantMatch) {
data.claimant = claimantMatch[1].trim();
}
// 6. 小計・消費税を抽出
const subtotalMatch = text.match(/小計.*?\n\s*¥?([\d,]+)/);
if (subtotalMatch) {
data.subtotal = parseInt(subtotalMatch[1].replace(/,/g, ""));
}
const taxMatch = text.match(/消費税.*?\n\s*¥?([\d,]+)/);
if (taxMatch) {
data.tax = parseInt(taxMatch[1].replace(/,/g, ""));
}
// 7. 明細情報を抽出
// 抽出対象の範囲を特定する
// '商品名 / 品目' から '備考欄:' の直前までを対象とする
var isDetailOk = true;
const startKeyword = "商品名 / 品目";
const endKeyword = "備考欄:";
var startIndex = text.indexOf(startKeyword);
var endIndex = text.indexOf(endKeyword);
if (startIndex === -1 || endIndex === -1) {
isDetailOk = false;
}
if (isDetailOk) {
// > 明細部分のテキストを抽出
// '商品名 / 品目' から始まるヘッダー行全体を含めて一旦抽出
var detailBlock = text.substring(startIndex, endIndex).trim();
// > ヘッダー行を除去する
// ヘッダー部分(例: '商品名 / 品目\n数量\n単価\n金額')を除去
// OCRの読み取り誤差でヘッダー行の区切りが不正確な可能性があるため、
// 明細の最初の品目名までで区切る
const headerEndMark = "金額"; // ヘッダーの最後の項目名
detailBlock = detailBlock.substring(detailBlock.indexOf(headerEndMark) + headerEndMark.length).trim();
// > 行ごとに分割し、各行のデータを整形する
// 各行のデータが連続した文字列になっているため、改行文字で分割
let lines = detailBlock.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0 && line !== "."); // 空行を除去
// >> 数値だけの行を格納する配列
const numberOnlyLines = [];
// 数値以外の要素(品目名、数量、単位など)を含む行を格納する配列
const otherLines = [];
// >> 各行をチェックして配列を分ける
lines.forEach(line => {
// 数値判定用の正規表現:
// ^ : 行頭
// [\d,]+ : 数字またはカンマが1回以上続く
// $ : 行末
// ※ 請求書データなので、カンマ区切りも許容します
if (/^[\d,]+$/.test(line)) {
numberOnlyLines.push(line);
} else {
otherLines.push(line);
}
});
// > データを行(レコード)に再構成する
let details = [];
// 数量単位の連結文字は除去
// 連続した4つの要素で1つの明細行を構成
const otherColumnCount = 2;// 品目と数量
for (let i = 0; i < otherLines.length; i += otherColumnCount) {
if (i + otherColumnCount <= otherLines.length) {
let item = {
no : details.length + 1 ,
item : lines[i].replace(/\s*(\.+)\s*/g, '').trim(), // 例外的な文字(.)や余分なスペースを除去
quantity : lines[i+1],
unitPrice : numberOnlyLines[i].replace(/[¥,\s]/g, '') ?? "",
price : numberOnlyLines[i+1].replace(/[¥,\s]/g, '') ?? ""
};
details.push(item);
}
}
data.details = details;
}
return data;
}
/**
* 和暦からDateオブジェクトに変換する
*/
function warekiToDateAny(str) {
const regex = /(令和|平成|昭和)(\d+)年(\d+)月(\d+)日/;
const match = str.match(regex);
if (!match) return null;
const era = match[1];
const year = parseInt(match[2]);
const month = parseInt(match[3]) - 1;
const day = parseInt(match[4]);
let seirekiYear;
switch (era) {
case "令和": seirekiYear = 2018 + year; break;
case "平成": seirekiYear = 1988 + year; break;
case "昭和": seirekiYear = 1925 + year; break;
default: return null;
}
return new Date(seirekiYear, month, day);
}
/**
* スプレッドシートに自動転記
*/
function saveToSheet(data) {
const sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME);
// 請求ヘッダ情報
sheet.appendRow([
new Date(),
data.recipientCompany ?? "",//宛名
data.recipientName ?? "", //宛名・担当者
data.claimant ?? "", //請求者
data.date ?? "", //請求日
data.subtotal ?? "", //小計
data.tax ?? "", //消費税
data.amount ?? "", //合計金額
]);
// 請求明細情報
if (data.details) {
for (const detail of data.details) {
sheet.appendRow([
"",
"",
"",
"",
"",
"",
"",
"",
detail.item ?? "", //明細情報・項目
detail.quantity ?? "", //明細情報・数量
detail.unitPrice ?? "", //明細情報・単価
detail.price ?? "", //明細情報・金額
]);
}
}
}手順
事前準備
- Google ドライブに「請求書画像」フォルダと「処理済み」フォルダを作成します。
- Google スプレッドシートを新規作成します。
・シート名を「請求データ」など分かりやすい名前に変更
・1行目に列タイトル(作業日、宛名、宛名・担当名、請求元、請求日、小計、消費税、合計金額、明細情報・項目、明細情報・数量、明細情報・単価、明細情報・金額)を入力しておきます
Google Apps Script(GAS)の設定
- スプレッドシートのメニューから 拡張機能 → Apps Script を開きます。
- 上述したサンプルコードを貼り付け、スプレッドシートIDやシート名、各フォルダIDを自分の環境に合わせて編集します。
- Advanced Drive Service を有効化します。
・エディタ左側のメニューで「サービス」を選択
・「+ 新しいサービスを追加」をクリック
・一覧から「Drive API」を探して追加
・バージョンが「v3」であることを確認
・追加後は必ずプロジェクトを保存してください(保存しないと反映されません)
実行準備
- 請求書の画像ファイルを、先ほど作成した「画像保存用フォルダ」にアップロードします。
OCRの実行
- スプレッドシートを開くと、新しく「請求書取込」というメニューが追加されています。
- メニューから 請求書取込 → 実行 をクリックすると、OCR処理が開始されます。
- 結果がスプレッドシートに自動で反映されます。
処理実行すると、スプレッドシートには請求書のデータが自動入力されました。

うまく活用するポイント
必要な情報取得のために、正規表現等の調整
OCRで取得したテキストに合わせて、正規表現やキーワードで補正しましょう。
たとえば、金額は「¥マーク」や「カンマ」込みで抽出されるので、コード内でクリーニングしましょう。
また、請求書フォーマットは会社ごとに異なります。請求書に合わせて情報取得できるように調整しましょう。(サンプルコード中の extractInvoiceData 関数では、請求書の発行元(会社)に応じて、異なるフォーマットに対応できるよう処理を自動で切り替えています。)
Google Apps Script(GAS)実行の自動化
今回はスプレッドシートに追加したメニューをクリックして実行しますが、これを自動化することもできます。
GASのトリガーに登録することで、毎日決められた時間に自動実行することも可能です。
GASのトリガー設定とは
GASの「トリガー」とは、スクリプトを自動的に実行するための仕組みです。通常、GAS のコードは手動で「▶実行」ボタンを押さないと動きませんが、トリガーを設定することで、特定のタイミングやイベントに合わせて自動的にスクリプトを動かすことができます。
トリガーの種類
GAS では主に次の2種類のトリガーが使えます。
1. 時間主導型トリガー(時間ベースのトリガー)
設定した時刻や間隔でスクリプトを自動実行します。
例:
・毎日午前9時に自動実行
・1時間ごとに実行
・毎週月曜日に実行
2. イベントトリガー(操作に反応するトリガー)
スプレッドシートやフォームの動作に応じてスクリプトを実行します。
例:
・スプレッドシートが開かれたとき(onOpen())
・セルが編集されたとき(onEdit())
・Google フォームの回答が送信されたとき(onFormSubmit())
補足
無料アカウントではトリガー実行の回数や頻度に制限があります。

Googleフォーム連携
「請求書の画像をアップロード」するフォームを作れば、簡単に画像をGoogle Driveに保存できます。
会計ソフト連携への発展
スプレッドシートにデータ化できれば、その後の会計ソフトや販売管理システムへの連携も、ローコードツールなどを使うことで実現できます。
残念ながら、現時点でOCRは100%正確ではありません。たとえば「手書き文字は認識精度が低い」「レイアウトが複雑な請求書は想定通りに抽出されないこともある」といった課題があります。ですが、「毎回ゼロから入力するよりは格段に効率的」なのは間違いありません。人が最終的に確認する仕組みを残すことで、安全性を保ちつつ効率的な作業を実現しましょう。
まとめ
- Google Drive OCRとGoogle Apps Script(GAS)を使うと、「複数ファイルの自動一括処理とデータ転記」が可能
- 手入力によるミスと時間を大幅に削減
- サンプルコードをコピー・ペーストし、請求書のフォーマットに合わせた正規表現等の調整をすれば、すぐに導入可能(Google Workspace環境があれば、追加費用は掛かりません)
弊社 株式会社システムエンジニアリング では、ご相談や実装サポートも行っております。
「うちの請求書フォーマットでも自動化できる?」「使っている会計ソフトに合わせたデータ化できる?」という方は、お気軽にお問い合わせください。


コメント