GASでSheets APIを直接使う方法
高度なサービスとbatchUpdate

高度なサービス(Advanced Google Services)を有効にすると、GASからSheets APIを直接呼べます。離れた範囲の一括読み書きや書式のまとめ変更を、動くコードで解説します。

|対象: GAS / スプレッドシート / Sheets API / Claude API

Table of Contents

高度なサービスとは|SpreadsheetAppとの違い

結論から言うと、高度なサービス(Advanced Google Services)はGoogleのAPIをほぼそのままGASから呼べるようにする機能です。エディタでサービスを1つ追加するだけで、面倒な認証やトークン管理はGASが裏で済ませてくれます。

普段使うSpreadsheetAppは、Sheets APIをGAS向けに使いやすくまとめた入口です。書きやすい代わりに、細かい指定はできません。両者はこう違います。

SpreadsheetApp

書き方が簡単。1か所ずつの読み書きに向く。呼び出しのたびにシートと往復するため、回数が増えると遅くなる

Sheets(高度なサービス)

離れた複数範囲を1回のリクエストにまとめられる。書式やシート設定も細かく指定できる。書き方はやや長くなる

置き換えではなく、使い分けです。ふだんはSpreadsheetApp、まとめたいところだけSheets API、という混在で問題ありません。

Sheetsサービスを有効化する手順

コードを書く前に、サービスの追加が必要です。手順は3ステップだけです。

1. エディタの「サービス」から追加する

Apps Scriptエディタ左メニューの「サービス」の+を押し、一覧から「Google Sheets API」を選んで追加します。IDはSheetsのままで構いません。このIDがコード中の呼び出し名になります。

2. appsscript.json を確認する

追加すると、マニフェスト(スクリプトの設定ファイル)に自動で以下が書き込まれます。エディタの「プロジェクトの設定」で「appsscript.json を表示する」にチェックを入れると確認できます。

{
  "timeZone": "Asia/Tokyo",
  "dependencies": {
    "enabledAdvancedServices": [
      {
        "userSymbol": "Sheets",
        "serviceId": "sheets",
        "version": "v4"
      }
    ]
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8"
}

3. 初回実行で権限を承認する

初めて実行するとき、スプレッドシートへのアクセス許可を求められます。承認すれば準備完了です。APIキーの発行やOAuthの設定は不要です。

値を読む|Values.getとbatchGet

まずは1範囲の読み取りです。スプレッドシートのIDとA1記法(売上!A1:D100のような範囲の書き方)を渡します。

function readValues() {
  const id = SpreadsheetApp.getActive().getId();

  // range は A1記法。シート名を付けるのが確実
  const res = Sheets.Spreadsheets.Values.get(id, "売上!A1:D100");

  const values = res.values || []; // 空のときは values 自体が返らない
  console.log("取得行数: " + values.length);
  console.log(values[0]); // ヘッダー行

  return values;
}

注意点が1つあります。範囲が空のとき、valuesプロパティ自体が返ってきません。SpreadsheetAppのように空文字の二次元配列にはならないため、res.values || []で受けるのが定番です。行末の空セルも省略されるため、列数が揃わない点にも気をつけてください。

本領を発揮するのはbatchGetです。バラバラの場所にある設定・ヘッダー・明細・マスタを、1回のリクエストで取得できます。

// 離れた4か所を「1回の呼び出し」でまとめて読む
function readSeparatedRanges() {
  const id = SpreadsheetApp.getActive().getId();

  const res = Sheets.Spreadsheets.Values.batchGet(id, {
    ranges: [
      "設定!B2:B10",   // 設定値
      "売上!A1:D1",    // ヘッダー
      "売上!A2:D500",  // 明細
      "マスタ!A2:B200", // 商品マスタ
    ],
    valueRenderOption: "UNFORMATTED_VALUE", // 表示形式を外した生の値
    dateTimeRenderOption: "FORMATTED_STRING",
  });

  const [config, header, rows, master] = res.valueRanges.map((v) => v.values || []);
  console.log("明細: " + rows.length + "行 / マスタ: " + master.length + "件");

  return { config, header, rows, master };
}

valueRenderOptionは値の見え方の指定です。UNFORMATTED_VALUEなら「¥1,200」ではなく数値の1200が返るため、計算に使うときはこちらを選びます。表示どおりの文字列が欲しい場合はFORMATTED_VALUEです。

値を書く|Values.batchUpdateで複数範囲を一括更新

レポートのように「B2に月、B4:B6に数値、D2に数式、別シートに実行日時」と書き込み先が散らばるケースは、SpreadsheetAppだとsetValueを4回呼ぶことになります。Values.batchUpdateなら1回です。

// 離れた複数範囲へ「1回の呼び出し」でまとめて書き込む
function writeSeparatedRanges() {
  const id = SpreadsheetApp.getActive().getId();

  const resource = {
    // RAW: 入力値をそのまま入れる / USER_ENTERED: 手入力と同じ解釈(数式や日付が効く)
    valueInputOption: "USER_ENTERED",
    data: [
      { range: "レポート!B2", values: [["2026年7月"]] },
      { range: "レポート!B4:B6", values: [[1200], [980], [1530]] },
      { range: "レポート!D2", values: [["=SUM(B4:B6)"]] },
      { range: "ログ!A1", values: [[new Date().toISOString()]] },
    ],
  };

  const res = Sheets.Spreadsheets.Values.batchUpdate(resource, id);
  console.log("更新セル数: " + res.totalUpdatedCells);
}

valueInputOption: RAW

渡した値をそのまま入れる。「=SUM(...)」は数式ではなく文字列として入る

valueInputOption: USER_ENTERED

人が手入力したのと同じ解釈。数式が数式として効き、日付も日付として入る

引数の順番に癖があります。値を書く系のメソッドはリソース(送るデータ)が第1引数、スプレッドシートIDが第2引数です。読み取り系はIDが第1引数なので、逆に書いてエラーになりがちな箇所です。

見た目を変える|Spreadsheets.batchUpdateとrepeatCell

値ではなく書式やシート構造を変えるときは、Sheets.Spreadsheets.batchUpdateに「リクエストの配列」を渡します。1つの配列に何十件並べても、通信は1回です。

// 条件に合う行の背景色を、1回のリクエストでまとめて変える
function highlightRows(targetRowNumbers) {
  const ss = SpreadsheetApp.getActive();
  const sheet = ss.getSheetByName("売上");
  const sheetId = sheet.getSheetId(); // シートごとの数値ID

  const requests = targetRowNumbers.map((rowNumber) => ({
    repeatCell: {
      range: {
        sheetId: sheetId,
        // GridRange は 0始まり・終端は含まない
        startRowIndex: rowNumber - 1,
        endRowIndex: rowNumber,
        startColumnIndex: 0,
        endColumnIndex: 4,
      },
      cell: {
        userEnteredFormat: {
          backgroundColor: { red: 1, green: 0.95, blue: 0.8 },
          textFormat: { bold: true },
        },
      },
      // 変更する項目だけを明示する(書かない項目は元のまま)
      fields: "userEnteredFormat.backgroundColor,userEnteredFormat.textFormat.bold",
    },
  }));

  if (requests.length === 0) return;

  Sheets.Spreadsheets.batchUpdate({ requests: requests }, ss.getId());
}

ここで最大の落とし穴がGridRange(範囲を数値で指定する形式)です。SpreadsheetAppのgetRange(2, 1)は1始まりですが、GridRangeは0始まりで、終端は含みません。シートの5行目1行だけならstartRowIndex: 4, endRowIndex: 5です。

もう1つ重要なのがfieldsです。「今回変えるのはこの項目だけ」という宣言で、ここに書かなかった書式は元のまま残ります。逆にuserEnteredFormatとだけ書くと、指定しなかった罫線や文字色まで初期化されるため注意してください。

同じ配列にaddSheet(シート追加)やupdateBorders(罫線)、autoResizeDimensions(列幅の自動調整)なども混ぜられます。月次レポートのシート作成から書式設定までを1回で終わらせる、といった使い方ができます。

AIの判定結果をまとめて書き戻す

AIでの一括処理と相性が良いのが、この一括書き戻しです。1行ずつAIに投げて、そのつどセルに書き込む実装は、シートとの往復が件数ぶん発生します。判定結果を配列に貯めておき、最後に1回で書き戻すほうが速く、途中で止まったときの状態も分かりやすくなります。

// AIで判定した結果を、まとめて1回で書き戻す
function classifyAndWriteBack() {
  const ss = SpreadsheetApp.getActive();
  const id = ss.getId();

  const res = Sheets.Spreadsheets.Values.get(id, "問い合わせ!A2:C500");
  const rows = res.values || [];

  const data = [];        // 書き戻す値
  const alertRows = [];   // 色を付ける行番号
  for (let i = 0; i < rows.length; i++) {
    const body = rows[i][1];
    const done = rows[i][2];
    if (!body || done) continue; // 空行と判定済みはスキップ

    const result = classifyWithClaude_(body);
    const rowNumber = i + 2; // A2始まりなので +2

    data.push({ range: "問い合わせ!C" + rowNumber, values: [[result.category]] });
    if (result.urgent) alertRows.push(rowNumber);
  }

  if (data.length > 0) {
    Sheets.Spreadsheets.Values.batchUpdate(
      { valueInputOption: "RAW", data: data },
      id
    );
  }
  highlightRows(alertRows); // 書式もまとめて1回
}

function classifyWithClaude_(text) {
  const apiKey = PropertiesService.getScriptProperties().getProperty("ANTHROPIC_API_KEY");

  const payload = {
    model: "claude-opus-5",
    max_tokens: 512,
    system: "あなたは問い合わせを分類する担当です。必ず指定のJSON形式だけを返します。",
    messages: [{ role: "user", content: "次の問い合わせを分類してください。\n" + text }],
    output_config: {
      format: {
        type: "json_schema",
        schema: {
          type: "object",
          properties: {
            category: { type: "string", enum: ["見積依頼", "不具合", "質問", "その他"] },
            urgent: { type: "boolean" },
          },
          required: ["category", "urgent"],
          additionalProperties: false,
        },
      },
    },
  };

  const res = UrlFetchApp.fetch("https://api.anthropic.com/v1/messages", {
    method: "post",
    contentType: "application/json",
    headers: { "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true,
  });

  if (res.getResponseCode() !== 200) {
    console.error("Claude APIエラー: " + res.getContentText());
    return { category: "その他", urgent: false };
  }

  const json = JSON.parse(res.getContentText());
  return JSON.parse(json.content[0].text);
}

ポイントは、シートへの書き込みが値と書式の2回だけに収まっていることです。時間がかかるのはAIの応答待ちなので、シート側の往復を削ると全体の見通しが良くなります。件数が多く6分の実行時間制限に当たりそうなら、未判定の行だけを処理する今回の作りのまま、時間主導トリガーで小分けに回せます。

AIの回答はoutput_config.formatで構造を固定しています。分類先をenumで列挙しておくと、表記ゆれのない値だけが返るため、そのままシートの絞り込みに使えます。

実務での注意点

1. ループの中で呼ばない

Sheets APIには1分あたりのリクエスト数上限があり、超えると429エラーになります。速くするために導入したのに、1行ずつ呼んでいては逆効果です。「配列に貯めて最後に1回」を徹底してください。最新の上限値は公式ドキュメントで確認できます。

2. SpreadsheetAppと混ぜるならflush()

SpreadsheetAppの書き込みは、まとめて反映されるまで保留されることがあります。書いた直後にSheets APIで読むと、古い値が返る可能性があります。境目ではSpreadsheetApp.flush()を挟んでください。

function mixedUsage() {
  const ss = SpreadsheetApp.getActive();
  const sheet = ss.getSheetByName("売上");

  // Sheets API で書き込んだ直後に SpreadsheetApp で読むときは
  // flush() でGAS側の保留分を吐き出してから読む
  sheet.getRange("A1").setValue("集計中");
  SpreadsheetApp.flush();

  const res = Sheets.Spreadsheets.Values.get(ss.getId(), "売上!A1");
  console.log(res.values[0][0]); // 集計中
}

3. シート名の変更で壊れる

A1記法はシート名を文字列で持つため、利用者がタブ名を変えると動かなくなります。名前ではなくgetSheetId()で得た数値IDを使うか、名前をスクリプトプロパティで管理しておくと安全です。

4. まず遅い箇所を測る

Sheets APIはコード量が増えます。導入前に、実際どこで時間を使っているかをログで測ってください。多くの場合、犯人はループ内のgetValuesetValueで、SpreadsheetAppのまま一括化するだけで解決することも珍しくありません。

まとめ

高度なサービスでSheetsを有効にすると、GASからSheets APIを直接呼べます。効くのは「まとめられる」場面です。離れた範囲の読み取りはValues.batchGet、散らばった書き込みはValues.batchUpdate、書式やシート構造はSpreadsheets.batchUpdateで、それぞれ1回の通信に畳めます。つまずくのはGridRangeが0始まりであること、fieldsで変更対象を明示すること、値を書く系はリソースが第1引数であることの3点です。AI処理の結果を一括で書き戻す構成と組み合わせれば、数百行の自動処理でも実行時間に余裕が生まれます。

よくある質問

高度なサービス(Advanced Google Services)は、SpreadsheetAppやDriveAppのような専用サービスとは別に、GoogleのAPIをほぼそのままGASから呼べるようにした機能です。エディタの「サービス」から追加するだけで、認証やトークン管理はGASが裏で処理してくれます。Sheetsサービスを追加すると、Sheets APIのメソッドをSheets.Spreadsheets.〜という形で呼び出せます。

基本はSpreadsheetAppで十分です。読み書きの回数が少なく、コードも短く済みます。Sheets APIが向くのは、離れた複数範囲をまとめて読み書きしたいとき、数百セルの書式を一度に変えたいとき、SpreadsheetAppに用意されていない細かい設定を触りたいときです。1つのスクリプトで両方を混ぜて使えます。

「1回の呼び出しにまとめられるとき」は速くなります。GASが遅くなる主因は、シートとのやり取り(往復)の回数です。10か所の範囲を10回に分けて読むのと、batchGetで1回にまとめるのとでは往復回数が10分の1になります。逆に、1回で済む処理をSheets APIに置き換えても速くはなりません。

Sheets APIのGridRangeは0始まりで、終端は「その手前まで」を意味するためです。シートの2行目から4行目を指すなら startRowIndex: 1, endRowIndex: 4 になります。getRange(2, 1, 3, 1) のような1始まりのSpreadsheetAppと混ぜて書くと必ず混乱するので、行番号を変換する小さな関数を用意しておくと安全です。

あります。GAS自体の1日あたりの上限とは別に、Sheets API側にも1分あたりのリクエスト数上限が設けられています。上限に達すると429エラーが返るため、ループの中で1行ずつ呼ぶのではなく、batch系のメソッドで1回にまとめる設計にしてください。最新の数値は公式ドキュメントで確認してください。

スクリプトをコピーして配布する場合、コピー先でもサービスが有効になっている必要があります。appsscript.json の enabledAdvancedServices に設定が書かれていれば、通常はコピーとともに引き継がれます。うまく動かないときは、エディタの「サービス」にSheetsが並んでいるかを最初に確認してください。

遅いスプレッドシート処理を、
作り直さずに速くする。

実行時間制限に引っかかるGAS、件数が増えて止まった自動化、AIを組み込んだ一括処理まで、既存のスクリプトの改善からご相談いただけます。

AI×GAS自動化を見る