GASで商品レビューをAIで分析し
改善点を自動抽出する方法

スプレッドシートに集めたレビューや口コミをClaude APIに渡し、不満点をテーマ別に分類しながら「具体的に何を直すべきか」を自動で抜き出す方法を、動くコードで解説します。

|対象: GAS / Claude API / レビュー分析

Table of Contents

感情分析との違いと、この記事のゴール

感情分析(レビューがポジティブかネガティブかを判定する処理)は「どれくらい満足されているか」を測る手法です。 一方この記事では、否定的なレビューから一歩踏み込み、「何を、どう改善すべきか」という具体的なアクションをAIに抜き出させます。人が数百件のレビューを読み込む代わりに、GASとClaude API(AnthropicのAIをHTTPで呼び出せるサービス)で自動化するのがゴールです。

テーマに分類

「配送・梱包」「品質」などあらかじめ決めたテーマに振り分ける

改善点を抽出

「梱包材を厚手に変更する」のように動詞で具体化する

根拠を引用

抽出のもとになったレビューの一節も返し、事実確認できるようにする

前提として、レビュー本文はスプレッドシートのA列に並んでいるものとします。 フォームやECモールのエクスポートから貼り付けるだけで使えます。

APIキーを安全に保管する

APIキー(AIを呼び出すための鍵)をコードに直接書くと、共有時に漏れる危険があります。 GASのPropertiesService(スクリプトごとの設定値を保存する仕組み)に保管し、コードからは名前で読み出します。

// スクリプトプロパティにAPIキーを保存しておく(コードに直書きしない)
// 「プロジェクトの設定」→「スクリプト プロパティ」で CLAUDE_API_KEY を登録
function getApiKey() {
  const key = PropertiesService
    .getScriptProperties()
    .getProperty("CLAUDE_API_KEY");
  if (!key) {
    throw new Error("CLAUDE_API_KEY が未設定です。スクリプトプロパティに登録してください。");
  }
  return key;
}

Claude APIを呼び出す共通関数

GASからHTTPリクエストを送るUrlFetchAppでClaudeのMessages APIを呼びます。muteHttpExceptions: trueを付けると、エラー時もレスポンス本文を取得できてデバッグしやすくなります。

// Claude API(Messages API)を呼び出す共通関数
// userText には分析用のプロンプト、systemText には役割・出力形式の指示を渡す
function callClaude(systemText, userText) {
  const url = "https://api.anthropic.com/v1/messages";
  const payload = {
    model: "claude-sonnet-4-5",
    max_tokens: 2000,
    system: systemText,
    messages: [{ role: "user", content: userText }],
  };

  const options = {
    method: "post",
    contentType: "application/json",
    headers: {
      "x-api-key": getApiKey(),
      "anthropic-version": "2023-06-01",
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true,
  };

  const res = UrlFetchApp.fetch(url, options);
  const code = res.getResponseCode();
  if (code !== 200) {
    throw new Error("Claude APIエラー: " + code + " / " + res.getContentText());
  }

  const body = JSON.parse(res.getContentText());
  // Messages APIは content 配列の最初のテキストブロックに本文が入る
  return body.content[0].text;
}

モデル名(例:claude-sonnet-4-5)は用途とコストに応じて選びます。分類のような軽い処理なら、より軽量なモデルに変えるとコストを抑えられます。

改善点をJSONで受け取るプロンプト設計

AIの回答を機械的に扱うには、出力形式を固定するのが重要です。 テーマは選択肢から選ばせ、「本文にないことは推測しない」「JSON配列のみを返す」と明示します。 各レビューの番号(index)も返させると、あとで元の行に結果を紐づけられます。

// レビューをまとめて渡し、改善点をJSON配列で受け取るプロンプトを組み立てる
const THEMES = ["品質", "配送・梱包", "価格", "サイズ・仕様", "接客・対応", "その他"];

function buildPrompt(reviews) {
  const list = reviews
    .map((r, i) => (i + 1) + ". " + String(r).replace(/\n/g, " "))
    .join("\n");

  return [
    "以下は当社商品のレビューです。各レビューから改善につながる不満点を抽出してください。",
    "",
    "# ルール",
    "- テーマは次から必ず1つ選ぶ: " + THEMES.join(" / "),
    "- レビュー本文に書かれていないことは推測しない",
    "- 好意的なだけで不満がないレビューは結果に含めない",
    "- 改善点は具体的な動詞で1文にまとめる(例: 梱包材を厚手に変更する)",
    "",
    "# 出力形式(JSON配列のみ。前後に説明文を付けない)",
    '[{"index":1,"theme":"配送・梱包","point":"改善点","quote":"根拠となった一節"}]',
    "",
    "# レビュー",
    list,
  ].join("\n");
}

AIは前後に説明文を付けてくることがあるため、返答から[ ... ]の範囲だけを切り出して解析する関数も用意します。

// AIの返答からJSON部分だけを安全に取り出す
// 前後に説明文が混ざっても [ ... ] の範囲を切り出して解析する
function parseJsonArray(text) {
  const start = text.indexOf("[");
  const end = text.lastIndexOf("]");
  if (start === -1 || end === -1) {
    throw new Error("JSON配列が見つかりませんでした: " + text);
  }
  return JSON.parse(text.substring(start, end + 1));
}

10件ずつまとめて分析するバッチ処理

1件ずつAPIを呼ぶと通信回数が増え、GASの6分実行時間制限に達しやすくなります。 10件程度をまとめて1回のリクエストで送り、処理済みの行はフラグ列でスキップします。

// レビュー列を10件ずつ分析し、改善点シートに書き出す
function analyzeReviews() {
  const ss = SpreadsheetApp.getActive();
  const src = ss.getSheetByName("レビュー");     // A:レビュー本文, B:処理済みフラグ
  const out = ss.getSheetByName("改善点");        // 抽出結果の出力先
  const values = src.getDataRange().getValues();

  const system = "あなたはECサイトの品質改善担当です。指定されたJSON形式のみで回答します。";
  const BATCH = 10;
  const buffer = [];   // {rowNumber, text}
  const results = [];

  for (let i = 1; i < values.length; i++) {   // 1行目はヘッダー
    const text = values[i][0];
    const done = values[i][1];
    if (!text || done) continue;               // 空行・処理済みはスキップ

    buffer.push({ rowNumber: i + 1, text: text });

    if (buffer.length === BATCH || i === values.length - 1) {
      flushBatch(buffer, system, src, out, results);
      buffer.length = 0;
      Utilities.sleep(1000);                   // レート制限に配慮して間隔を空ける
    }
  }

  Logger.log(results.length + "件の改善点を抽出しました");
}

実際にAPIへ送って結果を書き込む部分は、リトライ(失敗時の再試行)を挟んで安定させます。 一時的な通信エラーやレート制限(短時間の呼び出し回数の上限)に当たっても、待ってから再実行します。

// 1バッチ分をAPIに送り、結果を書き込む(リトライ付き)
function flushBatch(buffer, system, src, out, results) {
  const reviews = buffer.map((b) => b.text);
  const prompt = buildPrompt(reviews);

  let items;
  for (let attempt = 1; attempt <= 3; attempt++) {
    try {
      items = parseJsonArray(callClaude(system, prompt));
      break;
    } catch (e) {
      if (attempt === 3) throw e;
      Utilities.sleep(attempt * 2000);         // 2s→4sと待って再試行
    }
  }

  items.forEach((item) => {
    const src2 = buffer[item.index - 1];       // index は1始まり
    const rowNo = src2 ? src2.rowNumber : "";
    out.appendRow([new Date(), rowNo, item.theme, item.point, item.quote]);
    results.push(item);
  });

  // 送った行を処理済みにする(B列に true)
  buffer.forEach((b) => src.getRange(b.rowNumber, 2).setValue(true));
}

テーマ別に集計して優先順位をつける

抽出した改善点をテーマごとに数えれば、どの領域に不満が集中しているかが分かります。 件数の多いテーマから対策すれば、限られたリソースで効果の大きい改善に集中できます。

// 改善点シートをテーマ別に集計し、多い順に並べる
function summarizeByTheme() {
  const out = SpreadsheetApp.getActive().getSheetByName("改善点");
  const rows = out.getDataRange().getValues().slice(1); // ヘッダー除外

  const counts = {};
  rows.forEach((row) => {
    const theme = row[2];                      // C列=テーマ
    if (!theme) return;
    counts[theme] = (counts[theme] || 0) + 1;
  });

  const ranking = Object.keys(counts)
    .map((theme) => ({ theme: theme, count: counts[theme] }))
    .sort((a, b) => b.count - a.count);

  ranking.forEach((r) => Logger.log(r.theme + ": " + r.count + "件"));
  return ranking;
}

トリガーで定期実行を自動化する

新しいレビューが継続的に増えるなら、時間主導トリガー(決まった時刻に自動実行する仕組み)で定期分析します。 未処理分だけを対象にしているため、毎週実行しても同じレビューを二重に分析しません。

// 毎週月曜の朝に自動でレビュー分析を実行する
function createWeeklyTrigger() {
  ScriptApp.newTrigger("analyzeReviews")
    .timeBased()
    .onWeekDay(ScriptApp.WeekDay.MONDAY)
    .atHour(7)
    .create();
}

運用時の注意点

AIの抽出はあくまで下書きです。改善点シートに引用を残しているので、施策化する前に担当者が根拠を確認しましょう。 また件数が非常に多い場合は、1回のトリガーで処理する上限件数を決め、時間主導トリガーを短い間隔で複数回動かすと実行時間制限を回避できます。

まとめ

GASとClaude APIを組み合わせれば、大量のレビューから改善点をテーマ別に自動抽出できます。 ポイントは、テーマを選択肢で固定し、JSONで受け取り、引用を残して事実確認できるようにすることです。 バッチ処理と定期トリガーを加えれば、レビューを「集めるだけ」から「改善に活かす」仕組みへと変えられます。

よくある質問

感情分析はレビューが好意的か否定的かをスコア化するのが目的です。この記事の方法は一歩進めて、否定的なレビューから「何を、どう改善すべきか」という具体的なアクションを抜き出します。たとえば『梱包が雑で角が潰れていた』というレビューから、テーマ=配送・改善点=梱包材の見直し、という形で構造化して受け取ります。

できます。1件ずつAPIを呼ぶと実行時間制限に達しやすいため、10件程度をまとめて1回のリクエストで送るバッチ処理にします。それでも件数が多い場合は、処理済みフラグ列で未処理分だけを対象にし、時間主導トリガーで数回に分けて実行すると6分の実行時間制限を回避できます。

プロンプトで「レビュー本文に書かれていない内容は推測しない」「該当する不満がなければ空配列を返す」と明示するのが有効です。さらに改善点だけでなく根拠となったレビューの一節(引用)も一緒に返させると、担当者が事実確認しやすくなり、誤った抽出に気づけます。

自社の商材に合わせて「品質」「配送・梱包」「価格」「サイズ・仕様」「接客・対応」などを事前にプロンプトへ列挙し、その中から選ばせるのが安定します。自由に分類させると表記がぶれて集計しづらくなるため、選択肢を固定するのがポイントです。

テーマ別に件数を集計すると、どの領域の不満が多いかがひと目で分かります。件数が多いテーマから優先的に対策を検討し、月次でSlackやメールにレポートを自動送信すれば、改善サイクルを仕組み化できます。この記事ではテーマ別集計までのコードを紹介します。

AI×GASで
業務を自動化する。

レビュー分析のほかにも、問い合わせ分類・要約・書類読み取りなど、Claude APIとGASを組み合わせた業務自動化をご相談いただけます。

AI×GAS自動化を見る