网站内容采集(为什么要进行网站流量数据统计分析?(一)?)
优采云 发布时间: 2022-02-09 20:25网站内容采集(为什么要进行网站流量数据统计分析?(一)?)
为什么要对网站流量数据进行统计分析?
随着大数据时代的到来,各行各业产生的数据爆炸式增长,大数据技术从之前的“虚无”成为可能。在每个行业。比如对网站流量数据进行统计分析,可以帮助网站管理员、运营商、推广者等实时获取网站流量信息,分析流量来源,网站@ > 内容、网站@网站访客特征等方面为网站分析提供数据基础。这将有助于增加网站流量,提升网站用户体验,让更多的访客落户并成为会员或客户,以更少的投入获得最大的收益。
网站流量日志数据采集原理分析
首先,用户的行为会触发浏览器对要统计的页面的http请求,比如打开某个网页。当网页打开时,页面中嵌入的javascript代码将被执行。
嵌入是指:预先在网页中添加一小段javascript代码。此代码片段一般会动态创建脚本标签,并将 src 属性指向一个单独的 js 文件。这时这个单独的js文件(图中绿色节点)就会被浏览器请求并执行,这个js往往是真正的数据采集脚本。
数据采集完成后,js会请求一个后端数据采集脚本(图中backend),一般是伪装成图片的动态脚本,js会将采集到的数据通过http参数传递给后端。脚本,后端脚本解析参数并以固定格式记录到访问日志中,并可能在http响应中为客户端植入一些cookies进行跟踪。
设计实施
根据原理分析,结合谷歌分析,如果要搭建自定义日志数据采集系统,需要做以下几件事:
确定信息的采集
确定埋藏码
嵌入是网站分析的常用数据采集方法。核心是在需要执行数据采集的关键点植入统计代码,执行数据采集。例如,在 Google Analytics 原型的情况下,需要将其提供的 javascript 片段插入到页面中。这个片段通常被称为埋藏代码。(以谷歌的埋藏代码为例)
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)创建脚本,从而引入一个外部js文件(ma.js)。) 将src指向对应的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服务器获取一些客户端无法获取的信息,比如guest 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,然后向 nginx 发送 USR1 信号以重新打开日志文件。
USR1 通常用于告诉应用重新加载配置文件,向服务器发送 USR1 信号会导致以下步骤发生:停止接受新连接,等待当前连接停止,重新加载配置文件,重新打开日志文件,重启服务器,导致变化比较顺利,不会关机。
cat ${_prefix}/logs/nginx.pid 获取nginx的进程ID
然后在 /etc/crontab 中添加一行:
59 * * * * 根 /path/to/directory/rotatelog.sh
在每小时的第 59 分钟启动脚本以进行日志轮换。