Softex CelwareTech Blog
Google Apps Script2026-07-09

Excel VBAからGAS Web APIへTSVでPOSTする実装定石

Excel VBAからGAS WebアプリへTSVを直接POSTするときの認証、302リダイレクト、UTF-8、全行検証、LockServiceの定石をまとめます。

GASExcel VBAWeb APITSVUTF-8業務アプリ連携

はじめに

Excel VBAで作った入力ブックから、Google Apps ScriptWeb APIへデータを直接渡したい場面があります。 CSVを一度書き出してGoogle Driveへ置く方法でも動きますが、アップロード忘れ、置き場所ミス、読込タイミングのズレが起きやすくなります。

この記事では、Excel VBAからGAS WebアプリへTSVを直接POSTする実務向けの構成を整理します。

先に押さえる定石

論点定石理由
認証トークンを本文に入れるGASのdoPostでは任意ヘッダーを前提にしにくいため
HTTPクライアントMSXML2.ServerXMLHTTP.6.0GASの/execで302リダイレクトが絡むため
Content-Typetext/plain;charset=utf-8生文字列をe.postData.contentsで受けやすい
データ形式TSVVBA側でJSONライブラリを増やさずに済む
応答OK\t件数\t年月 のようなプレーンテキストVBA側のパースが簡単
文字コードUTF-8バイト配列日本語をそのままsendすると文字化けしやすい

リクエスト形式

本文は、設定ヘッダーとTSV本体を---で分けます。

token: <APIトークン>
action: importRows
---
日付	担当者	開始	終了	区分1	区分2
2026/02/01	サンプル担当者A	9:00	12:30	通常
2026/02/01	サンプル担当者B	13:00	18:00	通常

日付は2026/02/01のようなスラッシュ形式にしておくと、JavaScript側でローカル日付として扱いやすくなります。 2026-02-01はUTC解釈で日付がずれることがあるため、業務日付では注意が必要です。

GAS側の受け口

GAS側では、本文を正規化してヘッダー部分とTSV部分へ分けます。 UTF-8 BOMが残ると先頭キーがtokenではなくなり認証に失敗するため、先頭BOMも除去します。

function doPost(e) {
  try {
    const req = parseRequest_(e);
    checkApiToken_(req.headers.token);

    if (req.headers.action === "importRows") {
      const result = importRows_(req.headerRow, req.dataRows);
      return textOut_("OK\t" + result.imported + "\t" + result.month);
    }

    throw new Error("不明なactionです: " + req.headers.action);
  } catch (error) {
    return textOut_("NG\t" + (error && error.message ? error.message : error));
  }
}

function parseRequest_(e) {
  if (!e || !e.postData || !e.postData.contents) {
    throw new Error("リクエストボディがありません");
  }

  const raw = String(e.postData.contents).replace(/^\uFEFF/, "");
  const lines = raw.replace(/\r\n?/g, "\n").split("\n");
  const sep = lines.findIndex((line) => line.trim() === "---");
  if (sep < 0) throw new Error("区切り行がありません");

  const headers = {};
  for (let i = 0; i < sep; i++) {
    const pos = lines[i].indexOf(":");
    if (pos < 0) continue;
    headers[lines[i].slice(0, pos).trim()] = lines[i].slice(pos + 1).trim();
  }

  const rows = lines
    .slice(sep + 1)
    .filter((line) => line.trim())
    .map((line) => line.split("\t"));

  if (rows.length < 2) throw new Error("データ行がありません");
  return { headers, headerRow: rows[0], dataRows: rows.slice(1) };
}

書き込みは全行検証のあと

スプレッドシートへ1行ずつ書きながら検証すると、途中で失敗したときにデータが半端に壊れます。 まず全行を配列上で検証し、問題がなければsetValuesでまとめて書きます。

function importRows_(headerRow, dataRows) {
  const rows = dataRows.map((row, index) => {
    if (!row[0]) throw new Error(index + 2 + "行目: 日付が空です");
    return row;
  });

  const lock = LockService.getScriptLock();
  if (!lock.tryLock(30000)) throw new Error("処理中です");

  try {
    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("取込");
    sheet.getRange(2, 1, rows.length, rows[0].length).setValues(rows);
    SpreadsheetApp.flush();
    return { imported: rows.length, month: "2026-02" };
  } finally {
    lock.releaseLock();
  }
}

VBA側はServerXMLHTTPとUTF-8変換を使う

MSXML2.XMLHTTPではなく、MSXML2.ServerXMLHTTP.6.0を使います。 また、送信する本文はUTF-8バイト配列へ変換し、応答もresponseBodyからUTF-8として戻します。

Private Function PostToGas(ByVal url As String, ByVal body As String) As String
    Dim http As Object
    Dim payload As Variant
    Dim stage As String

    On Error GoTo EH

    stage = "オブジェクト生成"
    Set http = CreateObject("MSXML2.ServerXMLHTTP.6.0")

    stage = "Open"
    http.Open "POST", url, False

    stage = "setRequestHeader"
    http.setRequestHeader "Content-Type", "text/plain;charset=utf-8"

    stage = "UTF-8変換"
    payload = Utf8Bytes(body)

    stage = "send"
    http.send payload

    If http.Status <> 200 Then
        Err.Raise vbObjectError + 513, "PostToGas", "HTTP " & http.Status
    End If

    stage = "応答デコード"
    PostToGas = Utf8Decode(http.responseBody)
    Exit Function

EH:
    Err.Raise Err.Number, Err.Source, "[" & stage & "] " & Err.Description
End Function

payloadByte()ではなくVariantで受けます。この理由は、VBAでCOMに配列を渡すときはVariantで受けるで整理しています。

注意点

  • APIトークンは利用者向けURLのトークンと分ける
  • Webアプリを匿名公開する場合、トークン漏洩時の再発行手順を用意する
  • 日付と時刻は送信前後で形式を固定する
  • GAS側で行数上限、年月混在、必須列を検証する
  • Excel側、通信側、GAS側の単体テストを分けておく

関連記事

まとめ

Excel VBAからGASへ直接POSTする場合は、通信、文字コード、認証、排他制御を最初に決めておくと安定します。 TSVとプレーンテキスト応答に寄せることで、VBA側の依存を増やさず、業務ブックからGAS Web APIへ安全にデータを渡せます。

この技術で業務改善しませんか?

Excel VBA・GAS・Webアプリで業務の自動化ツールを開発しています。 「こんなことできる?」というご相談だけでもお気軽にどうぞ。

無料相談はこちら →