【GAS】スプレッドシートに書きだしたGA4データをGeminiで自動分析、Slack投稿を行えるようにしてみた

# ▼背景・目的

GASにてGemini APIを用いてGA4のデータ分析を自動化してみる。

GASにてGoogle Analytics Data APIを使ってGA4データを抽出し、それをAIで分析してChatworkに投稿するという記事を見た。しかし、Google Analytics Data APIを使って予めスプレッドシートに数値を書き出すことも多いと感じる。

この記事ではスプレッドシートに書きだしたGA4データをAIで分析してSlackに自動投稿する流れを紹介する。一つ一つの手順については世に情報が溢れているため説明は行わない。

[Zennの参考記事](https://zenn.dev/nabesan/articles/702aa69369df01)

以下のように毎日データが書き出されている状況を想定する。  
1週間ごとに7日分の分析を自動でSlackに投稿することを目指す。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998651/99dfd09f-c7e9-4df9-a5a6-89996db9d48e.png)

# ▼流れ

- Gemini APIを有効化
- APIキー発行
- Slack Webhook作成
- GASのプロジェクト作成
- GASのスクリプトプロパティにAPIキーを登録
- GAS記述
- トリガー設定
- 完成

# ▼結果

以下のように自動で分析を行うことに成功した。  
ただし、分析結果は何とも言えない。
プロンプトを改善するなどをすれば、ある程度使えるものにはなりそう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998651/5cf07ab4-1b25-4b63-b020-d3590a60024e.png)

## ナレッジ・所感

思ったより簡単に出来た。Geminiは無料枠もあるため試しやすい。  
しかし、分析の精度などについては今後の課題としたい。

## 参考リンク

- [Zenn](https://zenn.dev/nabesan/articles/702aa69369df01)
- [Gemini APIの使い方](https://www.softbank.jp/biz/blog/cloud-technology/articles/202502/gws-gas-gemini-api/)

# ▼GASコード

`analyzeLast7RowsFromColumns` 関数を1週間に1回、月曜日に実行するようにトリガーをセットする。

変更する必要がある箇所
- スプレッドシートID
- シート名
- どの列からどの列までを分析するのか
    - startCol
    - endCol
- 何行分分析するのか
    - numRows
- GEMINI_API_KEY
    - この名前は任意で付けたGASのスクリプトプロパティの名前。変更可
- webhookUrl
    - これはslack apiの管理画面から取得したもの

```javascript
const SPREADSHEET_ID = 'XXXXXXXX'; // スプレッドシートIDを入力
const SHEET_NAME     = 'hogehoge';
const startCol       = 1; // B列
const endCol         = 3; // D列
const numRows        = 7; // 直近7行分

function analyzeLast7RowsFromColumns() {
  const sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME);

  const numCols = endCol - startCol + 1;
  const lastRow = sheet.getLastRow();
  const numDataRows = lastRow - 1; // ヘッダーを除く
  const numRowsToFetch = Math.min(numRows, numDataRows);
  const startRow = lastRow - numRowsToFetch + 1;

  const headers = sheet.getRange(1, startCol, 1, numCols).getValues()[0];
  const data = sheet.getRange(startRow, startCol, numRowsToFetch, numCols).getValues();

  const structuredData = data.map(row => {
    let obj = {};
    row.forEach((value, idx) => {
      obj[headers[idx]] = value;
    });
    return obj;
  });

  const prompt = `
以下はスプレッドシートから取得した直近1週間のデータです。それぞれの項目名と値を参考に、全体の傾向や特徴、気づき、改善点などを200〜300文字で日本語で簡潔にまとめてください。

【データ】
${JSON.stringify(structuredData, null, 2)}
  `.trim();

  const result = callGeminiApi(prompt);
  sendMessageToSlack(result);
}

// Gemini API呼び出し（モデル: gemini-2.0-flash）
function callGeminiApi(prompt) {
  const modelName = "gemini-2.0-flash";
  const apiKey = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY');
  const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelName}:generateContent?`;

  const options = {
    method: 'post',
    contentType: 'application/json',
    headers: {
      'x-goog-api-key': apiKey
    },
    payload: JSON.stringify({
      contents: {
        parts: [{ text: prompt }]
      }
    })
  };

  try {
    const response = UrlFetchApp.fetch(url, options);
    const jsonResponse = JSON.parse(response.getContentText());
    const answerText = jsonResponse.candidates[0].content.parts[0].text;
    return answerText;
  } catch (error) {
    console.log(error);
    return '回答を取得できませんでした。';
  }
}

function sendMessageToSlack(text) {
  const webhookUrl = 'https://hooks.slack.com/services/XXXXXXXXXXXXXXX/UUUUUUU'; // 自分のWebhook URL
  const payload = {
    text: text,
  };

  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
  };

  UrlFetchApp.fetch(webhookUrl, options);
}