为什么要进行网站流量数据统计分析?(一)?
优采云 发布时间: 2021-08-20 01:04为什么要进行网站流量数据统计分析?(一)?
为什么需要进行网站traffic数据统计分析?
随着大数据时代的到来,各行各业产生的数据呈爆炸式增长。大数据技术从以前的“虚无”变成了可能,数据产生的各种潜在价值正在被人们慢慢发掘。在各行各业中使用它。比如网站流量数据统计分析,可以帮助网站管理员、运营商、推广者等实时获取网站流量信息,从流量来源、网站内容、 网站visitor 特征等 网站Analyzed 数据基础。这将有助于增加网站 的流量,改善网站 的用户体验,让更多的访问者成为会员或客户,并以更少的投资获得最大的收益。
网站交通日志数据采集原理分析
首先,用户的行为会触发浏览器向被统计的页面发出http请求,比如打开某个网页。当网页打开时,页面中嵌入的javascript代码将被执行。
埋点是指:在网页中预先添加一小段javascript代码。这段代码片段通常会动态创建一个脚本标签,并将 src 属性指向一个单独的 js 文件。这时候浏览器就会请求并执行这个单独的js文件(图中绿色的Node),这个js往往就是真正的数据采集脚本。
数据采集完成后,js会请求一个后端数据采集脚本(图中的后端)。这个脚本一般是伪装成图片的动态脚本程序。 Node.js 将通过 http 参数传递采集到的数据。对于后端脚本,后端脚本解析参数并以固定格式记录访问日志。同时,它可能会在 http 响应中为客户端植入一些跟踪 cookie。
设计与实现
基于原理分析,结合Google Analytics,如果要搭建自定义日志数据采集系统,需要做以下几件事:
确认采集信息
确定埋点代码
买点是网站分析常用的数据采集方法。核心是在需要数据采集的关键点植入统计代码,执行数据采集。例如,在 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_agent||$u_account";
注意这里以u_开头的变量是我们后面自己定义的变量,其他的是nginx的内置变量。然后是两个核心位置:
location / log.gif {#伪装成 gif 文件default_type image/gif;#本身关闭 access_log,通过 subrequest 记录 logaccess_log off;access_by_lua "-- 用户跟踪 cookie 名为__utracelocal uid = ngx.var.cookie___utraceif 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 格式为 tickaccess_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}.logkill -USR1 `cat ${_prefix}/logs/nginx.pid `
该脚本将ma.log移动到指定文件夹并重命名为ma-{yyyymmddhh}.log,然后向nginx发送USR1信号重新打开日志文件。
USR1 通常用于告诉应用程序重新加载配置文件。向服务器发送USR1信号会导致以下步骤发生:停止接受新连接,等待当前连接停止,重新加载配置文件,重新打开日志文件,重启服务器,从而实现比较平滑的变化无需关机。
cat ${_prefix}/logs/nginx.pid 取nginx的进程号
然后在/etc/crontab中添加一行:
59 * * * * root /path/to/directory/rotatelog.sh
每小时 59 分钟启动此脚本以执行日志轮换操作。