1. 程式人生 > >網站流量日誌數據自定義采集實現

網站流量日誌數據自定義采集實現

之前 數據 拼接 win 其它 .com rac 特性 網頁

為什麽要進行網站流量數據統計分析?

隨著大數據時代的到來,各行各業產生的數據呈爆發式增長,大數據的技術從之前的“虛無”變成可能,數據產生的各種潛在價值慢慢的被人們挖掘出來利用在各行各業上。比如網站流量數據統計分析,可以幫助網站管理員、運營人員、推廣人員等實時獲取網站流量信息,並從流量來源、網站內容、網站訪客特性等多方面提供網站分析的數據依據。從而幫助提高網站流量,提升網站用戶體驗,讓訪客更多的沈澱下來變成會員或客戶,通過更少的投入獲取最大化的收入。

網站流量日誌數據采集原理分析

  首先,用戶的行為會觸發瀏覽器對被統計頁面的一個 http 請求,比如打開某網頁。當網頁被打開,頁面中的埋點 javascript 代碼會被執行。

技術分享圖片

  埋點是指:在網頁中預先加入小段 javascript 代碼,這個代碼片段一般會動態創建一個 script 標簽,並將 src 屬性指向一個單獨的 js 文件,此時這個單獨的 js 文件(圖中綠色節點)會被瀏覽器請求到並執行,這個 js 往往就是真正的數據收集腳本。

  數據收集完成後,js 會請求一個後端的數據收集腳本(圖中的 backend),這個腳本一般是一個偽裝成圖片的動態腳本程序,js 會將收集到的數據通過http 參數的方式傳遞給後端腳本,後端腳本解析參數並按固定格式記錄到訪問日誌,同時可能會在 http 響應中給客戶端種植一些用於追蹤的 cookie。

設計實現

  根據原理分析並結合 Google Analytics,想搭建一個自定義日誌數據采集系統,要做以下幾件事:

技術分享圖片

確定收集信息

名稱 途徑 備註
訪問時間 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)被請求後會被執行,一般要做如下幾件事:

  1. 通過瀏覽器內置javascript對象收集信息,如頁面title (通過document.title)、referrer(上一跳 url,通過 document.referrer)、用戶顯示器分辨率(通過windows.screen)、cookie 信息(通過 document.cookie)等等一些信息。

  2. 解析_maq 數組,收集配置信息。這裏面可能會包括用戶自定義的事件跟蹤、業務數據(如電子商務網站的商品編號等)等。
  3. 將上面兩步收集的數據按預定義格式解析並拼接(get 請求參數)。
  4. 請求一個後端腳本,將信息放在 http request 參數中攜帶給後端腳本。

這裏唯一的問題是步驟 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 圖片的腳本。後端腳本一般需要完成以下幾件事情:

  1. 解析 http 請求參數得到信息。
  2. 從 Web 服務器中獲取一些客戶端無法獲取的信息,如訪客 ip 等。
  3. 將信息按格式寫入 log。
  4. 生成一副 1×1 的空 gif 圖片作為響應內容並將響應頭的 Content-type設為 image/gif。
  5. 在響應頭中通過 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:

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 分啟動這個腳本進行日誌輪轉操作。

網站流量日誌數據自定義采集實現