谷歌分析优化加载和防止拦截

关键词: 谷歌分析 “Google Analytics” “Measurement Protocol” 服务端使用谷歌分析
总结一下这次折腾
开始看到了imququ网站的beacon.html,通过这种方式来统计访问流量,感到挺好玩的,然后就查了一些东西,发现统计网站统计客户访问原理也差不多是这样,通过加载一个js文件来搜集浏览者信息,然后发送到自家服务器。
这样有两个好处:

  • 一是加快网页加载,谷歌统计经常还是加载很慢,并且发送collect信息时也需要很长时间
  • 二是可以防止uBlock Origin等拦截,现在浏览网页基本都会用这东西了。
    其实现原理为客户端统计信息=>网站服务器=>谷歌服务器。将用户客户端信息发送到自己的服务器也可以在服务器上自行处理统计,之所以能中转是因为谷歌服务器有提供api,可以通过参数uip来传递IP地址,其他的没有提供类似方法的就不能用了。

折腾1

开始我的想法也是向imququ这个网站一样,简单统计浏览者信息然后发送到服务器进行处理。
网页中附加以下js

1
2
3
4
5
6
7
(function(e, n, o, f) {
var t = e.screen,
a = encodeURIComponent,
r = ["dt=" + a(a(document.title)), "dl=" + a(f.href), "dr=" + a(a(document.referrer)), "ul=" + (o.language || o.browserLanguage).toLowerCase(), "sd=" + t.colorDepth + "-bit", "sr=" + t.width + "x" + t.height, "vp=" + e.innerWidth + "x" + e.innerHeight, "de=" + (n.characterSet || n.charset), "ua=" + a(o.userAgent), "_" + +new Date],
i = "?" + r.join("&");
img = new Image, img.src = "/stat" + i;
})(window, document, navigator, location);

开始我是想用 服务端使用 Google Analytics这篇文章写的来折腾,不过不怎么会thinkjs,玩了半天不怎么会就放弃了,发送信息谷歌服务器老是识别不了。

另备注下post测试工具:谷歌插件ARC(Advanced REST client)挺好用的,感觉比那个postman好用多了。另外也可以通过curl来测试。

然后就用php作为服务端,初步就仅仅记录访问信息,没有转发谷歌服务器,nginx用try_files内部重定向下。
关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ip = getIp();
$dt = $_GET['dt'];
$dr = $_GET['dr'];
$ul = $_GET['ul'];
$sd = $_GET['sd'];
$sr = $_GET['sr'];
$sv = $_GET['sv'];
$ti = $_GET['ti'];
$arr = array($ip,$dt,$dr,$ul,$sd,$sr,$sv,$ti);
$arrjson = json_encode($arr);

if (file_put_contents('arr.json', "${arrjson}\n", FILE_APPEND|LOCK_EX)) {
header('Content-Type: image/gif');
echo 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
} else {
header('Content-Type: text/plain');
echo 'false';
}

折腾2

网页中加如上统计代码
修改服务端php,增加发送功能。记得要将php设为非阻塞方式,避免等待时间过长。
关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
$v = 1;
$t ='pageview';
$tid = 'UA-000000-0';
$z = mt_rand();

$uip = getIp();
$dt = $_GET['dt'];
$dl = $_GET['dl'];
$dr = $_GET['dr'];
$ul = $_GET['ul'];
$sd = $_GET['sd'];
$sr = $_GET['sr'];
$vp = $_GET['vp'];
$de = $_GET['de'];
$ua = $_GET['ua'];
$cid = clientId();

send_post2('https://www.google-analytics.com/collect', $post_data);

function clientId() {
$cid = $_COOKIE["statcid"];
if(!$cid) {
try {
$cid = Uuid::uuid4()->toString(); //i.e. 25769c6c-d34d-4bfe-ba98-e0ee856f3e7a
} catch (UnsatisfiedDependencyException $e) {
//TODO err log
$cid = gen_uuid();
//echo 'Caught exception: ' . $e->getMessage() . "\n";
}
setcookie("statcid", $cid, time() + 3600 * 24 * 365 * 2); //两年有效期
}
return $cid;
}

//cid
function gen_uuid() {
return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low"
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),

// 16 bits for "time_mid"
mt_rand( 0, 0xffff ),

// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
mt_rand( 0, 0x0fff ) | 0x4000,

// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
mt_rand( 0, 0x3fff ) | 0x8000,

// 48 bits for "node"
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
);
}

function send_post($url, $post_data) {
$postdata = http_build_query($post_data);
$options = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type:application/x-www-form-urlencoded',
'content' => $postdata,
'timeout' => 15 * 60 // 超时时间(单位:s)
)
);
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);

return $result;
}

function send_post2($url, $post_data) {
$postData = '';
foreach($post_data as $k => $v) {
$postData .= $k . '=' . $v . '&';
}
$postData = rtrim($postData, '&');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, count($postData));
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
$output = curl_exec($ch);
if($output === false)
{
echo "Error Number:".curl_errno($ch)."<br>";
echo "Error String:".curl_error($ch);
}
curl_close($ch);
return $output;
}

折腾3

在谷歌统计的后台看到好像以上方法都没有体现出会话时间等一些参数,想着还不如就用它的统计文件,然后中转一下。就打算直接修改analytics.js文件,将收集的信息发送到我的统计服务器。

这里本来想着用nginx反向代理直接获取文件并替换其中信息的,用nginx的’ngx_http_sub_module’和ngx_http_substitutions_filter_module都试了不知道为什么不行(用多说的embed.js文件测试的)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
header('Content-Type: image/png');
echo 'data:image/png;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';

$a = $_SERVER["QUERY_STRING"];
$a = $a . "&uip=" . getIp();
parse_str($a, $aArray);
$log->info('receive', $aArray);

send_post2('https://www.google-analytics.com/collect', $a);

function send_post2($url, $post_data) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, count($post_data));
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
$output = curl_exec($ch);
if($output === false)
{
echo "Error Number:".curl_errno($ch)."<br>";
echo "Error String:".curl_error($ch);
}
curl_close($ch);
return $output;
}