ゆるおたノート

Tomorrow is another day.

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

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

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

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

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

準備

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. コンテナバインドスクリプトでライブラリを追加

先人の知恵に感謝!

  • SlackAppライブラリ
    Libraryキー:M3W5Ut3Q39AaIwLquryEPMwV62A3znfOO
    qiita.com

  • Momentライブラリ
    Libraryキー:MHMchiX6c1bwSqGM1PZiW_PxhMjh3Sh48
    tonari-it.com

Slack

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

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

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

先ほど作成したスクリプトを開いて、スクリプト・プロパティ(orユーザー・プロパティ)に
トークンを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時に設定。

送信イメージ

以上のコードを実行すると、こんな感じでメッセージが投稿されます*2


※文字が小さかったので差し替えました。

疑問と補足

Slack APIの使い方

前に作ったBotを流用して作ったのでトークンを取得していますが、
メッセージを自動送信するだけならIncoming Webhooksで充分で、
わざわざ認証しなくても出来る*3みたいです。

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

関数の引き数

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

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

ちょっと長くなってしまっているので、メッセージの生成とSlackへのアクセスは
別のアクションとして一応スクリプトファイルを分けて書いていますが、
今回の用途だと2.のファイルにアクションを追加することは無さそう*4ですね。

1つのファイルにしてしまっても良いかもしれません。

桁揃え

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

半角スペース"\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%っていう記事を見たけどホント…?
途中で辞めちゃう人が多いとかの統計マジックではなくて…????

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

記事修正の副作用

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

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

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

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

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

感想

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

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

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

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

注釈

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

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

*3: 送信先のURLさえ分かれば何処でも誰でも使えるので…

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

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

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

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