非エンジニアがGASでOCR抽出して請求書処理自動化にチャレンジしてみた

📝 PICK UP!

職種に関わらずフルリモート実施中、詳しくは下の『採用情報』からチェック✅

みなさま、こんにちは!
虎の穴ラボのFantiaディレクターのK.Fです。

みなさんの中には請求書処理を行っている方もいると思います。
もちろん最終的には経理が対応するものですが、ディレクターも請求書の情報から過去の請求額推移などをとりまとめることがあると思います。

ただ請求書はだいたいPDFで届くので、必要箇所を手作業で転記することも少なくありません。
手作業はミスを助長しますし、手間ですので、何とか自動化(あるいは半自動化)できないかと考え試したので、ご紹介します。

今回はGoogleActionScript(以降GAS)を使いますが、非エンジニアということで、ChatGPTに必要なGASを書いてもらいます。


最初に

結論から言うと、今回私が自動化したかったPDFでは、実現できませんでした・・・
PDFの構成によってはうまくいくと思いますので、興味がある方は一度お試しいただければと思います。

何をしたか

  1. PDFからGASを使ってOCRでテキスト抽出

  2. 抽出したテキストをGASで成形してGoogleスプレッドシートに転記

OCRについて

OCRとは、Optical Character Recognition/Readerの略で、手書きや印刷された文字を読みとって、デジタル上のテキストに変換する技術のことです。

Googleドキュメントには標準機能としてこのOCRの機能が付属しています。
今回はこれを利用して実践しました。

GoogleドキュメントのOCR機能の使い方

すごくシンプルで、テキスト抽出した画像やPDFをGoogleドキュメントで開くだけです。

利用手順

  • Googleドライブ上の対象ファイルを右クリック

  • 「アプリで開く」から「Googleドキュメント」を選択

  • 対象ファイルと同階層に、同ファイル名でGoogleドキュメントが生成され、その中にOCR結果が抽出される
    ※画像ファイルをOCR抽出すると、Googleドキュメント内に画像ファイルが自動的に添付されます

抽出結果

  • 丁寧に書いたメモ(手書き)

    • 読み取り精度は十分だと思います

    • 二重線を引いていても読み取ってくれています
      (逆に不要なこともあると思いますが・・)

  • ある程度雑に書き、表形式のメモ(手書き

    • 当然ではありますが、誤字やおかしな改行は目立ちます

    • 行列の関連も読み取ってはもらえていません

  • PDFの読み取り

    • 文字読み取りはもちろん問題なしです

    • 表組は下記のパターンの崩れが確認できました

      • 表組を無視して抽出し、構成が崩れてしまう

      • ある程度表組認識するが、1列毎にセルが結合される&空白処理が行間処理で行われスプレッドシートに転記すると崩れる

表組が無視された例
セルが統合され空白処理がおかしくなった例

PDFの表組の読み取り精度の問題で、今回私が行いたかった自動化は断念せざるえをえませんでした・・。

仮に、使える形で抽出された場合は、以下のGASでの制御をすることで、スプレッドシートに自動転記ができますので、集計作業を半自動化することができます。

GoogleドキュメントのOCR機能をGASで制御する

事前準備

GAS上でGoogleドライブを操作することになるため、Drive APIを利用する必要があります。これが使える環境かは事前にご確認ください。

手順

  • OCR抽出したいファイル(画像やPDF)を1つのフォルダに格納

  • ActionScriptを新たに作成し、画面左の「サービス」をクリックし、DriveAPIを追加

  • スクリプト記入欄に下記コードを記載
    ※「フォルダID」を下記手順で取得して置き換えてください

    • Googleドライブで作成したフォルダを開く

    • URL内の下記にある「英数字文字列」を記入
      「https://drive.google.com/drive/folders/{英数字文字列}」

  • 実行が終わると、フォルダ内に格納したファイル分だけGoogleドキュメントが生成され、その中にOCR抽出結果が表示されます。

function openAllFilesInFolder() {
  // フォルダのID
  var folderId = 'フォルダID';

  // フォルダ内のすべてのファイルを取得
  var folder = DriveApp.getFolderById(folderId);
  var files = folder.getFiles();

  // 各ファイルをGoogleドキュメントに変換して開く
  while (files.hasNext()) {
    var file = files.next();
    var fileId = file.getId();
    var mimeType = file.getMimeType();

    // ファイルがPDFまたは画像であれば変換して開く
    if (mimeType === 'application/pdf' || mimeType.startsWith('image/')) {
      var docFile = Drive.Files.copy({}, fileId, { 'convert': true });
      var docFileId = docFile.id;
      var docUrl = DocumentApp.openById(docFileId).getUrl();
    }
  }
}

以上でGASでGoogleドキュメント生成までできるので、あとは成形したい形にスプレッドシートに転記するGASをChatGPTに追記してもらうことで、半自動的に集計できるようにもなります。

OCR抽出結果をスプレッドシートに転記する

前述の通り私が使いたかったPDFはうまくいきませんでしたが、スプレッドシートに転記する例を記載しておきます。
当然、表の形式やどのように転記したいかによってコードは大きく変わるので、適宜ChatGPTに聞いてみてください。

OCRにより出力されたGoogleドキュメント

この例では、下記の形で出力されたGoogleドキュメントをスプレッドシートに、AA費~DD費の「請求金額」を転記するコードを例として用意します。

スプレッドシートへの転記指示を加えたGAS

function transferDataFromFilesToSpreadsheet() {
  // フォルダのID
  var folderId = '{PDFを格納しているフォルダID}';

  // スプレッドシートのID
  var spreadsheetId = '{転記先のスプレッドシートID}';

  // フォルダ内のすべてのファイルを取得
  var folder = DriveApp.getFolderById(folderId);
  var files = folder.getFiles();

  // スプレッドシートを取得
  var sheet = SpreadsheetApp.openById(spreadsheetId).getActiveSheet();

  // 列のヘッダーを設定
  sheet.getRange("A1").setValue("年月");
  sheet.getRange("B1").setValue("AA費");
  sheet.getRange("C1").setValue("BB費");
  sheet.getRange("D1").setValue("CC費");
  sheet.getRange("E1").setValue("DD費");
  sheet.getRange("F1").setValue("合計");

  // 行のカウンターを初期化
  var rowCounter = 2;

  // 各ファイルを処理してデータを転記
  while (files.hasNext()) {
    var file = files.next();
    var fileId = file.getId();
    var mimeType = file.getMimeType();

    // ファイルがPDFまたは画像でなければスキップ
    if (mimeType !== 'application/pdf' && !mimeType.startsWith('image/')) {
      continue;
    }

    // Googleドキュメントに変換
    var docFile = Drive.Files.copy({}, fileId, { 'convert': true });
    var docFileId = docFile.id;
    var doc = DocumentApp.openById(docFileId);

    // Googleドキュメントからテキストを取得
    var body = doc.getBody();
    var docText = body.getText();

    // 対象期間を取得
    var periodMatch = docText.match(/対象期間 ([\s\S]+)\|/);
    var period = "";
    if (periodMatch) {
      period = periodMatch[1].trim();
    }

    // 請求金額を取得
    var expensePattern = /(\S+)\s+(\S+)\s+¥([\d,]+)\s+¥([\d,]+)/g;
    var match;
    while (match = expensePattern.exec(docText)) {
      var item = match[1].trim();
      var amount = match[4].replace(/,/g, '');

      switch (item) {
        case "AA費":
          sheet.getRange("B" + rowCounter).setValue(amount);
          break;
        case "BB費":
          sheet.getRange("C" + rowCounter).setValue(amount);
          break;
        case "CC費":
          sheet.getRange("D" + rowCounter).setValue(amount);
          break;
        case "DD費":
          sheet.getRange("E" + rowCounter).setValue(amount);
          break;
      }
    }

    // 年月と合計を転記
    sheet.getRange("A" + rowCounter).setValue(period);
    sheet.getRange("F" + rowCounter).setFormula(`SUM(B${rowCounter}:E${rowCounter})`);

    // 行のカウンターをインクリメント
    rowCounter++;

    // Googleドキュメントを削除
    Drive.Files.remove(docFileId);
  }
}

(補足)
Googleドキュメント自体は残す必要がないため、スプレッドシートへ転記後、削除するコードを入れています。
これはご自身の使い方にあわせて入れる・入れないは判断いただければと思います。

出力結果

実行後、下記のようにB列~E列までが自動で転記されています。
※F列はSUM関数を入れているだけですので、割愛します。

最後に

まとめ

  • 文字読み取りの精度は必要十分

  • 表組の読み取りに難あり

  • ものによっては十分利用できる

総評

断念する結果になりましたが、活用の可能性を感じました。
請求書だけでなく、例えば印刷物で書き出し前の元データが残っておらず画像やPDFなどからテキスト下記起こしたい場合などに使えます。
その場合はGASで制御する必要はないので、手軽に使えるOCRとして非常に便利だと思います。

ご自身の環境で使えると思ったら、ご活用いただけますと幸いです。