google apps scriptと chart apiを使ったレポート自動化

今回は、アクセス解析の話ではないのですが、データとして GAを使ったので、ここに書きます。

前回、スクロール率をgoogle analyticsで計測した話を書きました。 レポート部分は、エクセルでの表示だったんですが、今回、自動化のひとつの方法として、タイトルの方法をやったので、報告します。

Google Apps Script(GAS)は、かなりの便利ツール

データを外部から引っ張ってきて、加工して、メール配信というのが、割合簡単にできます。

今回は、google analyticsのデータを引っ張って、加工して、HTMLメールで配信という形の紹介です。

GASの記述は、javascriptです。 google analyticsの集計の設定も javascriptですので、 javascriptは習得しがいのある技術かもしれません。

手順としては、URLFetchで google analytics Data Export  Api からデータを取得して、それを google chart apiを使って、 HTMLメールで送る作業です。

Google Chart APIも、自動化ツールとして価値大。

URLを指定すれば、チャート画像を返してくれます。

ですので、変化する数字を自動的にグラフ化してレポーティングしたい場合に価値が高いです。

パラメータがたくさんあって、マニュアル(解説)が英語なのでとっつきにくいですが、データ、チャートタイプ、ラベル、軸、などの概念とapiでのパラメータが一致すれば、それなりに使えるのではと思ってます。

別サイトにメモみたいなのを書きましたが、、、あまり参考にならないかも。

GMailの問題?

いろいろいじって、なんとか自動化にこぎつけたのですが、GmailのHTML表示の時に、imageタグの部分が上手く表示されなくて、いろいろ探したのですが、結局あきらめました。 たぶん、文字数の問題のような気がしてるのですが、、、どうなんでしょう。 Yahoo Japanの Web mailなら問題なかったです。

出力画像と スクリプトを貼りつけます。

スクロール計測とそのレポート配信を試してみたい方は連絡下さい(コメントでスクロール計測希望と書いてくだい)。 設定して、週次 or 日次で、特定のディメンジョンで切ったスクロール率のよる閲覧割合をメールレポーティングします。無料です。 以下の画像のような形でのHTMLメールが送られます。

画像レポートの後に、参考に、GASのスクリプトを貼りつけておきます。 貼り付けた以外にも追加の定義(関数、代入式)などがあるので、全部ではないです。

2010-06-03から2010-06-15のセッション数上位3ページの精読率調査です。数字はセッション数です。

以下、レポート配信に使ったscriptの一部。

   1:  function CRReport(id,name,title,address){
   2:    //id = "ga:21600568", name="shirai", title="wordpress", address="kimiyuki[at?]gmail.com";
   3:    var startdate; var enddate; var dimensions = []; var metrics = []; var filters = ""; var segments = "";
   4:    var entries; var sort;var data;
   5:    var tmp;
   6:    var siteurl;
   7:    startdate = getMyDate(-15); enddate = getMyDate(-3);
   8:    dimensions = ["ga:pagePath,ga:hostname"]; metrics=["ga:visits"]; sort=["-ga:visits"];
   9:    entries =  getDataFromApi(id, startdate, enddate,dimensions, metrics, filters, segments, sort);
  10:    pagePaths = [];
  11:    entries.slice(0,3).forEach(function(entry){
  12:      var el = entry.getElements("http://schemas.google.com/analytics/2009", "dimension");
  13:      el.forEach(function(e){
  14:        if(e.getAttribute("name").getValue() == "ga:pagePath") pagePaths.push(e.getAttribute("value").getValue());
  15:        if(!siteurl && e.getAttribute("name").getValue() == "ga:hostname") siteurl = e.getAttribute("value").getValue();
  16:      });
  17:    });
  18:  
  19:    data = {};
  20:    var w1 = [null,null, null]; var w2 = [];
  21:    pagePaths.forEach(function(path){
  22:      Utilities.sleep(500);
  23:      w1 = [null,null,null], w2=[];
  24:      var dim_sg = "ga:medium";
  25:      dimensions = ["ga:eventCategory", "ga:eventAction", "ga:pagePath",dim_sg]; metrics = ["ga:uniqueEvents"]; sort = null;
  26:      filters="ga:eventCategory==CompRead0523B;ga:pagePath==" + path;
  27:      segments=null;
  28:      entries = getDataFromApi(id, startdate, enddate, dimensions, metrics, filters, segments, sort);
  29:      entries.forEach(function(entry){
  30:          var ds = entry.getElements("http://schemas.google.com/analytics/2009", "dimension");
  31:          var es = entry.getElements("http://schemas.google.com/analytics/2009", "metric");
  32:          var w = new Array();
  33:          ds.forEach(function(e){
  34:            if(e.getAttribute("name").getValue()=="ga:eventAction") w1[1] = parseInt(e.getAttribute("value").getValue().replace("z",""));
  35:            if(e.getAttribute("name").getValue()== dim_sg){w1[0] = e.getAttribute("value").getValue();}
  36:          });
  37:          es.forEach(function(e){
  38:            if(e.getAttribute("name").getValue() == "ga:uniqueEvents") w1[2] = parseInt(e.getAttribute("value").getValue());
  39:          });
  40:          w2.push([w1[0], w1[1],w1[2]]); //値にして設定するため
  41:          w1 = [null,null, null];
  42:        });
  43:      w2 = w2.sort(function(a,b){return a[1] > b[1]});
  44:      if(data[path] == null) data[path] = [];
  45:      data[path] = w2.map(function(x){return x});
  46:        //data[path] = w2.map(function(e){return [e[0], Math.floor((e[1]/total)*100)]});
  47:    });
  48:   // Logger.log(data);
  49:    html = outputChart(data, startdate, enddate, siteurl);
  50:    Logger.log(html);
  51:    MailApp.sendEmail(address, "CompRead","html mail", {cc: "kimiyuki[aat]gmail.com", htmlBody: html});
  52:  }
  53:  
  54:  
  55:  function outputChart(data, startdate, enddate ,siteurl){
  56:    var html = "<div>" + startdate + "から" + enddate + "のセッション数上位3ページの精読率調査です。数字はセッション数です。</div>";
  57:    for(e in data){
  58:      //Logger.log(e);
  59:      //Logger.log(data[e] instanceof Array);   
  60:      //Logger.log(data[e].length);
  61:      //Logger.log(data[e].toString());
  62:  
  63:      //making dimentionable data    
  64:      var dimmed_data = data[e].reduce(function(r, x){
  65:        if(r[x[0]] == null){
  66:          r[x[0]] = x[2];
  67:        }else{
  68:          r[x[0]] += x[2];
  69:        }
  70:        return r;
  71:      }, {});
  72:      //Logger.log(dimmed_data.toSource());
  73:  
  74:      var tmp = [];
  75:      Logger.log("object="); Logger.log(dimmed_data.toSource() + "\n");
  76:      for(c in dimmed_data){tmp.push([c, dimmed_data[c]])};
  77:      dimmed_data = tmp.sort(function(a,b){ return a[1] < b[1]});
  78:      Logger.log("dimmed_data=");
  79:      Logger.log(dimmed_data.toString()+"\n");
  80:      var label = data[e].map(function(x){return x[1]}).unique();
  81:      var data1 = data[e].filter(function(x){return x[0] == dimmed_data[0][0]}).map(function(x){ return x[2]});
  82:      var data2 = data[e].filter(function(x){return x[0] == dimmed_data[1][0]}).map(function(x){ return x[2]});
  83:      var data3 = dimmed_data.length < 3 ? "" : data[e].filter(function(x){return x[0] == dimmed_data[2][0]}).map(function(x){return x[2]});
  84:      //Logger.log(label);
  85:      //Logger.log(data1);
  86:      //Logger.log(data2);                                       
  87:      //html += "<tr><td>" + e + "</br>" + data[e].map(function(x){return x[0] + '=>' + x[1]}).join(',') + "</td></tr>";
  88:      html += "<div><div>";
  89:      html +=  makeChart(label, data1, data2,data3, dimmed_data[0][0], dimmed_data[1][0], (dimmed_data.length > 2 ? dimmed_data[2][0] : ""));
  90:      html +=  "</br>";
  91:      html += "<a href='http://" + siteurl + e + "'>" + e + "</a></div>";
  92:      html += "<div>  </div>" //全角空白を入れた。間隔を作るため
  93:    }
  94:    html += "</div>";
  95:    return html;
  96:  }
  97:  
  98:  function makeChart(label, data1, data2, data3, dataLabel1, dataLabel2, dataLabel3){
  99:        var max = data1.concat(data2).reduce(function(r, e){return r > e ? r : e;}, 10);
 100:        var chds = "chds=" + "0," + max;
 101:        var chxr = "chxr=1,0," + max;
 102:        var chco = "chco=4d89f9,c6d9fd" + (data3 != "" ? ",63C6DE" : "");
 103:        var chdl = "chdl=" + dataLabel1 + "|" + dataLabel2 + (dataLabel3 != "" ? "|"+dataLabel3 : "");
 104:        var chm = "chm=N,000000,0,,12,0,e|N,00FFFF,1,,12,0,e" + (data3 != "" ? "|N,0000FF,2,,12,0,e" : "");
 105:        var url =
 106:            "<img style='display:block;' src='http://chart.apis.google.com/chart?cht=bhg&chs=250x" + (dataLabel3 != "" ? "990" : "660") + "&chd=t:" +
 107:            data1.join(",") + "|" + data2.join(",") + (data3 != "" ? "|"+data3.join(",") : "") +
 108:            "&" + chds + "&" + chxr + "&" + chm + "&" + chdl + "&" + chco +
 109:            "&chxt=y,x&chxl=0:|" + label.reverse().join("|") + "&chxs=0,ff0000,12,0,lt|1,0000ff,10,1,lt'/>";
 110:    //Logger.log(url);
 111:    return url //.replace("&", "&amp;");  
 112:  
 113:  }
Posted in 実施例 | Tagged , | Leave a comment

ページのどこまで読まれたかを計測する

ウェブサイトのコンテンツをページングするのはユーザビリティを損なう事が多いと僕は思ってます。ユーザビリティの定義は知りませんが、読むこと以外に”意識”を取られると嫌な感じがします。 提供側の論理は知りません。ユーザビリティですから。

次のページへのリンクをクリックという行為は、

  1. そのアンカーテキストに目の焦点を併せる
  2. マウスを叩く
  3. リンク先のページがloadされる。

という順番で進むと思います。

速読の練習をした人はよく分かると思いますが、1の行為は、文字を探し・焦点を合わせるという、非常にコストのかかる行為です。読むという作業においては。。文脈を追う作業は超短期記憶のつなぎ合わせで、意識のallocation作業は天敵です。

一方で、アクセス数字を見たい人に取っては、ページ単位の計測が基本である以上、ページとコンテンツの粒度は一致してる方がいいと思ったりします。<h2> <h3>といった見出し単位での閲覧率を知りたいと思ったりすると思います。閲覧時間の方が知りたいかも、、、 とにかく、ページ閲覧量と滞在時間ではなくて、コンテンツ消費量とコンテンツ消化速度を知りたいとか思うと思います。

ヒートマップ使ったり、細かく計測タグを作れば、データは得られそうですが、面倒です。

で、とりあえずの一歩として、ページ全体でどの割合までページが表示されたかを、google analyticsで計測してみたので、その経過を書きます。

EventTrackで、表示領域割合をアクションとして送る。

当初、表示割合を計測するのに カスタム変数のページスコープ変数を使えばいいのかと思いました。上手く説明できないのですが、ページスコープは使いにくい機能(*)なので、前に使った手法(ページ別閲覧時間分布の作成)と同じような手法のtrackEventを利用する方法でやりました。

* ページスコープのカスタム変数を設定してtrackPageviewで送ったら、それは新規のページビューになってしまう。動的な行動記録をアトヅケで送れない。

それで、方針としは、

  1. 一秒毎に、画面の表示位置(割合)を取得。
  2. それが、そのページ閲覧中で最大値を超えていたら、trackEventする。
  3. 10%単位で区切る。google analyticsでは集計値しか取れないので、値は積み上げで送る。
    • 30%まで閲覧されたというデータを送る場合には、0, 10, 20, 30 と送る。
    • なんとなくだけど、データの連続送信制限があった気がしたので、少しsleepを入れておく。
    • このサイトのrs.jsという奴です。データを送る順番をコントロールするために、jsdeferred.jquery.js をいれました。このサイトの説明を参考にした

データを見る : totalEventsとpageviews

eventの数字を見るときは、totalEvents(イベント数)と uniqueEvents(ユニークイベント数)の二つを見ますが、uniqueEventは、セッション内の同一カテゴリ・アクション・ラベルを一つ(distinct)にします。

image

自作のDataViewerで見たところ。labelにページURL(path)を入れていたのですが、ディメンジョンとして pathPathも有効なので、labelは他の用途に振り向けてもいいかもしれません。

ちなみに、ABテスト別に見たい時は、カスタム変数を出してフィルタリングします。

ab_test_for_how_much_viewed_againt_content

ちなみにこの自作ツール、チョコチョコ改良してます。、カスタムレポートを組むより、操作時間は短いはずです。 日別のデータ作成には便利です。 GAのレポート画面だと、日別のデータをグラフにしてくれますが、数字で出すには、カスタムレポート組まないといけない。

レポート化

アクセス解析サミットで清水さんの講演を見て、スクロール計測のレポートの形式を真似ました。

image

画像にパーセントで線を入れるのにbookmarkletを実行、画像を撮って、エクセルでrept関数。

javascript:hg=document.height;wd=document.width;for(var i=1; i<10; i++){dv=document.createElement("div");dv.innerHTML= "<hr style=’color:#f00; background-color:#f00; height:5px;’/><strong>"+(i*10).toString()+"Percent==></strong>";dv.style.position="absolute";dv.style.top=parseInt((i/10)*hg).toString()+"px";dv.style.left="0";dv.style.width=wd+"px";dv.style.zIndex="1000";document.body.appendChild(dv.cloneNode(true));}

このbookmarkletは適当に作ったもの。 ページ全体画像撮りは、このchromeのextensionを使った

このレポートを自動化するには、

bookmarklet実行 + 画像収集 + データ抽出 + どこかで画像オブジェクトにする

という作業が必要。

Google Analtyicsで、trackEventを使って、ページのスクロールの割合を見て、レポートにするまで、でした。 アクションは知らない。

Posted in ANALYTICS, 実施例 | Tagged , , , | Leave a comment

Windows版 の Data Feed Query Explorer

Data-Feeds-Query-Explorer-in-windows-application

Google Analyticsの Data Export APIからデータを引っ張るソフトを作った。

Visual Studio 2008 express editionのPublish機能を使って、パッケージ化した。.Net Framework 3.1 SP が必要。ただ、SetUp中にDownloadされるはず。

作った動機

1. なぜか、web上の Data Feed Query Explorer で、日本語が文字化けするようになった。 フォーラムに投稿したけど、英語力の問題もあって、上手くasset出来なかった

2. .NETの ライブラリーが更新されたのとのニュースがあった。

ので、今まで、ruby や pythonapps script で やってたけど、GUIで使えるソフトにも挑戦しようとして、やってみた。 ちなみに、windowsソフト( visual Studioを使って作る)のは、ほぼ初めてなので、UI、機能、エラー処理とかは、プリミティブです。検索や2chなどを見ながら、コピペなどをしながら作ったものです。

出来ないこと

Data Feeds の方のデータを引っ張ってこれるようにしただけです。 Account Feedsの方は、profileを引っ張れるだけです。本当は、Account Feedsの方で、ゴールの設定やら カスタム変数、アドバンスセグメントの設定などが、引っ張れるれのですが、やってません。

できる事は、profileIDを指定して、そのprofileのデータを取得するだけです。 用途としては、データをコピーして、エクセルに貼り付けるといいと思います。(unicodeテキストの貼付けを選ぶと文字化けしないはずです)

注意

Data Export APIの制限に注意して下さい。たくさん使うと制限に引っかかるようです。

あと、Dimensionは7つ、Metricsは10が一度に取れる上限項目数です。また、Dimension, Metircsの組み合わせで、データ取得不可のものがあります。これらの場合は、このソフトの上のテキストボックスにある、Error Messageに、そのようなメーセージが出るはずです。

僕の事を悪意を持ってない人だと信頼できて、ソフトの不出来を許容できる方に使ってもらうのが望ましいです。ただ、いろいろ感想を言ってもらえれば、改善へのモチベーションになるので、なにかあれば、@phar までおっしゃって下さい。

使い方

メールアドレスとパスワードを入れて、GetProfiles ボタンを押すと、自分の管理・閲覧できるprofilesがでます。

その後で、データが欲しいProfileのIDをクリックして、選択した状態にして、getDataを押すと、データ取得です。 下の GridDataViewという所にデータが出力されます。 後は、全選択してコピーして、エクセルで、形式を選択して貼付け- unicodeテキストでの貼付け を選ぶと、日本語の問題なくペーストできると思います。

あと、記録するにcheckを入れると、メールアドレスとパスワードを保存します。逆に、空欄にしてからcheckを入れれば、以前に記憶されたものが、空白で上書き(消去)されます。

実際に使っているキャプチャーは、wikiの方に上げるつもりです。

Posted in その他 | Tagged , , | Leave a comment