ゆるおたノート

Tomorrow is another day.

【Google Analytics × Slack】GASを使ってレポートをSlackに自動送信させてみた。

この2週間ほど、ブログを多めに書いてみました。
時間はまちまちだけど、ほぼ毎日エディタ画面に向かっています。

このブログを作った頃にとりあえずGoogle Analyticsは入れていたのですが、大してアクセスは無いだろうし、性格的にも絶対気にし過ぎてしまうので見ないように見ないようにして放置していました。
そもそも、ページビューとセッションの違いとかもよく分からないし…

でも、1年経ったし記事が増えてきたらやっぱり気になってきたので、少しずつ読み方を勉強しつつ、こちら↓の記事*1を参考にSlack用のGoogle Analyticsのレポートを作成してみました。

あああー、これで一喜一憂の日々の始まりだー!

完成イメージ

今回は、こんな感じでメッセージが投稿されるように設定していきます*2

※文字が小さかったので差し替えました。
メッセージの完成イメージ

準備

Spreadsheet

1. Google Analyticsのアドオンを追加

参照記事にならってGoogle Analyticsを使います。

使い方やメトリクス、ディメンションの中身は隣IT様を見ていただくとして、入れてみたデータはこちら。

Daily Report 流入別セッション数※1 記事別ページビュー
Start Date※2 ブログ開始日 ブログ開始日 =TODAY()-1
End Date※2 =TODAY()-1 =TODAY()-1 =TODAY()-1
Metrics ga:pageviews,
ga:sessions,
ga:pageviewsPerSession,
ga:users,
ga:bounceRate,
ga:avgSessionDuration
ga:sessions ga:pageviews,
ga:users,
ga:sessions
Dimensions ga:date ga:date,
ga:channelGrouping
ga:pageTitle,
ga:pagePath
Order -ga:date -ga:date -ga:pageviews

メトリクスを多少並び替えて、ソートも追加しました。

※補足

  1. 流入別セッション数」はSlackには送っていませんが、ご参考まで…
  2. Start DateとEnd Dateは、将来的に手修正とかで数値がブレてしまわないように、別シートに分けておいて「絶対参照」としています。
2. ダウンロードのスケジュールを設定

3つとも下記のように設定しています。

頻度 実行時刻
1日1回 早朝(4時~5時)
3. コンテナバインドスクリプトを作成

ファイル名はAnalyticsReportとしました。

4. ライブラリを追加

先人の知恵に感謝です!

  • SlackAppライブラリ
    Libraryキー:M3W5Ut3Q39AaIwLquryEPMwV62A3znfOO

  • Momentライブラリ
    Libraryキー:MHMchiX6c1bwSqGM1PZiW_PxhMjh3Sh48

Slack

1. アクセストークンを取得

こちらの「2.3 Slack API Tokenの取得」を参考に、トークンを取得しておきます。

2. プロパティ・サービスでSlackのトークンを登録

先ほど作成したスクリプトを開いて、スクリプト・プロパティ(もしくはユーザー・プロパティ)にトークンをSLACK_ACCESS_TOKENとして登録します。
登録方法は、アクセストークンの参照記事(上記)をご覧いただければ…

スクリプト

コード

一部未完成(後述)ですが、ひとまずレポートは作成出来たのでokということにします。

スクリプトファイルは1.と2.に分けて作成しました。

1. メイン(メッセージの作成)
メッセージをまとめる
function SendAnalyticsReportToSlack() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  
  // レポート
  var dailySheet   = ss.getSheetByName('DailyReport');
  var reportValues = dailySheet.getDataRange().getValues();
  
  const YESTERDAY_INDEX = 16;
  var yesterdayRow = YESTERDAY_INDEX - 1;
  var report       = createAnalyticsReport(reportValues[yesterdayRow]);
  
  // 記事別ランキング
  var rankingSheet  = ss.getSheetByName('記事別ページビュー');
  var rankingValues = rankingSheet.getDataRange().getValues();
  
  var rankingMessage = createRanking(rankingValues);
  
  // メッセージをまとめる
  var message = ':male-construction-worker: < おはようございまーす。昨日の成績ですよー' + '\n'
              + report + '\n'
              + '\n'
              + rankingMessage;
  
  //Slackにポスト
  const POST_CHANNEL_NAME   = 'xxxxxxxx'; // 投稿先のチャンネル名
  const DISPLAYED_USER_NAME = 'xxxxxxxx'; // botの名前(表示用)
  postMessage(message, POST_CHANNEL_NAME, DISPLAYED_USER_NAME);
}
メッセージのレポート部分を生成する
/**
 * Slack投稿用メッセージのレポート部分を生成する
 *
 * @param {Array} DailyReportの1次元配列[
 *   ga:date, ga:pageviews, ga:sessions, ga:pageviewsPerSession, ga:users, ga:bounceRate, ga:avgSessionDuration
 *   ]
 */
var createAnalyticsReport = function(rowValues) {
 // 読みやすいように値を加工
  var gaDate                = Moment.moment(rowValues[0]).format('YYYY/MM/DD (ddd)');
  var gaPageviewsPerSession = Number(rowValues[3]).toFixed(2);
  var gaBounceRate          = Number(rowValues[5] * 100).toFixed(1);
  var gaAvgSessionDuration  = Number(rowValues[6]).toFixed(1);
  
  var report = '*▼' + gaDate + '*' + '\n'
             + '```'
             + 'Pageviews             : ' + rowValues[1]          + ' views'         + '\n'
             + 'Sessions              : ' + rowValues[2]          + ' sessions'      + '\n'
             + 'Pageviews/Session     : ' + gaPageviewsPerSession + ' pages/session' + '\n'
             + 'Users                 : ' + rowValues[4]          + ' people'        + '\n'
             + 'BounceRate            : ' + gaBounceRate          + ' %'             + '\n'
             + 'Session Duration(Avg.): ' + gaAvgSessionDuration  + ' sec.'
             + '```';
              
  return report;
}
メッセージのランキング部分を生成する
/**
 * Slack投稿用メッセージのランキング部分を生成する
 *
 * @param {Array} 記事別ランキングの2次元配列
 *   [ga:date][ga:pageTitle, ga:pagePath, ga:pageviews, ga:users, ga:sessions]
 */
var createRanking = function(values) {
  
  var emojis = [
    ':one:', ':two:',   ':three:', ':four:', ':five:',
//  ':six:', ':seven:', ':eight:', ':nine:', ':keycap_ten:'
  ];
  var ranking = '*▼デイリーランキング*' + '\n';
  
  for(var i = 0; i < 5; i++) {
    const FIRST_DATA_ROW = 15;
    var tempRow          = FIRST_DATA_ROW + i;
  
    var tmp = emojis[i] + values[tempRow][0].replace(' - ゆるオタクのすすめ', '') + '\n'
            + 'https://yuru-wota.hateblo.jp' + values[tempRow][1] + '\n'
            + '```'
            + values[tempRow][2] + ' pv, '
            + values[tempRow][3] + ' people, '
            + values[tempRow][4] + ' sessions.'+ '\n'
            + '```' + '\n'
            + '\n';
    ranking += tmp;
  }
  return  ranking;
}
2. Slackにポスト
投稿を行う
/**
 * メッセージをSlackに投稿する
 *
 * @param {string} 作成したSlackメッセージ
 * @param {string} 投稿先のチャンネル名
 * @param {string} 投稿に表示するユーザー名
 */
function postMessage(m, channelId, userNameForDisplay) {
  var accessToken = PropertiesService.getScriptProperties().getProperty('SLACK_ACCESS_TOKEN');
  
  // SlackAppインスタンスの取得
  var slackApp = SlackApp.create(accessToken);
  
  slackApp.postMessage(
    channelId,
    m,
    {username: userNameForDisplay}
  );
}

トリガーを設定

Spreadsheetの更新後にSendAnalyticsReportToSlack関数が実行されるように設定しています。

頻度 実行時刻
1日1回 午前6時~7時

完了。

これで設定完了です。

補足

スクリプトファイルの分け方

ちょっと長くなってしまっているので、メッセージの生成とSlackへのアクセスは「別のアクション」と判断して、一応スクリプトファイルを分けて書いています。

でも、今回の用途だと2.のファイルにアクションを追加することは無さそう*3です。
それなら1つのファイルにしてしまっても良いかもしれません。

記事修正の副作用

ちょっと脱線ですが、今回のレポートは先日のPowerShellの記事をリライトしながら作成していたのです。

書きながら調べる途中で、記事投稿後にURLやタイトルを修正したら別物として集計されてしまうことも知りました。まじですかスカ!*4

タイトルはともかく、URLも最近結構いじりましたよ…
その後もちょこちょこGoogleから変更前のページにアクセスがあって「変だな」とは思ってたのですよ…
試しに検索してみたら旧URL残ってるし。

この間にアクセス頂いていた方、申し訳ありません。そしてありがとうございます…!
この記事も読んで頂いているかは分かりませんが…

Search Consoleなるもので申請出来ることは分かったものの、反映されるかは保証が無いみたいなので、とりあえず旧URLから新記事に誘導する形で対応させていただきます。

疑問

Slack APIの使い方

今回は以前個人的に作ったBotを流用したのでトークンを取得していますが、メッセージを自動送信するだけならIncoming Webhooksで充分で、わざわざ認証しなくても出来るみたいです。
なんでも、送信先のURLさえ分かれば何処でも誰でも使えるとかで。

と言っても、そもそも私自身が違いをよく分かってない気がしますが…
Slackのドキュメントも読んでみたのですが私の英語力では解読できませんでした…(悲)

関数の引き数

各シートからgetValues()してから各関数に渡しています。
でも、その後シートの操作は特に無いのです。
可読性的には、変数ssを直接渡すのとどっちが良いのでしょうかね…?

桁揃え

レポートの数値が左揃えでちょっとカッコ悪いので、桁を揃えたい(願望)。

Google先生に聞いてみたところ、「半角スペース(\u0020)でエスケープ」というところまでは理解しました。
String型に変換して連結してみたのですが、Logger.Log()には反映されるのに送信メッセージに反映されませんでした…なんで…?

■メモ

/* --------------↓メモ↓-------------- */
// 余裕が出来たら、レポート↑を桁揃えしたい
function showSpace() {
  var num = 1;
  Logger.log('【\u0020%s】', num);
  Logger.log('【%s\u0020】', num);
  
  num = String(1);
  Logger.log('【\u0020%s】', num);
  Logger.log('【%s\u0020】', num);
}
/* --------------↑メモ↑-------------- */

これだと結果は同じなんだけどな…

噂?

1日50PVをキープ出来たら上位20%っていう記事を見たけどホント…?
途中で辞めちゃう人が多いとかの統計マジックではなくて…????

どっちにしても私にはずっと遠い話ですけども…しばらく目標に…

感想

隣ITの記事を見ながら作業してると数字の違いに少し悲しくなってしまうのですが笑、この2週間ほどでPVも見てくださっている方も徐々に増えているみたいで結構嬉しいです。皆様ありがとうございます!!

問題は、これがいつまで続けられるかですが…!
毎日コンスタントに更新するのは結構大変だなぁと思いました。

PCに向かう習慣はともかく、「日々情報収集して、その日のうちに更新して」っていう、このスピード感が結構たいへん。
ライターさんってすごい。

「無駄にこだわる」悪い癖*5が消せたら良いのですけど、それ以外の部分で効率化も考える必要がありそうです。
これも鍛えたら強くなれるのでしょうか…

注釈

*1: 本当は同じ連載の2記事目をリンク先にさせて頂きたかったのですが、何故かカードが反映されないみたいです…連載目次からさかのぼってご覧いただければと思います。

*2: 数値がしょっぱいけど晒しちゃえ。

*3: あるとすればスラッシュコマンドとかで検索出来るようにするくらい?

*4: by モーニング娘。
モーニング娘。 『まじですかスカ!』 (MV)

「まじで」まで打ったら予測変換で出てきた(驚)Windows優秀ですねぇ。

*5: それでクォリティが上がっていれば良いけど、現実はそうでもない…