![見出し画像](https://assets.st-note.com/production/uploads/images/136718789/rectangle_large_type_2_38ea16588684ef5b3487ce9d206a1dfe.png?width=1200)
受取請求書から正規表現を使ってのfreee取引作成
請求書をpdf等の電子データで受け取ることも増えてきました。紙の請求書をスキャンしたようなものもありますが、何らかのシステムから出力されたデータである場合も多いと思います。
システムから出力されたpdfファイルは、OCR処理とも相性がよく、レイアウト崩れを無視すれば、テキスト情報の抽出が容易です。
これまでの連載では、OCRで抽出したテキストから生成AIを活用して請求額などの情報を抽出してきました。
今回は、AIを使わないアプローチとして定型の請求書から正規表現で情報を抽出する手法を紹介します。
前提条件
まず、前提として処理したい請求書が安定したOCR処理ができるデータであることが重要です。テキスト抽出が正確にできなければ、実務では使えません。
加えて、レイアウトが安定している定型の請求書であるということも重要です。Drive APIを使ったOCR処理では、往々にして抽出したテキストの配置はバラバラであったりしますが、そのバラバラのなかにも決まったパターンを見いだせれば、正規表現での抽出が使えます。
正規表現とは何なのか?
正規表現とは、特殊な記号を組み合わせて文字列の集合パターンを定義するものです。これによって、狙った文字列の検索・置換・抽出が簡単になります。
これまで正規表現は、学習コストが高く習得が大変でした。しかし、今であれば、ChatGPT等に「正規表現で」と指示することで、簡単に狙った正規表現が得られます。
freee発行の請求書をOCRしてみる
支払先が、freee請求書を使って請求書を送付してきた例を考えてみましょう。
本当は、freee同士であれば『freeeの利用者同士でつながる』という機能があるのですが、私はまだ使ったことがないです…
![](https://assets.st-note.com/img/1712644348724-w0viIxvbkD.png)
今回のケースでは、スクリプトでOCRテキストを取得する前に、人力で安定したテキスト抽出ができるように正規表現を調整した上で、本番の実装をしたいと思います。
まずは請求書のpdfファイルをGoogleドライブの任意のフォルダに保存し、Googleドキュメントで開くことで、OCRしてテキストを確認します。
![](https://assets.st-note.com/img/1712644888548-e0hRcBYPWg.png?width=1200)
ドキュメントで開いた請求書は、レイアウトも文字組みも、めちゃくちゃになっているのですが、一端この出力結果をすべてコピーして、テキストエディタなどに貼り付けます。
![](https://assets.st-note.com/img/1712644986430-CLWTMsheaQ.png?width=1200)
今回は、Googleドキュメントの別ファイルを新規作成して、そこに書式なしで貼り付けました。
![](https://assets.st-note.com/img/1712645043691-UGMQPEY7ij.png)
請求書
透明書店株式会社
1410032
東京都品川区大崎1丁目2−2アートヴィレッジ大崎セントラルタ ワー21F
請求日
請求書番号
2024‑04‑09 2024‑04‑09‑2
下記の通りご請求申し上げます。 件名
freeeラボ(法人)ベーシック
freee lover
141‑0032
東京都品川区大崎1‑2‑2アートヴィレッジ大崎セン トラルタワー 21階
小計
消費税 請求金額
99,900円 109,890円
9,990円
入金期日 振込先
楽天銀行 第一営業支店 724937
摘要 数量 単価 明細金額
システムメンテナンス 1 件 99,900 99,900
備考
/
1 1
内訳
10%対象(税抜) 10%消費税
99,900円 9,990円
今回は単一の税区分であるので、請求金額の109,890円を取得するか、OCRテキストの最後部の10%対象(税抜) 、10%消費税のそれぞれの金額を取得する正規表現の作成を目指します。
こうした抽出対象の選択肢が複数ある場合に、どの部分が正規表現で取得しやすいかの勘所は、場数を踏んで養う必要があります。
今回は、最後部の99,900円 9,990円を取得に挑戦します。
尚、同じfreee発行の請求書でもレイアウトが変わったり、税区分が複数あったりするとOCRしたテキストのレイアウトも変わります。そのため同じ正規表現では、狙った金額を取得できない場合があります。あくまで、内容があまり変わらない受取請求書を対象しましょう。
ChatGPTに正規表現を教えてもらう
OCRテキストをプロンプトに渡して、最後部の99,900円 9,990円を抽出する正規表現をChatGPTに教えてもらいます。
しかし素直に質問しても、なかなか狙った回答は得られません。
以下のテキストから最後部の99,900円 9,990円のテキストを取得する正規表現を教えて
#テキスト
![](https://assets.st-note.com/img/1712646191269-Zl9SxVMKsZ.png?width=1200)
ということで、数値をひとつひとつ丁寧に取得していくようにプロンプトを調整します。
以下のテキストから"10%対象(税抜) 10%消費税 "の文字列の後に出現する最初の金額の数値を取得する正規表現を教えて。またGoogle Apps Scriptで検証できるテスト関数も出力してください。
#テキスト
![](https://assets.st-note.com/img/1712646710106-Re85LtSIsP.png?width=1200)
Googleドキュメントのコンテナバインドスクリプトでテストする
ChatGPTが出力してくれたGASは、あまり好みの書き方でないですが、今回の本題とはずれるので、いったん人力で微調整して、正規表現が狙った数値を取得できるかチェックします。
テキストを変数に入れて与えてもよいのですが、書式なしで貼り付けたGoogleドキュメントのコンテナバインドスクリプトでテスト関数を作成します。
![](https://assets.st-note.com/img/1712647546579-Wt5vjMjdSi.png?width=1200)
/**
* 現在開いているGoogleドキュメントから特定のパターンに一致する文字列を検索し、結果をコンソールに出力する関数
* 検索パターンは「10%対象(税抜) 10%消費税」という文字列に続く金額を抽出する正規表現を使用
* 一致する文字列がある場合はその金額部分をコンソールに出力し、ない場合はマッチしなかったことを示すメッセージを出力
*
* @return {void} 何も返さない
*/
function testRegExp01() {
// アクティブなGoogleドキュメントからテキストを取得
const textOcr = DocumentApp.getActiveDocument().getBody().getText();
// 検索パターンを定義
const pattern = /10%対象\(税抜\) 10%消費税\s*(\d+,\d+)円/;
// テキストからパターンに一致する部分を検索
const matches = pattern.exec(textOcr);
// 一致する部分があるかどうかを判断
if (matches && matches.length > 1) {
// 一致する部分があった場合、その金額をコンソールに出力
console.log(matches[1]);
} else {
// 一致する部分がなかった場合、メッセージをコンソールに出力
console.log("マッチする文字列がありませんでした。");
}
}
![](https://assets.st-note.com/img/1712647590062-ukI7m6ZRSz.png)
カンマがそのまま残っているのが気になりますが、これくらいは後で調整可能ですので、ひとまずOKとします。
指定した文字列の後に出現する2番目の金額の数値を取得する
続いて、指定した文字列の後に出現する2つ目の金額を取得するスクリプトを依頼します。
テスト関数は以下を採用しました。
〜中略〜
続いて"10%対象(税抜) 10%消費税 "の文字列の後に出現する2番目の金額の数値を取得する正規表現を教えて。
![](https://assets.st-note.com/img/1712647962782-jDpI4DYBQo.png?width=1200)
/**
* 現在開いているGoogleドキュメントから特定のパターンに一致する2番目の金額を検索し、結果をコンソールに出力する関数
* 検索パターンは「10%対象(税抜) 10%消費税」という文字列に続く2番目の金額を抽出する正規表現を使用
*
* @return {void} 何も返さない
*/
function testRegExp02() {
// アクティブなGoogleドキュメントからテキストを取得
const textOcr = DocumentApp.getActiveDocument().getBody().getText();
// 検索パターンを定義
const pattern = /10%対象\(税抜\) 10%消費税\s*\d+,\d+円\s*(\d+,\d+)円/;
// テキストからパターンに一致する部分を検索
const matches = pattern.exec(textOcr);
// 一致する部分があるかどうかを判断
if (matches && matches.length > 1) {
// 一致する部分があった場合、その金額をコンソールに出力
console.log(matches[1]);
} else {
// 一致する部分がなかった場合、メッセージをコンソールに出力
console.log("マッチする文字列がありませんでした。");
}
}
![](https://assets.st-note.com/img/1712648027984-Ex7DnYmjJj.png)
カンマを含まない数値型として出力させる
それぞれイイ線いってますが、後ほどfreeeに取引登録するには、カンマを含まない数値型の出力が必要です。取得した金額の文字列からカンマを削除し、さらにそれを数値型に変換するステップを追加します。
// console.log(matches[1]); // この部分を以下に変更
// 一致する部分があった場合、カンマを除いて数値に変換
const amount = Number(matches[1].replace(/,/g, ''));
// 変換した数値をコンソールに出力
console.log(amount);
税抜額や消費税額がカンマを含まない桁数の場合
ChatGPTは博識で便利ですが、指示されていないことを汲み取る能力が高くなく、おっちょこちょいなところがあります。
今回のケースだと税抜額や消費税額がカンマを含まない桁数の場合には、そもそもの正規表現が意図した動作をしません。
![](https://assets.st-note.com/img/1712649231084-WpV2Aqk1Om.png)
![](https://assets.st-note.com/img/1712649175260-b7tjS8PbR0.png)
カンマが存在する場合もない場合もマッチするよう正規表現を調整します。
この記事が気に入ったらサポートをしてみませんか?