Google Apps Scriptを用いたGoogle Search Consoleのデータ自動取得＆Slack通知の自動化

# Google Apps Scriptを用いたGoogle Search Consoleのデータ自動取得＆Slack通知の自動化

## はじめに

SEO 対策を行う上で、Google Search Console（GSC）のデータを定期的にチェックすることは欠かせません。  
しかし、毎日ブラウザでレポートを見るのは手間であったり、特にトレンド性の高いサイトではリアルタイムにSEOを検知できないことは事業チャンスを逃してしまうことにも繋がります。

そこで本記事では、Google Apps Script（GAS）を用いて GSC の検索クエリデータを自動的に取得し、見やすい形式で Slack に通知する仕組みの構築方法を詳細に説明します。

この仕組みでは以下の機能を実現します。

- **GSC API** を使用して、前々日分の検索クエリデータを取得（GSC は通常、データが約2日後に反映されるため）
- 取得したデータのうち、過去に通知していない「新規検索ワード」と、検索ボリューム上位のワードを抽出
- 整形後のデータを Slack へ、CSV 風の 1 行表示で通知
- GAS のトリガー機能により、毎朝自動で実行（例：午前8時～9時に通知）
- OAuth2 認証には Web アプリケーションとして発行したクライアント情報を使用  
  ※**注意：デスクトップアプリケーションではリダイレクト URI の設定ができないため、必ず「Web アプリケーション」として発行してください。**  
  承認済みリダイレクト URI には  
  ```
  https://script.google.com/macros/d/【あなたのGASプロジェクトID】/usercallback
  ```  
  を登録してください。

---

## 必要な準備

### 1. Google Cloud Platform（GCP）の設定

1. **プロジェクトの作成**  
   [Google Cloud Console](https://console.cloud.google.com/) にアクセスし、新規プロジェクトを作成します。

2. **Search Console API の有効化**  
   GCP コンソールの「API とサービス」→「ライブラリ」で「Google Search Console API」を検索し、有効にしてください。

3. **OAuth 2.0 クライアント作成（Web アプリケーションとして）**  
   - GCP の「API とサービス」→「認証情報」から「認証情報を作成」→「OAuth クライアントID」を選択します。  
   - アプリケーションの種類は必ず「Web アプリケーション」を選択してください。  
   - 承認済みのリダイレクト URI として、  
     ```
     https://script.google.com/macros/d/【あなたのGASプロジェクトID】/usercallback
     ```  
     を登録します。  
   - 発行されたクライアント ID とクライアント シークレットは後で使用するため、メモしておいてください。

### 2. Slack の Incoming Webhook の設定

Slack の管理画面で、通知を送信したいチャンネルに対して Incoming Webhook を設定し、Webhook URL を取得します。

### 3. Google Apps Script プロジェクトの作成

- [Google Apps Script](https://script.google.com/) にアクセスし、新規プロジェクトを作成します。  
- プロジェクト名は任意ですが、「Search Console Report Bot」など、分かりやすい名前にしておくと良いでしょう。

### 4. OAuth2 ライブラリの導入方法

本記事では、OAuth2 の認証部分を「auth.gs」に実装します。  
Google の公式 OAuth2 ライブラリは、最新の安定版が GitHub で公開されています。  
[OAuth2.gs (dist)](https://github.com/googleworkspace/apps-script-oauth2/blob/main/dist/OAuth2.gs) の内容をコピーして、プロジェクトに `OAuth2.gs` という新規ファイルとして貼り付けてください。

---

## ソースコードの構成

プロジェクトは以下の 3 つのファイルで構成します。

- **OAuth2.gs**：公式ライブラリ（[こちら](https://github.com/googleworkspace/apps-script-oauth2/blob/main/dist/OAuth2.gs) の内容をそのまま貼り付け）
- **auth.gs**：OAuth2 認証処理および認証用ユーティリティ
- **Code.gs**：メインのロジック（GSC APIからのデータ取得、通知処理、Slackへの送信処理）

### 1. OAuth2.gs

GitHub の公式リポジトリから [こちら](https://github.com/googleworkspace/apps-script-oauth2/blob/main/dist/OAuth2.gs) のコードをすべてコピーしてください。  
（詳細なコードはここでは省略しますが、最新バージョンを必ず使用してください。）

### 2. auth.gs

以下は、OAuth2 認証に必要なコードです。  
このコードでは、`getOAuthService()` 関数でサービスインスタンスを作成し、`authCallback()` 関数と `authorizeManually()` で認証フローを実装しています。

```javascript
// ファイル名: auth.gs

var CLIENT_ID = 'YOUR_CLIENT_ID';           // Webアプリケーションとして発行したクライアントIDに置換
var CLIENT_SECRET = 'YOUR_CLIENT_SECRET';     // 同様にクライアントシークレットに置換

function getOAuthService() {
  return OAuth2.createService('SearchConsole')
    .setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
    .setTokenUrl('https://oauth2.googleapis.com/token')
    .setClientId(CLIENT_ID)
    .setClientSecret(CLIENT_SECRET)
    .setScope('https://www.googleapis.com/auth/webmasters.readonly')
    .setCallbackFunction('authCallback')
    // 認証情報はプロジェクト全体に共有するため ScriptProperties を使用
    .setPropertyStore(PropertiesService.getScriptProperties());
}

function authCallback(request) {
  var service = getOAuthService();
  var authorized = service.handleCallback(request);
  if (authorized) {
    return HtmlService.createHtmlOutput('✅ 認証に成功しました！');
  } else {
    return HtmlService.createHtmlOutput('❌ 認証に失敗しました。');
  }
}

function authorizeManually() {
  var service = getOAuthService();
  if (!service.hasAccess()) {
    var authorizationUrl = service.getAuthorizationUrl();
    Logger.log('👇 以下のURLをブラウザで開いて認証してください:');
    Logger.log(authorizationUrl);
  } else {
    Logger.log('✅ すでに認証済みです。');
  }
}

function resetAuth() {
  getOAuthService().reset();
  Logger.log('🔄 認証情報をリセットしました。再度 authorizeManually() を実行してください。');
}
```

### 3. Code.gs

以下は、GSC API からデータを取得し、Slack に CSV 風の一覧形式で通知するメインコードです。  
このコードでは、取得データから「新規検索ワード」と「検索ボリューム上位ワード」を抽出し、Slack に1行ずつ番号付きで通知します。

```javascript
// ファイル名: Code.gs

// ==== 固定設定 ==== 
var SITE_URL = 'sc-domain:xxxxxxx.com';  // GSCに登録されたプロパティ。sc-domain形式推奨。お使いの環境に合わせて修正ください。
var SLACK_WEBHOOK_URL = 'YOUR_SLACK_WEBHOOK_URL'; // SlackのIncoming Webhook URLに置換してください
var STORAGE_KEY = 'notifiedQueries';   // 通知済みの検索クエリを保存するためのキー
var QUERY_LIMIT = 1000;                // 取得する検索クエリの上限

// ==== メイン処理 ==== 
function notifySearchConsoleData() {
  var service = getOAuthService(); // auth.gs の getOAuthService() を利用
  if (!service.hasAccess()) {
    Logger.log("❌ Search Console API へのアクセスが許可されていません。");
    Logger.log(service.getLastError());
    return;
  }
  
  // GSC のデータは約2日遅延して反映されるため、前々日分（3日前）のデータを取得
  var startDate = new Date();
  startDate.setDate(startDate.getDate() - 3);
  var endDate = new Date(startDate);  // 同一日のデータ
  
  var formattedStart = Utilities.formatDate(startDate, 'Asia/Tokyo', 'yyyy-MM-dd');
  var formattedEnd = formattedStart;
  
  var WEEKDAYS_JA = ['日', '月', '火', '水', '木', '金', '土'];
  var weekday = WEEKDAYS_JA[startDate.getDay()];
  var formattedDisplayDate = Utilities.formatDate(startDate, 'Asia/Tokyo', 'yyyy年M月d日') + '(' + weekday + ')';
  
  var payload = {
    startDate: formattedStart,
    endDate: formattedEnd,
    dimensions: ['query'],
    rowLimit: QUERY_LIMIT
  };
  
  var res = UrlFetchApp.fetch(
    'https://www.googleapis.com/webmasters/v3/sites/' + encodeURIComponent(SITE_URL) + '/searchAnalytics/query', 
    {
      method: 'post',
      contentType: 'application/json',
      headers: { Authorization: 'Bearer ' + service.getAccessToken() },
      payload: JSON.stringify(payload),
      muteHttpExceptions: true
    }
  );
  
  var result = JSON.parse(res.getContentText());
  var rows = result.rows || [];
  
  var props = PropertiesService.getScriptProperties();
  var prev = new Set(JSON.parse(props.getProperty(STORAGE_KEY) || '[]'));
  
  // 新規検索ワード：これまで通知されていないクエリのみ抽出
  var newRows = rows.filter(function(row) {
    return !prev.has(row.keys[0]);
  });
  
  // 検索ボリューム上位：impressions（表示回数）の降順で上位20件を抽出
  var topRows = rows.sort(function(a, b) {
    return b.impressions - a.impressions;
  }).slice(0, 20);
  
  var message = '📈 *' + formattedDisplayDate + 'の検索流入レポート*\n\n';
  message += formatAsCsvList('📌 新規検索ワード', newRows.slice(0, 20)) + '\n';
  message += formatAsCsvList('📊 検索ボリューム上位', topRows);
  
  sendToSlack(message);
  
  // 新規通知したクエリを保存し、重複通知を防ぐ
  var updatedSet = new Set(Array.from(prev).concat(newRows.map(function(row) { return row.keys[0]; })));
  props.setProperty(STORAGE_KEY, JSON.stringify(Array.from(updatedSet)));
}

// ==== CSV 風リスト形式出力 ==== 
function formatAsCsvList(title, rows) {
  if (rows.length === 0) return title + '：データなし\n';
  
  var text = title + '（' + rows.length + '件）\n';
  // ヘッダー：Query 部分を太字に（Slackの markdown を使用）
  text += '*Query*,   Clicks,  Impr.,  CTR,  Pos.\n';
  
  rows.forEach(function(row) {
    var query = row.keys[0];
    // クエリの最大長は 40 文字に制限。超える場合は省略して「…」を付与する
    if (query.length > 40) {
      query = query.slice(0, 39) + '…';
    }
    var line = query + ', ' +
               row.clicks + ', ' +
               row.impressions + ', ' +
               (row.ctr * 100).toFixed(1) + '%, ' +
               row.position.toFixed(1);
    // クエリ部分のみを太字に変換
    line = line.replace(/^([^,]+)/, '*$1*');
    text += line + '\n';
  });
  
  return text;
}

// ==== Slack への送信 ==== 
function sendToSlack(message) {
  var payload = JSON.stringify({ text: message });
  UrlFetchApp.fetch(SLACK_WEBHOOK_URL, {
    method: 'post',
    contentType: 'application/json',
    payload: payload
  });
}
```

※めちゃくちゃ細いTipsですが、Slackの通知だと表形式にするのが難しいです。そのため今回は文字数が不明確な検索クエリを右端に来るよう設定することでSlackでの通知でも見やすいようにしております。

アウトプットはこのようになります。

![名称未設定のデザイン (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199085/57b845d5-8130-4f1a-ad6b-ccb8aa1bfe9c.png)


---

## トリガー設定について

GAS エディタの「時計アイコン（トリガー）」から、`notifySearchConsoleData` 関数を  
以下のように設定してください。

- **実行する関数**：`notifySearchConsoleData`
- **イベントの種類**：時間主導型
- **時間ベースのトリガー**：日付ベースのタイマー
- **実行時間帯**：午前9:00～10:00

調べたところ、Google Search Consoleは毎日7~8時にデータを集計するようだったので、確実に終わっている9~10時のレコメンドにしました。

---

## まとめ

この記事では、Google Apps Script を用いた GSC データの自動取得と Slack 通知システムの構築方法を詳解しました。  
重要なポイントは以下です。

- **GSC API 有効化と OAuth2 認証の設定**  
  認証は Web アプリケーションとして発行し、必ず承認済みリダイレクト URI を設定する（例：`https://script.google.com/macros/d/【GASプロジェクトID】/usercallback`）。

- **GASプロジェクトの構成**  
  1. `OAuth2.gs`（公式ライブラリ）  
  2. `auth.gs`（認証処理：手動認証、コールバック処理、認証リセット）
  3. `Code.gs`（メインロジック：GSC API から検索クエリデータ取得、通知処理、Slack送信）

- **データ取得と通知のロジック**  
  前々日のデータを取得し、既に通知済みのクエリを除外して新規データだけを Slack に CSV 風の形式で通知します。  
  通知フォーマットは、クエリ部分を太字にし、数値情報との間隔を均等にして読みやすくしています。

- **自動実行（トリガー設定）**  
  毎朝決まった時間帯（例：午前8時）に `notifySearchConsoleData` 関数を実行するトリガーを設定すれば、自動で最新レポートが Slack に送信されます。  
  もし初回実行時に認証が必要な場合は、`authorizeManually()` 関数を実行して認証を完了してください。

これで、毎日Google Search ConsoleのデータがSlackで届くようになると思います。
是非、本記事の内容を元にフローを構築し、毎日のSEO対策にお役立てください！

---

【参考リンク】

- [Google Apps Script OAuth2 ライブラリ（公式）](https://github.com/googleworkspace/apps-script-oauth2/tree/main/dist)
- [Slack Incoming Webhooks](https://api.slack.com/messaging/webhooks)
- [Google Search Console API ドキュメント](https://developers.google.com/webmaster-tools/search-console-api-original)