1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GASをAPIサーバーとして使う実践パターン — doGet/doPost + トークン認証 + 外部連携

1
Last updated at Posted at 2026-06-20
## この記事で解決できること

「GASで作ったスプレッドシートのデータを、外部のNode.jsスクリプトやPlaywrightから読み書きしたい」——そんな場面に遭遇したことはないでしょうか。

Google Apps Scriptの `doGet` / `doPost` を使えば、**GASをAPIサーバーとして公開**し、外部システムと双方向にデータ連携できます。本記事では、実際に稼働しているフリーランス案件管理システムをベースに、以下のパターンを解説します。

- トークン認証で不正アクセスを防ぐ
- 関数ホワイトリストで実行可能な処理を制限する
- スプレッドシートのデータをJSON形式で返す
- POSTでバッチデータを投入する

## 全体構成

外部システム (Node.js / Playwright)

│ GET: シートデータ取得 / 関数実行
│ POST: バッチデータ投入

GAS Web App (doGet / doPost)
│ トークン検証 → ルーティング → 処理

Google Spreadsheet


## 1. GAS側: doGet / doPost の実装

### トークン認証 + ルーティング

```javascript
// ── 設定 ──────────────────────────────────────────────────
// Script Properties に VIEWER_TOKEN を登録しておく
function getToken_() {
  return PropertiesService.getScriptProperties().getProperty('VIEWER_TOKEN');
}

// 実行を許可する関数のホワイトリスト
var ALLOWED_FUNCTIONS = [
  'collectLeads',
  'buildAiReviewPrompt',
  'applyAiResponses',
  'importLeadsFromNotes'
];

// ── doGet: データ取得 & 関数実行 ──────────────────────────
function doGet(e) {
  var token = e.parameter.token;

  // トークン認証
  if (token !== getToken_()) {
    return ContentService
      .createTextOutput(JSON.stringify({ error: 'unauthorized' }))
      .setMimeType(ContentService.MimeType.JSON);
  }

  // アクション分岐
  var action = e.parameter.action;

  // 関数実行モード
  if (action === 'run') {
    var fn = e.parameter.fn;
    if (ALLOWED_FUNCTIONS.indexOf(fn) === -1) {
      return jsonResponse_({ error: 'function not allowed: ' + fn });
    }
    // グローバルスコープから関数を取得して実行
    var result = this[fn]();
    return jsonResponse_({ ok: true, result: result });
  }

  // シートデータ返却モード
  var sheetName = e.parameter.sheet || 'leads';
  return jsonResponse_(getSheetData_(sheetName));
}

// ── doPost: バッチデータ投入 ─────────────────────────────
function doPost(e) {
  var body = JSON.parse(e.postData.contents);

  // トークン認証
  if (body.token !== getToken_()) {
    return jsonResponse_({ error: 'unauthorized' });
  }

  var action = body.action;

  // バッチインポート
  if (action === 'importLeads') {
    var result = importLeadsBatch_(body.leads);
    return jsonResponse_(result);
  }

  // 個別レコード更新
  if (action === 'reviewLead') {
    updateLeadReview_(body.row, body);
    return jsonResponse_({ ok: true });
  }

  return jsonResponse_({ error: 'unknown action: ' + action });
}

ヘルパー関数

// JSON レスポンスを生成する共通関数
function jsonResponse_(data) {
  return ContentService
    .createTextOutput(JSON.stringify(data))
    .setMimeType(ContentService.MimeType.JSON);
}

// スプレッドシートのデータをオブジェクト配列として返す
function getSheetData_(sheetName) {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName(sheetName);

  if (!sheet) {
    return { error: 'sheet not found: ' + sheetName };
  }

  var data = sheet.getDataRange().getValues();
  var headers = data[0]; // 1行目をヘッダーとして扱う
  var rows = [];

  for (var i = 1; i < data.length; i++) {
    var row = {};
    for (var j = 0; j < headers.length; j++) {
      row[headers[j]] = data[i][j];
    }
    rows.push(row);
  }

  var result = {};
  result[sheetName] = { headers: headers, rows: rows };
  return result;
}

// バッチインポート(重複チェック付き)
function importLeadsBatch_(leads) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Leads');
  var existing = sheet.getDataRange().getValues();

  // 既存URLのセットを作成(重複排除用)
  var existingUrls = {};
  for (var i = 1; i < existing.length; i++) {
    existingUrls[existing[i][1]] = true; // B列 = URL
  }

  var added = 0, skipped = 0;

  for (var k = 0; k < leads.length; k++) {
    var lead = leads[k];
    if (existingUrls[lead.url]) {
      skipped++;
      continue;
    }
    sheet.appendRow([
      new Date(),     // 検出日時
      lead.url,       // URL
      lead.title,     // タイトル
      lead.source,    // ソース (CrowdWorks / Lancers)
      lead.budget,    // 予算
      'Pending'       // ステータス
    ]);
    existingUrls[lead.url] = true;
    added++;
  }

  return { ok: true, added: added, skipped: skipped };
}

2. Node.js側: GETでデータ取得

const axios = require('axios');
require('dotenv').config();

const GAS_URL = process.env.GAS_WEB_APP_URL;
const GAS_TOKEN = process.env.GAS_VIEWER_TOKEN;

// シートデータをJSON取得
async function fetchLeads() {
  const res = await axios.get(
    `${GAS_URL}?token=${GAS_TOKEN}&sheet=leads`,
    { maxRedirects: 5, timeout: 30000 }
  );

  const rows = res.data.leads.rows;
  console.log(`取得件数: ${rows.length}`);
  return rows;
}

// GAS関数をリモート実行
async function runGasFunction(fnName) {
  const res = await axios.get(
    `${GAS_URL}?token=${GAS_TOKEN}&action=run&fn=${fnName}`,
    { maxRedirects: 5, timeout: 60000 }
  );
  return res.data;
}

3. Node.js側: POSTでバッチ投入

Playwrightでスクレイピングした案件データをGASに一括投入するパターンです。

// スクレイピング結果をGASへバッチ送信
async function postLeadsToGas(leads) {
  if (leads.length === 0) {
    console.log('送信する案件なし');
    return;
  }

  const response = await axios.post(
    GAS_URL,
    {
      token: GAS_TOKEN,
      action: 'importLeads',
      leads: leads   // [{ url, title, source, budget }, ...]
    },
    {
      headers: { 'Content-Type': 'application/json' },
      timeout: 45000,
      maxRedirects: 5
    }
  );

  console.log(`追加: ${response.data.added}件, 重複スキップ: ${response.data.skipped}件`);
  return response.data;
}

4. デプロイ手順と注意点

Web Appとしてデプロイ

  1. GASエディタ → デプロイ → 新しいデプロイ
  2. 種類: ウェブアプリ
  3. 実行ユーザー: 自分
  4. アクセス: 全員(トークンで認証するため)

.env の設定

GAS_WEB_APP_URL=https://script.google.com/macros/s/DEPLOYMENT_ID/exec
GAS_VIEWER_TOKEN=your-secret-token-here

気をつけるポイント

項目 対策
GASの実行時間制限(6分) バッチ投入は1回あたりの件数を制限する
URLパラメータの文字数制限 長いデータはGETではなくPOSTを使う
GASの自動リダイレクト axios等では maxRedirects: 5 を指定
同時実行によるロック LockService で排他制御する
トークンの管理 Script Properties に格納し、コードに直書きしない

LockServiceによる排他制御

function importLeadsBatch_(leads) {
  // 同時書き込みを防ぐ
  var lock = LockService.getScriptLock();
  lock.waitLock(30000); // 最大30秒待つ

  try {
    // ... 書き込み処理 ...
  } finally {
    lock.releaseLock();
  }
}

このパターンが活きるユースケース

  • スクレイピング → スプレッドシート管理: Playwrightで取得したデータをGASに投入し、スプレッドシートで一覧管理
  • AI評価の自動化: 外部でClaude等のLLMを呼び出し、評価結果をGASにPOSTで書き戻す
  • Slack通知との連携: GAS側でSlack Webhook通知を仕込み、POSTのたびに自動通知

GASの「無料で常時稼働するAPIサーバー」という特性は、個人開発や小規模自動化で非常に強力です。Spreadsheet APIを直接叩くよりもロジックを集約でき、認証やバリデーションも一箇所で管理できます。


より詳しい実装例(3つのクラウドソーシングプラットフォーム対応・AI評価・提案文自動生成まで含めた実践的な構成)は、以下の記事で解説しています。


Qiita記事の本文を出力しました。実際のコードベース(`auto-review.js`、`crowdworks-scraper.js`)の実装パターンをベースに、トークン認証・ホワイトリスト・GET/POSTの使い分け・バッチ投入・排他制御まで網羅しています。約2800文字です。


---

## 関連リソース

- **[GitHub] GAS Automation Toolkit**: https://github.com/denden56-cloud/gas-automation-toolkit
- **[note] 全コード公開記事**: https://note.com/large_yarrow1156/n/nbbeaa3e6e1c8
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?