Google Apps ScriptでGoogleドライブ上のファイルにOCRを行ってスプレッドシートに書き込んでみる

2022.05.18

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Googleドライブ上の請求書ファイルを読み取り、Googleスプレッドシートに出力するまでを検証した時のことをメモとして残そうと思います。

※ OCRにはLINE CLOVA OCRのAPIを利用していますので、別途契約が必要になります

Googleドライブ上のファイル読み込み、OCR APIの実行、Googleスプレッドシートに出力する操作は全てGoogle Apps Scriptで行ってみました。

準備

  • Googleドライブにフォルダを作成しておく
  • 請求書ファイルをGoogleドライブに保存しておく
  • 出力用のGoogleスプレッドシートを作成しておく

やってみる

Apps Scriptのプロジェクト作成

出力用のGoogleスプレッドシートをブラウザで開き、

メニューの拡張機能->Apps scriptをクリックします。

このように、新規のプロジェクトが開きます。ここにあるgsファイルに色々な処理を書いていくことにします。

Googleドライブ上のファイル読み込み

Apps ScriptでGoogleドライブを扱うときは、DriveAppクラスを利用できます。

作成したフォルダに対してアクセスするときは、

const folderId = "<<フォルダID>>"

const folder = DriveApp.getFolderById(folderId)

getFolderById()にフォルダーのIDを指定して取得できます。

※ フォルダーに関する情報取得は、 メソッド一覧を参照

例)

Logger.log(folder.getName());
Logger.log(folder.getId());
Logger.log(folder.getOwner());

フォルダにあるファイルを取得するには、

getFiles() を実行することでフォルダーの子であるすべてのファイルのコレクションを取得できました。

const files = folder.getFiles()
Logger.log(files);

ファイル個別の情報へのアクセスは、

while (files.hasNext()) {
    var file = files.next();
    Logger.log(file.getName());
}

で行えます。

CLOVA OCRのAPIにリクエストする

請求書データのOCRを行うためにCLOVA OCRのInvoice APIにApps Scriptからリクエストを行います。

※ Invoice APIに関しては以前ブログに書いたので、ここでは割愛します

REST APIなので、httpリクエストを実行する必要がありますが、Apps Scriptからhttpリクエストを行うには、

UrlFetchAppクラス を使用します。

例) フォームデータをPOSTリクエストする例

// Make a POST request with form data.
var resumeBlob = Utilities.newBlob('Hire me!', 'text/plain', 'resume.txt');
var formData = {
  'name': 'Bob Smith',
  'email': 'bob@example.com',
  'resume': resumeBlob
};
// Because payload is a JavaScript object, it is interpreted as
// as form data. (No need to specify contentType; it automatically
// defaults to either 'application/x-www-form-urlencoded'
// or 'multipart/form-data')
var options = {
  'method' : 'post',
  'payload' : formData
};
UrlFetchApp.fetch('https://httpbin.org/post', options);

※ 初回実行時、以下のように外部サービスへの接続に対するリクエストの承認を求められますので、許可する必要があります。

では、実際にCLOVA OCRのInvoice APIにリクエストしてみます。

Invoice APIに対して請求書ファイルをフォームデータとして送っていきます。

var resumeBlob = file.getBlob();

var formData = {
    'file': resumeBlob
};

上記のように、取得したfileオブジェクト内のデータをblobとして取得する必要がありました。

var options = {
    'method': 'post',
    'headers': {
      'x-linebrain-apigw-api-key': <<払い出されたAPIキー>>
    },
    'payload': formData,
    "muteHttpExceptions" : true,
};

Invoice APIに送る必要のあるデータをつけます。

x-linebrain-apigw-api-keyにセットする値は契約時に払い出されたものです。

payloadはJavaScriptのオブジェクトなので、フォームデータとして解釈されます。 contentType を指定する必要はなく、自動的に 'application/x-www-form-urlencoded' または 'multipart/form-data' のいずれかがデフォルトとなります、

try {
    const res = UrlFetchApp.fetch(uri, options);
    console.log(res.getResponseCode())
    console.log(res.getContentText())

  } catch(e) {
    // 例外エラー処理
    console.error(e)
  }

fetchメソッドを実行することでAPIにアクセスします。

getContentText()を出力したところ、上記のようにJSONが返されてきました(成功時)

スプレッドシートにOCRの結果データを出力する

取得したOCRの結果をスプレッドシートに出力していきます。

まず、jsonのテキストをオブジェクトに変換しました。

const res = UrlFetchApp.fetch(uri, options);
var parsed_pdf = JSON.parse(res);

その後、必要なデータを取得し、スプレッドシートに出力するためのデータ(配列)に格納していきます。

請求元情報,⼀般情報にあるデータを格納する例)

var senderInfo = parsed_pdf[0].result.senderInfo.companyName[0].value
var issueDate = parsed_pdf[0].result.generalInfo.issueDate[0].value
var invoiceNum = parsed_pdf[0].result.generalInfo.num[0].value

var generalInfo = [[senderInfo, issueDate, invoiceNum]]
console.log(generalInfo)

明細情報にあるデータを格納する例)

var items = parsed_pdf[0].result.items
var details = []
for(var i in items) {
    var name = items[i].name[0].value
    var count = items[i].count[0].value
    var unitPrice = items[i].priceInfo.taxExcludedUnitPrice[0].value

    details.push([name, count, unitPrice])
}

今回は複数のデータを出力したかったので配列に格納しましたが、実際に出力する際は

RangeクラスsetValues()を使います。

上記で格納したデータを出力する例)

var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];


sheet.getRange("A1:C1").setValues(generalInfo);

const lastRow = details.length;   //行の数を取得する
sheet.getRange(3,1,lastRow,3).setValues(details);

上記のようにスプレッドシートに出力することに成功しました。

コードはApps Scriptのみで済んだので、Webで画面作るほどでもないなーと思うようなワークロードで使えそうなので記録として残しておきます。