配合 Measurement Protocol 优化 Google Analytics
我使用 Google Analytics 来统计访问情况,按照官方说明,直接引入 js 文件就可以了,但后来因为使用了 React 这些 SPA ,官方提供的方法不再起作用,就用了别人做好的专门用于这些框架的库。最近了解到 Measurement Protocol 这么一个东西,正好可以替代目前的引入方式。
我对 Google Analytics 的需求并不高,只看他的访客,页面,来源这些数据,使用频率也不高,算是可有可无的这么一个需求吧,但还是想尝试下这种新的方式,因为对我来说还是有些好处的:
- 无需使用官方 js ,减少加载
- 使用自已的域名,不用担心被拦截
- 不用担心个别地区官方 js 加载有问题
Google Analytics(分析)Measurement Protocol 可让开发者通过 HTTP 请求直接向 Google Analytics(分析)服务器发送原始用户互动数据。这样,开发者就可以衡量在各种环境中用户与商家互动的情况。
以上是官方的介绍,我们可以利用 Measurement Protocol 做一个中间代理,浏览器收集的信息先发送至代理,再有代理通过 Measurement Protocol 向 Google Analytics 提交访客信息
收集信息
获取浏览器标题,URL,分辨率,语言... 可以直接读取 window document location 相关属性,但如何把数据送到自己的后端,倒是需要考虑下。使用比较多的方案是新建一个 img 对象,设置 src 为后端 url ,将数据放在 params 里。还有一种是使用 navigator.sendBeacon
navigator.sendBeacon(url, data);
这个方法主要用于满足统计和诊断代码的需要,这些代码通常尝试在卸载(unload)文档之前向 web 服务器发送数据。
看描述信息,似乎这个方法就是专门用来做这种事的,而且使用也简单,只需传入目标 URL 和数据,就会发起 POST 请求,数据类型可以是 ArrayBufferView 或 Blob, DOMString 或者 FormData
具体的参数名称可以从 Measurement Protocol 参数参考 上找到。我自己也简单的把这些逻辑封装成了 ga-beacon
接受信息
需要有一个后端来接数据,我选择了阿里云的函数计算和 API 网关,使用 Serverless Framework 进行部署。但在部署的时候,发现有一个问题:如果 API 网关要能与函数通信,必须在同一实例类型(经典网络或 VPC),但 Serverless Framework 默认创建的 API 实例类型是经典网络,函数又是 VPC,然后我也没找到相关配置的地方,只好手动创建 API 网关。
对于每个不同的访客,需要用 UUID 来区分,我将这个信息放在 cookie 里,由于 Chrome 改了策略,我又使用了不同的域名,在加上 navigator.sendBeacon 使用的是 POST 请求,必须给 Set-Cookie 加上 SameSite=None; Secure;
这也导致后端必须使用 HTTPS
以下是我的函数计算主文件:
const axios = require("axios");
const cookie = require("cookie");
const uuidv4 = require("uuid").v4;
exports.proxy = async (event, context, callback) => {
try {
const request = JSON.parse(event);
const requestBody =
request.isBase64Encoded === true
? Buffer.from(request.body, "base64").toString()
: request.body;
const requestBodyJson = JSON.parse(requestBody);
const requestHeader = Object.keys(request.headers).reduce((header, key) => {
header[key.toLowerCase()] = request.headers[key];
return header;
}, {});
const requestCookie = cookie.parse(requestHeader["cookie"] || "");
const requestUUID = requestCookie["uuid"];
const responseUUID = requestUUID || uuidv4();
const gaBody = Object.assign({}, requestBodyJson, {
v: 1,
cid: responseUUID,
uip: request.headers.ClientIP || "",
});
const gaBodyString = Object.keys(gaBody)
.map((x) => {
return `${x}=${encodeURIComponent(gaBody[x] || "")}`;
})
.join("&");
await axios({
url: "https://www.google-analytics.com/collect",
method: "post",
data: gaBodyString,
});
const response = {
isBase64Encoded: false,
statusCode: 204,
headers: {
"Content-Type": undefined,
"Set-Cookie": requestUUID
? undefined
: cookie.serialize("uuid", responseUUID, {
maxAge: 63072000,
httpOnly: true,
sameSite: "none",
secure: true,
}),
},
};
callback(null, response);
} catch (error) {
callback(error);
}
};