Magicode logo
Magicode
10 min read

GAS でアクセス数集計(Google Search Console 編)

https://cdn.apollon.ai/media/notebox/01c7316d-3654-44cd-a38f-39825004d401.jpeg
前回紹介した、GAS でアクセス数集計(Google Analytics 編)に引き続き、今度は Google Search Console から取得してみます。
Google Search Console は Google の巡回ロボットが、自分のページをどのように解釈したかを報告してくれるツールです。その際に発覚した問題や、どのキーワードで引っかかっているかを教えてくれ、今後の SEO 改善に役立てることができます。
こちらも CSV などをダウンロードする方式です。右上の「エクスポート」を押すと出力形式を選択することができます。 ダウンロードしたものを開き、コピーして貼り付けなど、手間が大きいです。ここを API を使って楽をしましょう。

スクリプト環境の準備

準備としての Google Analytics との違いは、サービス(ライブラリ)が用意されていないことだけです。

1. Google Spreadsheet の準備

結果の出力先です。GAS を使うので Google Spreadsheet が最適です。

2. 記録用シートの作成

「Google Search Console API」とでもしておきましょう。この名前は GAS からシートを参照するときにで使います。

3. サイト ID またはドメイン ID の取得

現在のサイト ID またはドメイン ID を調べます。 レポート画面を表示し、アドレスバーに以下の記述を見つけます。
  • サイト ID の場合は「resource_id=https%3A%2F%2Fsite.jp%2F」これは記号がエンコードされており、実体は「resource_id=https ://site.jp/」という意味になります。エンコードされた値「https%3A%2F%2Fsite.jp%2F」を使用します。
  • ドメイン ID の場合は「resource_id=sc-domain%3Adomain.jp」こちらも記号エンコードされており、実体は「resource_id=sc-domain:domain.jp」という意味です。こちらもエンコード値「sc-domain%3Adomain.jp」を使用します。

4. API スコープの設定

今回はサービス(ライブラリ)ではなく、エンドポイントを使用します。この際に OAuth2 が必要なのですが、この GAS から外部に通信してよい、また Search Console からデータを取ってきていいよと許可を出す設定(スコープ)が必要です。Apps Script の設定画面に移動します。 application.json という特別な JSON ファイルが見えるようになるので、開き、中身を書き換えます。 書き換える内容は以下のとおりです(追記しました)。
"oauthScopes": [
    "https://www.googleapis.com/auth/webmasters.readonly",
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/spreadsheets"
  ],
ここにある oauthScopes がスコープを設定する部分で、スコープが以下の意味を持っています。
//Search Console からデータを取得していいよ。(昔は webmaster tool と呼ばれていました)
https://www.googleapis.com/auth/webmasters.readonly

//GAS から外部にデータを取りにいっていいよ。
https://www.googleapis.com/auth/script.external_request

// 結果をスプレッドシートに書き込んでいいよ。
https://www.googleapis.com/auth/spreadsheets
今回は以下の流れでデータを取得します。
GAS → GCP → Search Console API
ここまでは GAS に対する設定で、中間にある GCP にも設定が必要です。 GCP プロジェクトの設定画面を開きます。
この画面で、新しいプロジェクトを作ります。
Google Search Console API を有効にします。
次に、OAuth の同意画面を作ります。 このプロジェクトは公開しないので、ひとまずの値を埋めて使えるようにしておきます。 必須の部分のみを設定します。
設定が終わったら、今設定しているプロジェクト番号を覚えます。
次に、API を使えるように設定します。

6. GAS から動かす GCP プロジェクトを変更

GAS の初期設定では、安全のために制限がかかっているプロジェクトが割り当てられています。これを先程覚えたプロジェクト番号にし、プロジェクトを設定します。

スクリプト部分の実装

やっと実装です。 クセがあるところを明示します。 まずはテスト関数を定義します。
function testSearchConsole() {
  console.log(getSearchConsole('sc-domain:testsite.example.com'));
}
次にメイン処理です。
function getSearchConsole(_site, _search) {
 const URL = 'https://www.googleapis.com/webmasters/v3/sites/{site}/searchAnalytics/query'.replace('{site}', encodeURIComponent(_site));
 // 日付範囲の指定
 const dt1 = new Date(), dt2 = new Date(dt1.getTime());
 dt1.setDate(dt1.getDate() - 8); //昨日から一週間前
 dt2.setDate(dt2.getDate() - 1); //昨日
 // ペイロード部分の作成
 const body = {
   dimensions: ['DATE'],
   startDate: dt1.toISOString().match(/^(\d{4}-\d{2}-\d{2})/)[1],
   endDate: dt2.toISOString().match(/^(\d{4}-\d{2}-\d{2})/)[1],
 };
 const qry = {};
 // 文字検索
 if (_search) {
   body.dimensionFilterGroups = [{
     filters: [{
       dimension: 'PAGE',
       operator: 'CONTAINS',
       expression: _search,
     }]
   }];
 };
 // URL 生成
 const req_url = URL + '?' + (function (_queries) {
   var obj = _queries;
   return Object.keys(obj).map(function (_id) {
     return [encodeURIComponent(_id), encodeURIComponent(obj[_id])].join('=');
   }).join('&');
 })(qry);
 // パラメータ定義
 const req_params = {
   muteHttpExceptions: true,
   method: 'POST',
   headers: {
     // JSON形式での送信を宣言
     'content-type': 'application/json',
     // OAuth トークンを送信
     authorization: 'Bearer {TOKEN}'.replace('{TOKEN}', ScriptApp.getOAuthToken())
   },

   payload: JSON.stringify(body)
 };
 // リクエストヘッダ生成(引数確認用)
 console.log(UrlFetchApp.getRequest(req_url, req_params));
 const res = UrlFetchApp.fetch(req_url, req_params);
 // 送信処理
 const sts = res.getResponseCode();
 //console.log(['status', sts]);
 // コンテンツ取得
 const cnt = res.getContentText();
 //console.log(['contents', cnt]);
 if (sts !== 200) {
   console.error([arguments.callee.name, 'Status:' + sts, 'Headers:', res.getAllHeaders()]);
   console.error('%J', cnt);
 }
 var obj;
 try {
   obj = JSON.parse(cnt);
 } catch (_error) {
   console.error([arguments.callee.name, 'Body:\n' + cnt]);
   obj = {};
 }
 return obj;
}
応答はこのような感じなので、テスト部分を調整します。
5:39:36	情報	{ rows: 
   [ { keys: [Object],
       clicks: 0,
       impressions: 4,
       ctr: 0,
       position: 9.75 },
     { keys: [Object],
       clicks: 0,
       impressions: 8,
       ctr: 0,
       position: 32.875 },
     { keys: [Object], clicks: 0, impressions: 3, ctr: 0, position: 19 },
     { keys: [Object], clicks: 0, impressions: 2, ctr: 0, position: 97 },
     { keys: [Object],
       clicks: 0,
       impressions: 4,
       ctr: 0,
       position: 15.75 },
     { keys: [Object],
       clicks: 0,
       impressions: 5,
       ctr: 0,
       position: 17.2 } ],
  responseAggregationType: 'byProperty' }
テスト関数を調整します。
function testSearchConsole() {
  const sht = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Google Search Console API');
  // 引数はサイト ID
  const res = getSearchConsole('sc-domain:testsite.example.com');
  // データ確認
  console.log(res);
  // 戻りのオブジェクトを配列に変換
  const rows = res.rows || [];
  const ret = [];
  // データ変換
  [
    rows.map(function (_row) { return _row.keys[0] }),
    rows.map(function (_row) { return _row.clicks; }),
    rows.map(function (_row) { return _row.impressions; }),
    rows.map(function (_row) { return _row.ctr; }),
    rows.map(function (_row) { return _row.position; })
  ].forEach(function Transform(_array, _col) {
    // 行列変換
    _array.forEach(function (_value, _row) {
      if (!ret[_row]) ret[_row] = [];
      ret[_row][_col] = _value;
    });
  });
  // 整形後確認
  //console.log(ret);
  // 反映範囲確認
  //console.log(sht.getRange(1, 1, ret.length, ret[0].length).getA1Notation());
  sht.getRange(1, 1, ret.length, ret[0].length).setValues(ret);
}

Discussion

コメントにはログインが必要です。