本日0件のお問い合わせがありました。

✉️FIELDにお問い合わせ

システム開発

Google Apps Script × GPT-4o で請求書PDFを自動解析してスプレッドシートに記録する方法

PDFの請求書ファイルを自動で読み取り、GPT-4o(OpenAI)に渡して情報をJSON形式で解析し、Googleスプレッドシートに記録するシステムの構築手順を解説します。

結論こんな感じのことができます.
↓1分ですが見てみてください。

適格事業者のIDがまだ支店がきたりプロンプトやその前の処理はまだ改善の余地はありますが
けっこういいところまでできてますよね。


🎯 実現すること

  • Google Drive に保存された顧客別フォルダ内の請求書(PDF)を読み込み
  • PDFを Google Docs に変換し、全文テキストを抽出
  • テキストを GPT-4o に送信し、必要な請求情報(例:日付、請求元、請求先、金額など)を抽出
  • 抽出結果を Google スプレッドシートの指定シートに記録

🛠 事前準備

1. スプレッドシートを準備

  • スプレッドシートを開く
  • [拡張機能] → [Apps Script] をクリックし、スクリプトエディタを開く

2. Advanced Drive API を有効化

Drive API を使用するため、以下の手順で「Drive API」を有効化します。

手順:

  1. スクリプトエディタ上部のメニューから「拡張機能」→「サービス」
  2. 「+サービスを追加」をクリック
  3. 「Drive API」を検索して選択し、「追加」をクリック(名前:Drive

これにより、Drive.Files.insert() が使用できるようになります。


🔑 3. OpenAIのAPIキーを取得

  • https://platform.openai.com/ にログインし、APIキーを取得します
  • スクリプト内の以下の変数に貼り付けます:
javascriptコピーする編集するconst GPT_API_KEY = "ここにあなたのOpenAI APIキーを貼り付け";

📂 4. Google Driveのフォルダ構成

mathematicaコピーする編集するルート/
└─ 顧客ID(例:123456)/
└─ invoice/
└─ 月(例:4)/
└─ 請求書PDFファイル1.pdf
└─ 請求書PDFファイル2.pdf

こんな感じで4月なら4というフォルダに適当にpdfぶっ込んでおきます。


🧠 5. コードの機能解説

以下、主要な関数の説明です。


execute_get_client_invoice_from_id_and_month()

const client_id = "123456";
const month = "4";
get_client_invoice_from_id_and_month(client_id, month);





get_client_invoice_from_id_and_month(id, month)

const folder = DriveApp.getFoldersByName(id).next()
  .getFoldersByName("invoice").next()
  .getFoldersByName(month).next();


get_invoice_infomation_from_temporaly()

  • temporaly シートに記録されたファイル名を元に、実際のPDFを読み取り、GPTに送り、結果をスプレッドシートに書き出す処理の本体。

🔍 内容:

  1. temporaly にあるファイル名を1件ずつ処理
  2. Google Driveから対象ファイルを探し出す
  3. PDFをGoogle Docsに変換(extractTextFromPDF()
  4. 変換された全文テキストをGPTに渡す(askGPTForInvoiceData()
  5. GPTが返したJSON形式の請求情報を取り出す
  6. clientID_月 の名前のシートに行を追加して記録

extractTextFromPDF(pdfBlob)

// PDFをGoogle Docsに変換し、全文テキストを取得する
  • Drive.Files.insert() を使って PDF を Google Docs に変換します(OCRも対応)
  • 数秒待ってからそのGoogle Docsを開く
  • .getBody().getText() で全文テキストを取得
  • 使用後のファイルはゴミ箱に移動
  • 📌 注意点:
  • Drive API(拡張サービス)を有効化している必要があります。
  • スキャン画像だけのPDFは、変換できても読み取れない可能性あり。

askGPTForInvoiceData(text)

// GPT-4oに全文テキストを投げ、請求情報をJSON形式で受け取る

GPTに渡すプロンプトを生成(「この形式で返せ」と具体的に指定)
GPT-4oにAPIリクエストを送信(fetch())
GPTの返答の中からJSON部分だけを正規表現で抽出
JSONとしてパースして返却(失敗時は空のオブジェクト)


✅ GPTへの出力指示例:
{
"日付": "2025-04-01",
"請求元": "合同会社FIELD",
"請求先": "株式会社TAGAYA",
"適格事業者番号": "T7011203001975",
"金額": "55,000円"
}

📋 出力フォーマット(スプレッドシート)

各請求書ごとに、以下の列が作られます:

画像名(ファイル名)日付請求元請求先適格事業者番号金額

▶️ 実行手順

ステップ1:

execute_get_client_invoice_from_id_and_month() を実行
temporaly シートにファイル名一覧が記録される

ステップ2:

get_invoice_infomation_from_temporaly() を実行
→ GPTによる解析後、請求情報が出力される

const GPT_API_KEY = "sk-fake-mDOk_cvIVyx4IVwdl8HdhlzJROrSRkOn24bnRYgbT3BmochironlbkonocodehakFJMCWdammyvhs2desuyovyl7UYsw_3f6_Xa_UnwepX5p_7eZOO8P2QVdWmgkpptfAA";

function askGPTForInvoiceData(text) {
  const prompt = `以下は日本語の請求書PDFの全文テキストです。内容を読み取り、以下のJSON形式「のみ」を出力してください(前後の説明は禁止)。\n\n{\n  "日付": "",\n  "請求元": "",\n  "請求先": "",\n  "適格事業者番号": "",\n  "金額": ""\n}\n\nPDFテキスト:\n${text}`;
//今回はこういう命令だけど、必要なものを追記、ただ、追記したらスプシの項目も増やさないと

  const payload = {
    model: "gpt-4o",
    messages: [{ role: "user", content: prompt }],
    temperature: 0.2
  };

  const options = {
    method: "post",
    contentType: "application/json",
    headers: {
      Authorization: `Bearer ${GPT_API_KEY}`
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };

  const response = UrlFetchApp.fetch("https://api.openai.com/v1/chat/completions", options);
  const result = JSON.parse(response.getContentText());
  const content = result.choices?.[0]?.message?.content || "";

  const match = content.match(/\{[\s\S]*?\}/); // JSON部分だけ取り出す
  if (!match) {
    Logger.log("GPT出力のJSON解析に失敗:\n" + content);
    return {};
  }

  try {
    return JSON.parse(match[0]);
  } catch (e) {
    Logger.log("GPT出力のJSON再解析に失敗:\n" + match[0]);
    return {};
  }
}

function extractTextFromPDF(pdfBlob) {
  try {
    const resource = {
      title: pdfBlob.getName(),
      mimeType: MimeType.GOOGLE_DOCS
    };
    const file = Drive.Files.insert(resource, pdfBlob, { convert: true });
    Utilities.sleep(3000);
    const doc = DocumentApp.openById(file.id);
    const text = doc.getBody().getText();
    DriveApp.getFileById(file.id).setTrashed(true);
    return text;
  } catch (e) {
    Logger.log("Google Document AI または Drive API による変換に失敗: " + e.message);
    return "";
  }
}

function execute_get_client_invoice_from_id_and_month() {
  const client_id = "123456";//googleドライブにclient_idごとのフォルダがある感じでそのclient_idです
  const month = "4";
  get_client_invoice_from_id_and_month(client_id, month);
}

function get_client_invoice_from_id_and_month(id, month) {
  const folder = DriveApp.getFoldersByName(id).next()
    .getFoldersByName("invoice").next()
    .getFoldersByName(month).next();

  const files = folder.getFiles();
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("temporaly") ||
                SpreadsheetApp.getActiveSpreadsheet().insertSheet("temporaly");
  sheet.clearContents();
  sheet.appendRow(["id", "month", "filename"]);

  while (files.hasNext()) {
    const file = files.next();
    sheet.appendRow([id, month, file.getName()]);
  }
}

function get_invoice_infomation_from_temporaly() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("temporaly");
  const data = sheet.getDataRange().getValues();
  const sheetMap = {};

  for (let i = 1; i < data.length; i++) {
    const [id, month, filename] = data[i];
    const folder = DriveApp.getFoldersByName(id).next()
      .getFoldersByName("invoice").next()
      .getFoldersByName(month).next();
    const files = folder.getFiles();
    let targetFile = null;
    while (files.hasNext()) {
      const file = files.next();
      if (file.getName() === filename) {
        targetFile = file;
        break;
      }
    }

    if (targetFile) {
      const blob = targetFile.getBlob();
      const mimeType = blob.getContentType();
      const allowedTypes = ["application/pdf"];
      if (!allowedTypes.includes(mimeType)) {
        Logger.log(`スキップ: ${filename}(mimeType: ${mimeType})`);
        continue;
      }

      const text = extractTextFromPDF(blob);
      const gptData = askGPTForInvoiceData(text);

      const sheetName = `${id}_${month}`;
      if (!sheetMap[sheetName]) {
        let outputSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
        if (!outputSheet) {
          outputSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet(sheetName);
        } else {
          outputSheet.clearContents();
        }
        outputSheet.appendRow(["画像名", "日付", "請求元", "請求先", "適格事業者番号", "金額"]); //もし取りたいものが他にあるなら上のGPT命令とここの配列を変更
        sheetMap[sheetName] = outputSheet;
      }

      const outputSheet = sheetMap[sheetName];
      const row = [
        filename,
        gptData["日付"] || "",
        gptData["請求元"] || "",
        gptData["請求先"] || "",
        gptData["適格事業者番号"] || "",
        gptData["金額"] || ""
      ];

      outputSheet.appendRow(row);
    }
  }
}

Drive APIのv2 (v3ではだめ)を有効にします。

これ有効にしないとエラーになりますので注意。

請求書の形式にかかわらず、データを取ることが可能です。

バックオフィスの自動化はいずれあらゆる業務を株式会社SoVaが全てを自動化します。

Yamamoto Yuya

プロフェッショナルとしての高いスキルと知識を持ち、誠実さと責任感を大切にする。常に向上心を持ち、新たな挑戦にも積極的に取り組む努力家。