携帯版Google Analyticsで滞在時間とか検索ワードとか

/web/server-side

Note: この記事は、3年以上前に書かれています。Webの進化は速い!情報の正確性は自己責任で判断してください。

Google Analytics(以下、GoAn)はimg要素を読み込むことリクエストを送信してて統計にカウントしてたから、同じようなURLを生成して送信すれば携帯サイトでもGoAnは利用できる。ただしCookieに値を保存できない携帯版だと幾つかの制約があって、簡単に言うと「全アクセスがユニーク扱いになる」。ここまでが前回のお話

で、その後いろいろ試してみた結果。だいぶデータが見れるようになったので、公開してみる。統計に反映されるようになったデータを以下に記します。

  • 滞在時間、滞在中のページビュー、直帰率
  • リピート数、ブラウザ、OS、ユーザ定義
  • 参照サイト、参照元、検索エンジン、キーワード

幾つか怪しいデータもあるけど、まあ仕組みが解ってれば十分参考にはなるし。この手の統計ってのは「ページビュー○○万件です!」て主張するより、増減の傾向や存在証明にこそ意味があるもんだ。数字なんて飾りです。スーツな人にはそれが解らんのですよ。解ってて利用するのが真のスーツだ。

ともあれ。
ライバルはうごくひとだ! ってことで、こないだ起源が判明したらしいSugamo.cssの3回目に持って行きました。

Cookies info

ソースを晒す前に中身のまとめ。GoAnを利用しているサイトでCookieを見てみると、以下のようになっている。まずブクマから閲覧した場合(Urchin.js)。

Name Value
__utma 169191153.1496068014.1241010801.1241067869.1241195482.4
__utmb 169191153
__utmc 169191153
__utmz 169191153.1241010801.1.1.utmccn=(direct)|utmcsr=(direct)|utmcmd=(none)

次に、Googleから「Analytics 携帯」などと検索した結果ページから遷移した場合。

Name Value
__utma 169191153.1496068014.1241010801.1241067869.1241195482.4
__utmb 169191153
__utmc 169191153
__utmz 169191153.1241196701.4.2.utmccn=(organic)|utmcsr=google|utmctr=Analytics%E3%80%80%E6%90%BA%E5%B8%AF|utmcmd=organic

utmzの値の変化が判るだろうか。この2つの例と、The Chatterboxのてきとー和訳をもとに、それぞれの値の意味を掘り下げてみる。ここを作って送る訳だから、知ってるのと知らないのとでは天地の違いよ!

Google Analytics Cookies

Name Description
__utma
  • XXXX.RRRR.FFFF.PPPP.CCCC.N
  • 各種情報が収められているメインのCookie。
  • 保存期間が2年と長く、あるていど継続した調査を記録する
XXXX ドメインハッシュ。対象ドメイン名をランダムな数字で置き換えたもの。
RRRR ランダムな数字。訪問者のユニークIDになっている
→ おそらくは32bit整数型(2147483647以下の数字)。
FFFF タイムスタンプ(Unix time)。最初に訪れた時間(first_time)。
PPPP タイムスタンプ(Unix time)。前回訪れた時間(previous_visit)。
CCCC タイムスタンプ(Unix time)。今回訪れた時間(current_time)。
→ 今回のセッションで最初に踏んだ時間。なのでサイト内移動では変わらない。
N 訪問回数を表す...が、(多くの携帯サイトがそうであるように)セッションをサーバ側に保存して定期的に消している場合、ここの実現は困難。
→ GoAn(PC版)では、おそらくこの部分のために有効期間を2年とかにしている。
__utmb
  • XXXX.P.10.C
  • 現在のセッション中における変化を記録する
  • 旧バージョンではドメインハッシュを保存していただけ
  • 保存期間は、最後にページをロードしてから30分
  • 30分っていう数字は、おそらくブラウザが直接Cookieを消さないから
XXXX ドメインハッシュ。
P 1以上の数字。今回のセッションにおいて何ページ閲覧したか(というかLoadカウントっぽい)
C 現在時間のタイムスタンプ(Unix time)。
__utmc
  • ドメインハッシュのみ
  • 現在のセッションが終わるまで保持する
  • そのサイトから出たり、ブラウザを閉じると消える
  • 一度も上書きされない
  • 作られたタイムスタンプと現在のタイムスタンプを比較することのみに使用される。
  • おそらくはutmbで測れない「凄く長いセッション」のためのもの
__utmz
  • XXXX.TTTT.V.S.utmcsr{source}|utmccn{campaign}|utmcmd{medium}|utmctr{keyword}
  • 有効期間は最後の更新から半年
  • 必ずしも毎回更新されない
  • 極めて扱い辛い情報が収められている
  • どのようなユーザが到着したかを記録する
  • 参照元や検索エンジン、キーワードなんかは大抵ここ。
XXXX ドメインハッシュ。
TTTT 最後にCookieが設定された時間。
V 訪問回数。おそらくは__utmaのものと同じロジック
S 到達ルート数。異なる経路で辿り着くとカウントアップ
utmcsr 参照元。最後にCookieが更新された場所。URLとかドメイン名ってより、どうも任意で設定してるっぽい。Googleから来たならutmcsr=googleとなる。あとmsnとかliveとか。
utmccn キャンペーン。Adwordsに関連する何か。普通の検索結果からだと、utmccn=(organic)になる。
utmcmd メディア。検索結果からならorganic。リンクからだとreferralが入る。直接は(none)。たぶん。
utmctr 検索キーワードが入る。文字列はURLエンコード。「最後に入力した検索ワード」であって必ずしもサイトとは関係ない?
→ utmctr=textarea%E3%80%80%E9%81%B8%E6%8A%9E%E7%AF%84%E5%9B%B2
→ utmctrがあるとutmccn/utmcmdがorganicになる?
__utmv
  • ある意味レアなCookie。
  • サイト管理者がGoAnで設定してるときに出てるっぽい
  • フィルタとか?

ドメインハッシュの生成方法は判らなかったのだけど、どのみち初回はJS埋め込んでアクティベートしてやる必要があるので、そのJSが生成したCoolie情報をPCブラウザで見てやれば問題なく取得できる。

サンプル

というわけで、ここまでの調査結果をもとに前回のソースを編集すると、以下のような関数ができあがりました。長いよ?

  1. package Util;
  2.  
  3. use strict;
  4. use LWP::UserAgent;
  5.  
  6.  
  7. sub GoogleAnalystics {
  8. my $session = $_[0];
  9.  
  10. #setting
  11. # - host_name from target-host
  12. # - domainH from data of Domain-Cookies
  13. # - utmac from activate GoAn-data
  14. my $host_name = 'archiva.jp';
  15. my $domainH = '169191153';
  16. my $utmac = "UA-xxxxxx-x";
  17. #end setting
  18.  
  19. my $utmhn = "http://$host_name/";
  20. my $utmn = int(rand(9999999998)) + 1; #random number
  21. my $today = time();
  22. my $utmp = $ENV{'REQUEST_URI'};
  23. my $utmcs = 'Shift-JIS'; #character_coding
  24. my $utmsr = '-'; #1024x768
  25. my $utmsc = '-'; #32-bit
  26. my $utmul = 'ja'; #en
  27. my $utmje = '0'; #java_enable / 1
  28. my $utmfl = '-'; #flash_version / 9.0%20r28
  29. my $utmdt = '-'; #page title
  30.  
  31. #remove session-id from requestURL
  32. my $session_id = $session->{_session_id};
  33. if($session_id) {
  34. $utmp =~ s{/$session_id/?}{}xms;
  35. $utmp =~ s{\A([^/])}{/$1}xms;
  36. }
  37.  
  38. #check UA
  39. my $agent;
  40. if ($ENV{HTTP_USER_AGENT} =~ m/Baiduspider | Yahoo! \s+ Slurp | Googlebot/xms){
  41. return 1;
  42. }
  43. elsif ($ENV{HTTP_USER_AGENT} =~ m/DoCoMo/xms) {
  44. $agent = 'DoCoMo';
  45. }
  46. elsif ($ENV{HTTP_USER_AGENT} =~ m/Vodafone/xms) {
  47. $agent = 'Vodafone';
  48. }
  49. elsif ($ENV{HTTP_USER_AGENT} =~ m/SoftBank/xms) {
  50. $agent = 'SoftBank';
  51. }
  52. elsif ($ENV{HTTP_USER_AGENT} =~ m/\A(?: UP.Browser | KDDI )/xms) {
  53. $agent = 'au';
  54. }
  55. elsif ($ENV{HTTP_USER_AGENT} =~ m/iPhone;/xms) {
  56. $agent = 'iPhone'
  57. }
  58. elsif ($ENV{HTTP_USER_AGENT} =~ m/emobile/ixms) {
  59. $agent = 'EMobile';
  60. }
  61. elsif ($ENV{HTTP_USER_AGENT} =~ m/willcom/ixms) {
  62. $agent = 'Willcom';
  63. }
  64. else {
  65. $agent = $ENV{HTTP_USER_AGENT} if $ENV{HTTP_USER_AGENT};
  66. }
  67.  
  68. #check session
  69. my $visitor_id = $session->retrieve('visitor_id');
  70. my $first_time = $session->retrieve('first_time');
  71. my $current_time = $session->retrieve('current_time');
  72. my $last_time = $session->retrieve('last_time');
  73. unless ($visitor_id || $first_time || $current_time || $last_time) {
  74. $visitor_id = int(rand(2147483646)) + 1; #number under 2147483647
  75. $first_time = time();
  76. $current_time = time();
  77. $last_time = time();
  78. }
  79.  
  80. #check referer
  81. my $referer = $ENV{'HTTP_REFERER'};
  82. my $utmccn = 'utmccn%3D(direct)';
  83. my $utmcsr = '%7Cutmcsr%3D(direct)';
  84. my $utmcmd = '%7Cutmcmd%3D(none)';
  85. my $utmctr;
  86. if ( $referer =~ m{http://}xms ) {
  87. #strip host-name
  88. ($utmcsr = $referer) =~ s{\A http:// ([^/]*) .* \z}{$1}xms;
  89. $utmcsr = '%7Cutmcsr%3D'.$utmcsr;
  90.  
  91. if($utmcsr =~ m{$host_name}xms) {
  92. $utmcsr = '%7Cutmcsr%3D(direct)';
  93. }
  94. elsif ($referer =~ m{ezsch.ezweb.ne.jp}xms ){
  95. ($utmctr = $referer) =~ s{\A .+ query= ([^&]*) .* \z}{$1}xms;
  96. }
  97. elsif ($referer =~ m{search.mobile.yahoo.co.jp}xms ){
  98. ($utmctr = $referer) =~ s{\A .+ p= ([^&]*) .* \z}{$1}xms;
  99. }
  100. elsif ($referer =~ m{www.google.co.jp/m/}xms ){
  101. ($utmctr = $referer) =~ s{\A .+ q= ([^&]*) .* \z}{$1}xms;
  102. }
  103. else {
  104. $utmccn = 'utmccn%3D(referral)';
  105. $utmcmd = '%7Cutmcmd%3Dreferral';
  106. warn 'Referer: '.$referer;
  107. }
  108.  
  109. $utmccn = 'utmccn%3D(organic)' if $utmctr;
  110. $utmctr = '%7Cutmctr%3D'.$utmctr if $utmctr;
  111. $utmcmd = '%7Cutmcmd%3Dorganic' if $utmctr;
  112. }
  113.  
  114. ###############################################
  115.  
  116. #utmcc = cookiesettings
  117. my $urchinUrl = 'http://www.google-analytics.com/__utm.gif?utmwv=1'
  118. ."&utmn=$utmn"
  119. ."&utmcs=$utmcs"
  120. ."&utmsr=$utmsr"
  121. ."&utmsc=$utmsc"
  122. ."&utmul=$utmul"
  123. ."&utmje=$utmje"
  124. ."&utmfl=$utmfl"
  125. ."&utmdt=$utmdt"
  126. ."&utmhn=$utmhn"
  127. ."&utmr=$ENV{'HTTP_REFERER'}"
  128. ."&utmp=$utmp"
  129. ."&utmac=$utmac"
  130. .'&utmcc=__utma%3D'.$domainH.'.'.$visitor_id.'.'.$first_time.'.'.$last_time.'.'.$current_time.'.2'
  131. .'%3B%2B__utmb%3D'.$domainH
  132. .'%3B%2B__utmc%3D'.$domainH
  133. .'%3B%2B__utmz%3D'.$domainH.'.'.$last_time.'.2.2.'.$utmccn.$utmcsr.$utmctr.$utmcmd
  134. .'%3B%2B__utmv%3D'.$domainH.'.'.$visitor_id.'%3B'
  135. ;
  136. my $ua = LWP::UserAgent->new;
  137. my $request = HTTP::Request->new(GET => $urchinUrl);
  138. if ($ENV{'HTTP_ACCEPT_LANGUAGE'}) {
  139. $request->header('Accept-language' => $ENV{'HTTP_ACCEPT_LANGUAGE'});
  140. }
  141. if ($agent) {
  142. $ua->agent($agent);
  143. }
  144. my $response = $ua->request($request);
  145.  
  146. return 1;
  147. }
  148.  
  149. 1;

で、current_time等を更新してから初回だけタイムスタンプ類をセットして呼び出してやります。例としてはそこらも書いてやるべきなんかもしれませんが、セッション管理の方法は各々違うでしょうから、省略! 流れとしては...

  1. 主要な変数を設定(ホスト名、ドメインハッシュなど)
  2. URLからセッションIDを消す(セッションIDをURLに含めている場合)
  3. UAをセット
  4. 訪問時間をセット
  5. 参照元をセット
  6. URLを作って、送信!

...というような感じ。

UAのIF分岐は別にやらなくても良い。むしろ素のUAのままのほうが、「OS」等の解析結果が利用できるようになります。上記サンプルではちょっと詳しくキャリア別にカウントしたかったのと、そのまま検索botがDoCoMoに含まれることもあったため、自前で文字列作って渡してます。

検索キーワードの抽出は参照元の部分でやってます。検索エンジンごとにクエリ文字列の分解とかやってるのは泥臭く感じますが、Urchin.js見てみると、本家GoAnでも50近いサイトを登録して個別にやっているようですし、まあ致し方ないでしょう。数が少ないのは、ちょこっと運用していくうちに増やすとして...

いまんとこ、こんな感じです。

課題

参照元でドメインまでは出ても、そこから個別ページのURLが表示されないです。たぶんここまで空気だったutmrとの連携なんじゃないかなあ? まあドメインからログ検索すれば簡単に判るんですが、なんか悔しい...

あと、ページタイトルだけはどうしても取れない... これは実行タイミングの問題。うちのシステムだとテンプレ出力の前に実行しちゃうから、各ページタイトルを取得するのは難しいんだよなあ。テキストファイルとかから読み出すと重くなっちゃうし。DBアクセスは負けだと思ってる。

Note: スパム対策が面倒なので、コメント投稿を廃止しました。以前のコメントは残します。
ご意見・ご要望はtwitter@sigwygかはてブコメントにて。