ページごとのユニークなユーザー数(cookie単位)を取得する

cookieとpageを文字列で連結して、array_uniqueでcookieとpage完全一致の重複を削除して、そこからsubstrでurlのみ切り抜いて、array_count_valuesする。
※最初、cookieとpageで連想配列をつくって、そこから、連想配列のvalueの重複削除しようとしたが、何度やってもうまくいかず、これ考えるのに、4~5時間くらいかかった。。。

foreach ($cursor as $document) {
  if(!is_null($document->referrer) and $document->referrer != ""){
  $referrer = $document->referrer;
  $avgtime = $document->avgtime;
  $data1[] = array($referrer=>$avgtime); 
  }
  $page[] = $document->page;

  $a = $document->cookie;
  $b = $document->page;
  $data2[] = $a.$b;
}

$unique = array_values(array_unique($data2));
foreach($unique as $value){
    $urls[] = substr($value, 10);
}
$urls = array_count_values($urls);
arsort($urls);
var_dump($urls);

よしよしよし

繋げると

viewに反映

次は鬼門のリファラー関係
1.document.referrer でどれだけ取れるか
2.ga.js・twitterのsessionがどうやって定義しているか参考にする
3.vagrantだけでなく、実ドメインからのリンクでテストしながらつくる
->GAのようにorganic searchの検索ワードは抽出できないので、代替案を考える

リファラーからページごとの滞在時間を計算してPVと一緒に表示する

array_merge_recursiveでPVの配列と平均滞在時間の配列同士を結合させて、tableで表示する。

<?php 

$date1 = date("Y-m-d", strtotime("-10 day"));
  $date2 = date("Y-m-d H:i:s");

$mng = new MongoDB\Driver\Manager("mongodb://localhost:27017");
$filter =  &#91;
    'date' => [ '$gte' => $date1, '$lte' => $date2]
];
$options = [
  'projection' => ['_id' => 0],
  'sort' => ['_id' => -1],
];
$query = new MongoDB\Driver\Query($filter, $options);
$cursor = $mng->executeQuery('app.na007', $query);

foreach ($cursor as $document) {
  if(!is_null($document->referrer) and $document->referrer != ""){
  $referrer = $document->referrer;
  $avgtime = $document->avgtime;
  $data[] = array($referrer=>$avgtime); 
  }
  $page[] = $document->page;   
}
// var_dump($data);

function arraySum(array $arr)
{
    $res = [];
    if (is_array($arr)) {
        foreach ($arr as $val) {
            foreach ($val as $k => $v) {
                if (isset($res[$k])) {
                    $res[$k] = (double)($res[$k] + $v)/2;
                } else {
                    $res[$k] = (double)$v;
                }
            }
        }
    }
    return $res;
}
$data = arraySum($data);
// var_dump(arraySum($data));
$page = array_count_values($page);
// var_dump($page);
$newdata = (array_merge_recursive($page, $data));
arsort($newdata);
print_r("<pre>");
var_dump($newdata);
print_r("</pre>");
function s2h($avgtime){
  $hours = floor($avgtime / 3600);
  $minutes = floor(($avgtime / 60)%60);
  $seconds = $avgtime % 60;
  $hms = sprintf("%02d:%02d:%02d", $hours, $minutes, $seconds);
  return $hms; 
}
?>
<table>
<th>Page</th><th>PageView</th><th>Avg time</th>
      <?php
        $i = 0;
        foreach($newdata as $key => $value){
          if($i < 10 and !is_null($value&#91;1&#93;)){
            echo "<tr>";
            echo "<td>".$key."</td><td>".$value[0]."</td><td>".s2h($value[1])."</td>";
            echo "</tr>";
          } else {
            echo "<tr>";
            echo "<td>".$key."</td><td>".$value."</td><td>00:00:00</td>";
            echo "</tr>";
          }
          $i++;    
        }
      ?>
</table>

あ、ページごとのユニークなユーザー数もカウントしたくなってきた(笑)

リファラーの平均滞在時間を算出する

foreachでkeyにvalueを入れる際に、すでにvalue1があれば、(value1 + value2)/2とすれば、valueの平均が算出されるので、それをforeachで回せばよい。
具体的には、$res[$k] = ($res[$k] + $v)/2; のところ。

foreach ($cursor as $document) {
  if(!is_null($document->referrer) and $document->referrer != ""){
  $referrer = $document->referrer;
  $avgtime = $document->avgtime;
  $data[] = array($referrer=>$avgtime); 
  }  
}
var_dump($data);

function arraySum(array $arr)
{
    $res = [];
    if (is_array($arr)) {
        foreach ($arr as $val) {
            foreach ($val as $k => $v) {
                if (isset($res[$k])) {
                    $res[$k] = ($res[$k] + $v)/2;
                } else {
                    $res[$k] = $v;
                }
            }
        }
    }
    return $res;
}
var_dump(arraySum($data));

リファラーがある場合の各ページの平均滞在時間

新規ユーザーとリターンユーザーを表示する

$visit = json_encode(array_count_values($visit))を配列で渡して、
chart.jsで表示する。

  var visit = JSON.parse('<?php echo $visit; ?>');
  console.log(visit);
  var ctx = document.getElementById("myChart").getContext('2d');
  var myChart = new Chart(ctx, {
  type: 'pie',
  data: {
    labels: ["New", "Returned"],
    datasets: [{
      backgroundColor: [
        "#2ecc71",
        "#3498db",
      ],
      data: [visit['New User'],visit['Returned User']]
    }]
  }
});

ここまではそんなに難しくないんだが、次が問題。
(1)時間ごと、もしくは(2)日付ごとのアクセスを棒グラフで表示する。
まず分けて考えよう。

1.時間ごとの場合
->chart.jsのラベルは24時間
->訪問時間 y:m:d h:m:s の該当するhの数をcountしてjsonにencodeする

2.日付ごとの場合
->ラベルは期間の日付
->訪問時間 y:m:d h:m:s の該当するy:m:dの数をcountしてjsonにencodeする

ややこしいので、時間から作る。

cookie, session情報をajaxでpostする

viewから呼び込んだjsで、cookie, session情報を配列にpushして、ajaxでサーバー側にpostします。
na.js

var unix = Math.floor((new Date).getTime()/1E3);
var visit = "New User";
if(navigator.cookieEnabled){
    var all=document.cookie + ";";
    var cStart = all.indexOf("_na=",0);
    if(cStart == -1){   
        var char = "abcdefghijklmnopqrstuvwxyz123456789";
        var id = "";
        for(var i=0; i<10; i++){
            id += char[Math.floor(Math.random()*35)];
        }
        var expire = new Date();
        expire.setTime(expire.getTime() + 1000*3600*24*365*2);
        document.cookie="_na=NA1."+id+"."+unix+";expires=" + expire.toUTCString();
        a.push(['vist',visit]);
        a.push(['referrer',document.referrer]);
    } else {
        visit = "Returned User";
        a.push(['visit',visit]);
        var c = window.sessionStorage.getItem(['ScribeTransport']);
        if(c){
            d = JSON.parse(c);
            a.push(['referrer',d.path]);
            a.push(['滞在時間',(unix - d.time)]);        
        }       
    }
}
var data = '{"path":"'+location.pathname+'", "time":"'+unix+'"}';
window.sessionStorage.setItem(['ScribeTransport'],[data]);

a.push(['host',location.host]);
$(function(){
    $(document).ready(function(){
            var postData = {"userdata": a};
            $.post(
                "doubleclick.php",
                 postData,
                 function(data){
                    $(".box").html(data);
                }
            );
        });
});

doubleclick.php
$_POSTを受け取ります。

$date = date("Y-m-d H:i:s");
$data = $_POST['userdata'];
echo "アナリティクスアカウントID:" .$data[0][1]."<br><br>訪問時間:" .$date . "<br> ip:" .$data[1][1]."<br> user:" .$data[2][1]."<br> リファラー:".$data[3][1]."<br> 前のページでの滞在時間:".$data[4][1]."秒<br>";

ちゃんとsession, cookieがsetされ、配列がpostされました。

さあ、次は、postされたデータをどう処理してDBに格納するかですね。
analyticsのview(主にchart.jsを想定)も一緒につくりながらやった方が、効率良さそうな気がします。

sessionとcookieで閲覧開始・リファラー・滞在時間を取得する

1.cookieがsetされていればreturn user, setされていなければnew user
2.new userの場合、document.referrerで、リファラーを取得
3.cookieがsetされていれば、sessionのlocation.pathnameでリファラーを取得
4.cookieがsetされていれば、sessionのunixtimeの差でページ滞在時間を取得

var unix = Math.floor((new Date()).getTime()/1E3);
var data = '{"path":"'+location.pathname+'", "time":"'+unix+'"}';
var visit = "new user<br>";
if(navigator.cookieEnabled)
{
	var all=document.cookie + ";";
	var cStart = all.indexOf("_na=",0);
	if(cStart == -1){   
		var char = "abcdefghijklmnopqrstuvwxyz123456789";
		var id = "";
		for(var i=0; i<10; i++){
			id += char&#91;Math.floor(Math.random()*35)&#93;;
		}
		document.cookie="_na=NA1."+id+"."+unix+";";
		window.sessionStorage.setItem(&#91;'ScribeTransport'&#93;,&#91;data&#93;);
		document.write(visit);
		document.write("リファラー" + document.referrer + "<br>");
	} else {
		visit = "returned user<br>";
		document.write(visit);
		var a = window.sessionStorage.getItem(['ScribeTransport']);
		b = JSON.parse(a);
		var unix = Math.floor((new Date()).getTime()/1E3);
		document.write("リファラー" + b.path + "<br>");
		document.write("滞在時間" +(unix - b.time)+ "秒");
		window.sessionStorage.setItem(['ScribeTransport'],[data]);
	}
}else{
	window.sessionStorage.setItem(['ScribeTransport'],[data]);
}

あれ、待てよ、cookieがsetされていても、sessionがsetされていないパターンもあるな。
if(a)と書き換えます。cookieのexpireは1000*3600*24*365*2(730日)とします。

var unix = Math.floor((new Date).getTime()/1E3);
var visit = "new user<br>";
if(navigator.cookieEnabled){
	var all=document.cookie + ";";
	var cStart = all.indexOf("_na=",0);
	if(cStart == -1){   
		var char = "abcdefghijklmnopqrstuvwxyz123456789";
		var id = "";
		for(var i=0; i<10; i++){
			id += char&#91;Math.floor(Math.random()*35)&#93;;
		}
		var expire = new Date();
		expire.setTime(expire.getTime() + 1000*3600*24*365*2);
		document.cookie="_na=NA1."+id+"."+unix+";expires=" + expire.toUTCString();
		document.write(visit);
		document.write("リファラー" + document.referrer + "<br>");
	} else {
		visit = "returned user<br>";
		document.write(visit);
		var a = window.sessionStorage.getItem(['ScribeTransport']);
		if(a){
			b = JSON.parse(a);
			document.write("リファラー" + b.path + "<br>");
			document.write("滞在時間" +(unix - b.time)+ "秒");			
		}		
	}
}
var data = '{"path":"'+location.pathname+'", "time":"'+unix+'"}';
window.sessionStorage.setItem(['ScribeTransport'],[data]);

次は、このnew user・return user, リファラー, 滞在時間のデータを、サーバー側(ここではdoubleclick.php)に渡すところですね。

amazon・facebook・googleのsession strageを見てみよう

session storageとは?
>sessionStorage に保存されたデータはページのセッションが終了するときに消去されます。ページのセッションはブラウザを開いている限り、ページの再読み込みや復元を越えて持続します。新しいタブやウィンドウにページを開くと、新しいセッションが開始します。

なるほど、やりたいことに近いです。
続いて、各社のsession storageのkeyとvalueを見ていきましょう。

amazon.co.jpのsession storage:
CSM_previousURL
csmtid
rwolLastPage
rwolLastPageSet

CSM_previousURLとrwoLastPageのvalueはcurrent directoryのようです。
なにこれ?

facebookのsession storage:
sp_pi:{“pageInfo”:{“scriptPath”:”/”,”categoryToken”:”a6bebc6e”,”extraData”:{“imp_id”:”hogehoge”}},”clickPoint”:null,”time”:1523665957490}
うお、jsonだ。。。timeはunix timeっぽいですね。

google.co.jpのsession storage:
eob;;xpli p:m|l:14_[“4″,”2”]
_c;;i p:*|l:9007199254hogehoge_16
eob;;xplq p:m|l:15_[“google analytics 滞在時間”,”セッションとは”]
うおーーー前回の検索ワードがsessionに残ってる!

twitter.com/?lang=ja
ScribeTransport
{“internal_referer”:null,”client_version”:”macaw-swift”,”browser_name”:”chrome”,”duration_ms”:4231,”event_namespace”:{“client”:”web”,”page”:”home”,”section”:”home”,”component”:”full”,”element”:”ttft”,”action”:”ttft”},”triggered_on”:1523,”format_version”:2,”_categ..以下略
twitterもjsonでunixtimeが入っています。

あ~、facebook・twitterはunix time使ってるってことは、完全にトラッキングしてますね。amazonもトラッキングしてますね。googleに至ってはセッションに検索ワードが入ってる。なんかもー力抜けてきた。。
各社の設計から考えると、cookieとsessionの性質上、cookieはnewからreturnか判別、ページ遷移の挙動はsessionにunix time入れて取った方が良さそうですね。
要素が多いtwitterが一番参考になりそうです。

yahoo.co.jp
-> session storage empty
rakuten.co.jp
->phoenix, phoenix__rg, RJSCountDownTimer, RALRTID

ページ滞在時間とnew・return userを判定する

cookieのunix timeでページ滞在時間を計算します。

var unix = Math.floor((new Date()).getTime()/1E3);
var visit = "first visit!";
if(navigator.cookieEnabled)
{
	var all=document.cookie + ";";
	var cStart = all.indexOf("_na=",0);
	if(cStart == -1){   
		var char = "abcdefghijklmnopqrstuvwxyz123456789";
		var id = "";
		for(var i=0; i<10; i++){
			id += char&#91;Math.floor(Math.random()*35)&#93;;
		}
		document.cookie="_na=NA1."+id+"."+unix+";";
		document.write(visit);
	} else {
		visit = "returned user";
		var cEnd = all.indexOf(";",cStart);
		var cnt = all.substring(cStart,cEnd - 10); //_na=NA1.0000000000.
		document.cookie=cnt+unix+";";
		document.write("前回の滞在時間" + (unix - all.substring(cStart+19,cEnd)) + "秒<br>"); 
		document.write(visit);
	}
}else{
	document.write("cookieが使用できません。");
}

cookieのexpireとunix timeの差をどう考えるかですね。
exireをgoogleのように2年として

1.前回のcookie発行から10分以上差がある
unix – all.substring(cStart+19,cEnd) > 60 * 10
-> return userとみなす
2.前回のcookie発行から10分以内
unix – all.substring(cStart+19,cEnd) <= 60 * 10 -> ユーザのページ遷移とみなす
3.新規のcookie 発行
-> new visitor

何分滞在するかはページの性質によって変わってくるので、例えば1時間の動画のページでは、上記のロジックは機能しませんね。
するとセッションでしょうか。。
あれ、よくみたら、indexedDBに
https://googleads.doubleclick.netが入っていますね。

LocalStrageにもgoogleの値が入っています。
google_experiment_mod 602

これはadsenceのようです。

google_experiment_mod is used by Google AdSense for experimenting with which advertisement perform best on a website.

なんだこれは?
google_pub_config {“sraConfigs”:{“2”:{“sraTimeout”:60000},”4″:{“sraTimeout”:60000}}}

よーわからんぞ。

初めての訪問時はcookieにid・unixtimeを発行し2回目以降はunixtimeのみ更新

var cook;
var cStart,cEnd;
var cnt;

var unix = Math.floor((new Date()).getTime()/1E3);

if(navigator.cookieEnabled)
{
	cook=document.cookie + ";";
	console.log(cook);

	cStart = cook.indexOf("_na=",0);
	if(cStart == -1){   
		document.write("1回目の訪問です!");

		var char = "abcdefghijklmnopqrstuvwxyz123456789";
		var id = "";
		for(var i=0; i<10; i++){
			id += char[Math.floor(Math.random()*35)];
		}
		document.cookie="_na=NA."+id+"."+unix+";";
	} else {
		cEnd = cook.indexOf(";",cStart);
		cnt = cook.substring(cStart,cEnd - 10); // cStart + "_na="
		try {
			document.write(cnt);
			document.cookie=cnt+unix+";";
		} catch(e){
			document.write("訪問回数の取得に失敗しました。");
		}
	}
}else{
	document.write("cookieが使用できません。");
}

key:_na
value:NA.5ub8j1xn2p.1523632591

あれ、ブラウザを更新しても、GAのunixtimeが更新されない。。。何故だ????

cookieから何回目の訪問か判定する

<script>
var cook;
var cStart,cEnd;
var cnt;

if(navigator.cookieEnabled)
{
	cook=document.cookie + ";";
	console.log(cook);

	cStart = cook.indexOf("counts=",0);

	if(cStart == -1){   //indexOfで存在しない時は-1を返す
		document.write("1回目の訪問です!");
		document.cookie="counts=1;";
	} else {
		cEnd = cook.indexOf(";",cStart);
		cnt = cook.substring(cStart+7,cEnd); // cStart + "counts="
		try {
			cnt = parseInt(cnt)+1;
			document.write(cnt+"回目の訪問です!");

			document.cookie="counts="+cnt+";";
		} catch(e){
			document.write("訪問回数の取得に失敗しました。");
		}
	}
}else{
	document.write("cookieが使用できません。");
}

</script>

ga.jsのcookieの処理 18箇所出てきます。
出てくるのは、
_setCookiePath、_setVisitorCookieTimeout、_setSessionCookieTimeout、_setCampaignCookieTimeout、_cookiePathCopy、_setCookieTimeout、_setCookiePersistence
後半の、J.cookieは、J=documentなので、普通にdocument.cookieです。

b=n.cookie.split(";");

var Mc=N(),Nc=N(),Yb=N(),Jc=N(),Kc=N(),Lc=Va("utmtCookieName"),Cd=Va("displayFeatures"),Oc=N(),of=Va("gtmid"),Oe=Va("uaName"),Pe=Va("uaDomain"),Qe=Va("uaPath"),pf=Va("linkid"),w=N(),x=N(),y=N(),z=N();

V("_setCookiePath",P,9,0);

V("_setVisitorCookieTimeout",cb,28,1);V("_setSessionCookieTimeout",db,26,1);V("_setCampaignCookieTimeout",eb,29,1);

a("_cookiePathCopy",U.prototype.ha,30);

V("_setCookieTimeout",eb,25,3);V("_setCookiePersistence",cb,24,1);

pd=function(a){
	var b=[],c=J.cookie.split(";");
	a=new RegExp("^\\s*"+a+"=\\s*(.*?)\\s*$");
	for(var d=0;d

cookieのvalueは、"GA1.1.200404109.1517112693"とした場合、Timeoutの処理はUNIX timeで処理していそうですね。
あ、J.domain は、document.domainなので、refererのdomainが一緒だった場合は、UNIX Timeを引けば、滞在時間を求められますね。 なるほど、そういうことか。めちゃくちゃ頭いいな~、Paul Muret。閲覧開始数、直帰率は、リファラを見れば、判別できますね。
あれ、でも、同じドメインに遷移の場合はunix timeもsubstructすれば滞在時間が取得できますが、他のドメインに行ったり、ブラウザを閉じた場合、どうやってページ滞在時間を取得しているんでしょうか? 
なに~、わからん

あれ、こんな記載が。。。
>最後に訪問したページの滞在時間はGoogleアナリティクスが測定できないという注意点があるので気を付けましょう。
なるほど、やっぱりそうか!なぜ、cookieにunixtime入れてるのか完全に理解した!

ユーザー固有の ID:200404109 は何でしょうか?
あれ、もしかして、このユーザー(ID:200404109)が1回のセッションでどういうページ遷移をしたかを判別しているために発行しているのでは?
おお、少し仕組みが判ってきた~