网站标识javascript自定义对象状态码web前端数据采集方法
优采云 发布时间: 2021-06-25 21:24网站标识javascript自定义对象状态码web前端数据采集方法
网站Identification
javascript
自定义对象
状态码
网络服务器
Nginx $status
发送的内容量
网络服务器
Nginx $body_bytes_sent
确定埋点代码
买点是网站分析常用的数据采集方法。核心是在需要数据采集的关键点植入统计代码,执行数据采集。例如,在 Google Analytics 原型的情况下,需要将其提供的 javascript 片段插入到页面中。该片段通常称为嵌入代码。 (以Google的内嵌代码为例)
var _maq = _maq || [];
_maq.push(['_setAccount', 'UA-XXXXX-X']);
(function() {
var ma = document.createElement('script'); ma.type =
'text/javascript'; ma.async = true;
ma.src = ('https:' == document.location.protocol ?
'https://ssl' : 'http://www') + '.google-analytics.com/ma.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore( m a, s);
})();
其中_maq为全局数组,用于放置各种配置,每个配置的格式为:
_maq.push(['Action','param1','param2', ...]);
_maq 的机制不是重点。重点是匿名函数背后的代码。这段代码的主要目的是通过document.createElement方法创建脚本,并按照协议(http或https)将src指向对应的ma.js,最后插入一个外部js文件(ma.js)元素进入页面的dom树。
注意ma.async = true表示异步调用外部js文件,即不阻塞浏览器解析,在外部js下载完成后异步执行。此属性是 HTML5 中新引入的。
前端数据采集脚本
数据采集脚本(ma.js)会在被请求后执行。一般应做到以下几点:
通过浏览器内置的javascript对象采集信息,如页面标题(通过document.title)、referrer(最后一个url,通过document.referrer)、用户显示分辨率(通过windows.screen)、cookie信息(通过document.cookie) 等。
分析_maq数组并采集配置信息。这可能包括用户定义的事件跟踪、业务数据(例如电子商务网站 产品编号等)。以上两步采集到的数据,按照预定义的格式(获取请求参数)进行解析拼接。请求一个后台脚本,把http请求参数中的信息放到后台脚本中。
这里唯一的问题是第4步。javascript请求后端脚本常用的方法是ajax,但是不能跨域请求ajax。一个常用的方法是在js脚本中创建一个Image对象,将Image对象的src属性指向后端脚本并携带参数。这时候就实现了跨域请求后端。这就是为什么后端脚本通常伪装成 gif 文件的原因。
示例代码
(function () {
var params = {};
//Document 对象数据
if(document) {
params.domain = document.domain || '';
params.url = document.URL || '';
params.title = document.title || '';
params.referrer = document.referrer || '';
}
//Window 对象数据
if(window && window.screen) {
params.sh = window.screen.height || 0;
params.sw = window.screen.width || 0;
params.cd = window.screen.colorDepth || 0;
}
//navigator 对象数据
if(navigator) {
params.lang = navigator.language || '';
}
//解析_maq 配置
if(_maq) {
for(var i in _maq) {
switch(_maq[i][0]) {
case '_setAccount':
params.account = _maq[i][1];
break;
default:
break;
}
}
}
//拼接参数串
var args = '';
for(var i in params) {
if(args != '') {
args += '&';
}
args += i + '=' + encodeURIComponent(params[i]);
}
//通过 Image 对象请求后端脚本
var img = new Image(1, 1);
img.src = ' http://xxx.xxxxx.xxxxx/log.gif? ' + args;
})();
整个脚本被放置在一个匿名函数中,以确保它不会污染全局环境。其中 log.gif 是后端脚本。
后台脚本
log.gif 是一个后台脚本,一个伪装成 gif 图片的脚本。后端脚本一般需要完成以下几件事:
分析http请求参数获取信息。获取一些客户端无法从Web服务器获取的信息,如访问者ip。将信息按格式写入日志。生成一个1×1的空gif图片作为响应内容,并将响应头的Content-type设置为image/gif。通过响应头中的Set-cookie设置一些需要的cookie信息。
设置cookie的原因是因为如果要跟踪唯一的访问者,通常的做法是按照规则生成一个全局唯一的cookie,如果发现客户端没有就植入给用户请求时指定的跟踪cookie,否则 Set-cookie 将获取的跟踪cookie放入,以保持相同的用户cookie不变。虽然这种方式并不完美(例如,一个用户清除 cookie 或更换浏览器将被视为两个用户),但目前已被广泛使用。
我们使用nginx的access_log进行日志采集,但是存在nginx配置本身逻辑表达能力有限的问题,所以我们选择OpenResty来做这个。
OpenResty 是一个基于 Nginx 的高性能应用开发平台。它集成了很多有用的模块,其中核心是通过ngx_lua模块集成了Lua,从而可以在nginx配置文件中使用Lua来表达业务。
Lua 是一种轻量级紧凑的脚本语言,采用标准 C 语言编写,并以源代码形式开放。其设计目的是嵌入到应用程序中,为应用程序提供灵活的扩展和定制功能。
首先需要在nginx配置文件中定义日志格式:
log_format tick
"$msec||$remote_addr||$status||$body_bytes_sent||$u_domain||$u_url|
|$u_title||$u_referrer||$u_sh||$u_sw||$u_cd||$u_lang||$http_user_ag
ent||$u_account";
注意这里以u_开头的变量是我们后面自己定义的变量,其他都是nginx内置的变量。然后是两个核心位置:
location / log.gif {
#伪装成 gif 文件
default_type image/gif;
#本身关闭 access_log,通过 subrequest 记录 log
access_log off;
access_by_lua "
-- 用户跟踪 cookie 名为__utrace
local uid = ngx.var.cookie___utrace
if not uid then
-- 如果没有则生成一个跟踪 cookie,算法为
md5(时间戳+IP+客户端信息)
uid = ngx.md5(ngx.now() ..
ngx.var.remote_addr .. ngx.var.http_user_agent)
end
ngx.header['Set-Cookie'] = {'__utrace=' .. uid ..
'; path=/'}
if ngx.var.arg_domain then
-- 通过 subrequest 子请求 到/i-log 记录日志,
将参数和用户跟踪 cookie 带过去
ngx.location.capture('/i-log?' ..
ngx.var.args .. '&utrace=' .. uid)
end
";
#此请求资源本地不缓存
add_header Expires "Fri, 01 Jan 1980 00:00:00 GMT";
add_header Pragma "no-cache";
add_header Cache-Control "no-cache, max-age=0, must-
revalidate";
#返回一个 1×1 的空 gif 图片
empty_gif;
}
location /i-log {
#内部 location,不允许外部直接访问
internal;
#设置变量,注意需要 unescape,来自 ngx_set_misc 模块
set_unescape_uri $u_domain $arg_domain;
set_unescape_uri $u_url $arg_url;
set_unescape_uri $u_title $arg_title;
set_unescape_uri $u_referrer $arg_referrer;
set_unescape_uri $u_sh $arg_sh;
set_unescape_uri $u_sw $arg_sw;
set_unescape_uri $u_cd $arg_cd;
set_unescape_uri $u_lang $arg_lang;
set_unescape_uri $u_account $arg_account;
#打开日志
log_subrequest on;
#记录日志到 ma.log 格式为 tick
access_log /path/to/logs/directory/ma.log tick;
#输出空字符串
echo '';
}
这个脚本使用了很多第三方ngxin模块(都收录在OpenResty中),关键点用注释标记。你不需要完全理解每一行的意思,只要你在完成我们提到的结束逻辑之后知道这个配置就可以了。
日志格式
日志格式主要考虑日志分隔符,一般有几个选项:
固定数量的字符、制表符分隔符、空格分隔符、一个或多个其他字符、特定的开始和结束文本。
日志分割
日志采集系统只要访问日志,文件就会变得非常大,不容易把日志管理在一个文件中。通常日志是按照时间段划分的,比如每天或者每小时划分一个日志。通过crontab定时调用一个shell脚本来实现,如下:
_prefix="/path/to/nginx"
time=`date +%Y%m%d%H`
mv ${_prefix}/logs/ma.log ${_prefix}/logs/ma/ma-${time}.log
kill -USR1 `cat ${_prefix}/logs/nginx.pid `
该脚本将ma.log移动到指定文件夹并重命名为ma-{yyyymmddhh}.log,然后发送USR1信号给nginx重新打开日志文件。
USR1 通常用于通知应用程序重新加载配置文件。向服务器发送一个USR1信号会导致以下步骤发生:停止接受新连接,等待当前连接停止,重新加载配置文件,重新打开日志文件,重启服务器,从而实现比较平滑的变化无需关机。
cat ${_prefix}/logs/nginx.pid 取nginx的进程号
然后在/etc/crontab中添加一行:
59 * * * * root /path/to/directory/rotatelog.sh
每小时 59 分钟启动此脚本以执行日志轮换操作。