爲何要進行網站流量數據統計分析?javascript
隨着大數據時代的到來,各行各業產生的數據呈爆發式增加,大數據的技術從以前的「虛無」變成可能,數據產生的各類潛在價值慢慢的被人們挖掘出來利用在各行各業上。好比網站流量數據統計分析,能夠幫助網站管理員、運營人員、推廣人員等實時獲取網站流量信息,並從流量來源、網站內容、網站訪客特性等多方面提供網站分析的數據依據。從而幫助提升網站流量,提高網站用戶體驗,讓訪客更多的沉澱下來變成會員或客戶,經過更少的投入獲取最大化的收入。前端
網站流量日誌數據採集原理分析java
首先,用戶的行爲會觸發瀏覽器對被統計頁面的一個 http 請求,好比打開某網頁。當網頁被打開,頁面中的埋點 javascript 代碼會被執行。nginx
埋點是指:在網頁中預先加入小段 javascript 代碼,這個代碼片斷通常會動態建立一個 script 標籤,並將 src 屬性指向一個單獨的 js 文件,此時這個單獨的 js 文件(圖中綠色節點)會被瀏覽器請求到並執行,這個 js 每每就是真正的數據收集腳本。web
數據收集完成後,js 會請求一個後端的數據收集腳本(圖中的 backend),這個腳本通常是一個假裝成圖片的動態腳本程序,js 會將收集到的數據經過http 參數的方式傳遞給後端腳本,後端腳本解析參數並按固定格式記錄到訪問日誌,同時可能會在 http 響應中給客戶端種植一些用於追蹤的 cookie。ajax
設計實現算法
根據原理分析並結合 Google Analytics,想搭建一個自定義日誌數據採集系統,要作如下幾件事:shell
肯定收集信息windows
名稱 | 途徑 | 備註 |
訪問時間 | web server | Nginx $msec |
IP | web server | Nginx $remote_addr |
域名 | javascript | document.domain |
URL | javascript | document.URL |
頁面標題 | javascript | document.title |
分辨率 | javascript | window.screen.height & width |
顏色深度 | javascript | window.screen.colorDepth |
Referrer | javascript | document.referrer |
瀏覽客戶端 | web server | Nginx $http_user_agent |
客戶端語言 | javascript | navigator.language |
訪客標識 | cookie | Nginx $http_cookie |
網站標識 | javascript | 自定義對象 |
狀態碼 | web server | Nginx $status |
發送內容量 | web server | Nginx $body_bytes_sent |
肯定埋點代碼後端
埋點,是網站分析的一種經常使用的數據採集方法。核心就是在須要進行數據採集的關鍵點植入統計代碼,進行數據的採集。好比以谷歌分析原型來講,須要在頁面中插入一段它提供的 javascript 片斷,這個片斷每每被稱爲埋點代碼。(以Google的埋點代碼爲例)
<script type="text/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); })(); </script>
其中_maq 是全局數組,用於放置各類配置,其中每一條配置的格式爲:
_maq.push(['Action', 'param1', 'param2', ...]);
_maq 的機制不是重點,重點是後面匿名函數的代碼,這段代碼的主要目的就是引入一個外部的 js 文件(ma.js),方式是經過 document.createElement 方法建立一個 script 並根據協議(http 或 https)將 src 指向對應的 ma.js,最後將這個元素插入頁面的 dom 樹上。
注意 ma.async = true 的意思是異步調用外部 js 文件,即不阻塞瀏覽器的解析,待外部 js 下載完成後異步執行。這個屬性是 HTML5 新引入的。
前端數據收集腳本
數據收集腳本(ma.js)被請求後會被執行,通常要作以下幾件事:
經過瀏覽器內置javascript對象收集信息,如頁面title (經過document.title)、referrer(上一跳 url,經過 document.referrer)、用戶顯示器分辨率(經過windows.screen)、cookie 信息(經過 document.cookie)等等一些信息。
這裏惟一的問題是步驟 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 圖片的腳本。後端腳本通常須要完成如下幾件事情:
之因此要設置 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:
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 的進程號
而後再/etc/crontab 里加入一行:
59 * * * * root /path/to/directory/rotatelog.sh
在每一個小時的 59 分啓動這個腳本進行日誌輪轉操做。