0
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?

ChatGPT API × Google Apps Scriptで「壊れない」スプレッドシート自動化を作る話

0
Last updated at Posted at 2026-06-03

はじめに

スプレッドシートの手作業、まだ残っていませんか。

100行のレビューをポジ/ネガ仕分けする、顧客名から営業メールを量産する、長文の問い合わせから要点だけ抜き出す——こういう「考えなくていい繰り返し」こそChatGPT APIに丸投げすべき領域です。

ただ、ググって出てくるGASコードをそのままコピペしても**「6分制限で死ぬ」「APIキーが丸見えで詰む」「途中で止まって誰も気づかない」といった事故が起きます。本記事では、その地雷を踏まずに本番運用に耐える設計**まで踏み込んで整理します。

GAS × ChatGPT APIの得意/不得意

正直に書きます。「AIをスプレッドシートに繋げば何でもできる」は過信です。

得意なケース

  • データ分類・タグ付け: レビューをポジ/ネガ仕分け(手動比70〜85%短縮の事例あり)
  • 文章生成: セル内容を変数化してメール文面を量産(1件5分→30秒)
  • 要約・抽出: 長文問い合わせから要点抽出
  • データクレンジング: 表記ゆれの統一

苦手なケース

GASの実行時間は1回6分が上限(有料Workspaceでも同じ)。
数千行のAPI呼び出しを一括処理すると途中で強制終了します。

このサイズの自動化はMake/Zapierのテンプレートのほうが向いています。
GASを書く意味があるのは「シート上のデータと密結合した処理」「社内で誰でもボタン押せばよいUI」が欲しい場面です。

詰まりポイント①:APIキーの正しい格納(ScriptPropertiesに置く)

GASコードに const KEY = "sk-..." と書いて共有→流出、はあるあるです。

正しい格納先は PropertiesService.getScriptProperties()
画面操作で言うと「プロジェクトの設定」→「スクリプト プロパティ」に OPENAI_API_KEY のキー名で値を入れます。

function getApiKey_() {
  const key = PropertiesService.getScriptProperties().getProperty('OPENAI_API_KEY');
  if (!key) throw new Error('OPENAI_API_KEY未設定');
  return key;
}

これだけで「コードを公開しても鍵は漏れない」状態になります。

詰まりポイント②:UrlFetchAppのoptionsで爆死しない

API呼び出しテンプレ。muteHttpExceptions: true がないと、APIエラーで処理全体が止まります。

function callChatGPT_(prompt) {
  const url = 'https://api.openai.com/v1/chat/completions';
  const payload = {
    model: 'gpt-4o-mini',
    messages: [{ role: 'user', content: prompt }],
    temperature: 0.2,
    max_tokens: 500,
  };
  const options = {
    method: 'post',
    contentType: 'application/json',
    headers: { Authorization: 'Bearer ' + getApiKey_() },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true,  // ← 重要
  };
  const res = UrlFetchApp.fetch(url, options);
  const code = res.getResponseCode();
  if (code !== 200) {
    Logger.log('API error: ' + code + ' / ' + res.getContentText());
    return null;  // 失敗を呼び出し元で判別できるようnull返却
  }
  const json = JSON.parse(res.getContentText());
  return json.choices[0].message.content.trim();
}

詰まりポイント③:6分制限を「バッチ分割+トリガー」で回避

100行を一気に処理して6分で死ぬパターン。対策は「進捗をPropertiesに保存して途中再開」です。

function processBatch() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const lastRow = sheet.getLastRow();
  const props = PropertiesService.getScriptProperties();
  let startRow = parseInt(props.getProperty('CURSOR') || '2', 10);
  const BATCH = 20;  // 1回20行ずつ

  const start = Date.now();
  for (let row = startRow; row <= lastRow && row < startRow + BATCH; row++) {
    if (Date.now() - start > 5 * 60 * 1000) break;  // 5分で打ち切り
    const input = sheet.getRange(row, 1).getValue();
    if (!input) continue;
    const result = callChatGPT_('次を要約してください: ' + input);
    if (result) sheet.getRange(row, 2).setValue(result);
    startRow = row + 1;
  }
  props.setProperty('CURSOR', String(startRow));
}

あとは「時間ベースのトリガー」で processBatch を5〜10分間隔で起動すれば、CURSORが進みながら数千行でも完走します。

コスト感(gpt-4o-mini基準・2026年5月時点)

  • 入力: $0.15 / 1Mトークン
  • 出力: $0.60 / 1Mトークン

1リクエスト平均500トークン想定で 1,000行処理 ≒ $0.3〜0.5(約50〜80円) 程度。
本番投入前に必ずダッシュボードで予算上限を設定しておくのが鉄則です。

まとめ

  • APIキーは必ず ScriptProperties に格納(コードに直書き禁止)
  • muteHttpExceptions: true + ステータスコード判定で「黙って止まる」を防ぐ
  • 6分制限はバッチ分割+トリガー+CURSOR保存で回避
0
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
0
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?