1. 程式人生 > >網站統計中的資料收集原理及實現_埋點統計

網站統計中的資料收集原理及實現_埋點統計

網站資料統計分析工具是網站站長和運營人員經常使用的一種工具,比較常用的有谷歌分析、百度統計和騰訊分析等等。所有這些統計分析工具的第一步都是網站訪問資料的收集。目前主流的資料收集方式基本都是基於javascript的。本文將簡要分析這種資料收集的原理,並一步一步實際搭建一個實際的資料收集系統。

資料收集原理分析

簡單來說,網站統計分析工具需要收集到使用者瀏覽目標網站的行為(如開啟某網頁、點選某按鈕、將商品加入購物車等)及行為附加資料(如某下單行為產生的訂單金額等)。早期的網站統計往往只收集一種使用者行為:頁面的開啟。而後使用者在頁面中的行為均無法收集。這種收集策略能滿足基本的流量分析、來源分析、內容分析及訪客屬性等常用分析視角,但是,隨著ajax技術的廣泛使用及電子商務網站對於電子商務目標的統計分析的需求越來越強烈,這種傳統的收集策略已經顯得力不能及。

後來,Google在其產品谷歌分析中創新性的引入了可定製的資料收集指令碼,使用者通過谷歌分析定義好的可擴充套件介面,只需編寫少量的javascript程式碼就可以實現自定義事件和自定義指標的跟蹤和分析。目前百度統計、搜狗分析等產品均照搬了谷歌分析的模式。

其實說起來兩種資料收集模式的基本原理和流程是一致的,只是後一種通過javascript收集到了更多的資訊。下面看一下現在各種網站統計工具的資料收集基本原理。

流程概覽

首先通過一幅圖總體看一下資料收集的基本流程。

1.png

圖1. 網站統計資料收集基本流程

首先,使用者的行為會觸發瀏覽器對被統計頁面的一個http請求,這裡姑且先認為行為就是開啟網頁。當網頁被開啟,頁面中的埋點javascript片段會被執行,用過相關工具的朋友應該知道,一般網站統計工具都會要求使用者在網頁中加入一小段javascript程式碼,這個程式碼片段一般會動態建立一個script標籤,並將src指向一個單獨的js檔案,此時這個單獨的js檔案(圖1中綠色節點)會被瀏覽器請求到並執行,這個js往往就是真正的資料收集指令碼。資料收集完成後,js會請求一個後端的資料收集指令碼(圖1中的backend),這個指令碼一般是一個偽裝成圖片的動態指令碼程式,可能由php、python或其它服務端語言編寫,js會將收集到的資料通過http引數的方式傳遞給後端指令碼,後端指令碼解析引數並按固定格式記錄到訪問日誌,同時可能會在http響應中給客戶端種植一些用於追蹤的cookie。

上面是一個數據收集的大概流程,下面以谷歌分析為例,對每一個階段進行一個相對詳細的分析。

埋點指令碼執行階段

若要使用谷歌分析(以下簡稱GA),需要在頁面中插入一段它提供的javascript片段,這個片段往往被稱為埋點程式碼。下面是我的部落格中所放置的谷歌分析埋點程式碼截圖:

\

圖2. 谷歌分析埋點程式碼

其中_gaq是GA的的全域性陣列,用於放置各種配置,其中每一條配置的格式為:

_gaq.push(['Action', 'param1', 'param2', ...]);

Action指定配置動作,後面是相關的引數列表。GA給的預設埋點程式碼會給出兩條預置配置,_setAccount用於設定網站標識ID,這個標識ID是在註冊GA時分配的。_trackPageview告訴GA跟蹤一次頁面訪問。更多配置請參考:https://developers.google.com/analytics/devguides/collection/gajs/。實際上,這個_gaq是被當做一個FIFO佇列來用的,配置程式碼不必出現在埋點程式碼之前,具體請參考上述連結的說明。

就本文來說,_gaq的機制不是重點,重點是後面匿名函式的程式碼,這才是埋點程式碼真正要做的。這段程式碼的主要目的就是引入一個外部的js檔案(ga.js),方式是通過document.createElement方法建立一個script並根據協議(http或https)將src指向對應的ga.js,最後將這個element插入頁面的dom樹上。

注意ga.async = true的意思是非同步呼叫外部js檔案,即不阻塞瀏覽器的解析,待外部js下載完成後非同步執行。這個屬性是HTML5新引入的。

資料收集指令碼執行階段

資料收集指令碼(ga.js)被請求後會被執行,這個指令碼一般要做如下幾件事:

1、通過瀏覽器內建javascript物件收集資訊,如頁面title(通過document.title)、referrer(上一跳url,通過document.referrer)、使用者顯示器解析度(通過windows.screen)、cookie資訊(通過document.cookie)等等一些資訊。

2、解析_gaq收集配置資訊。這裡面可能會包括使用者自定義的事件跟蹤、業務資料(如電子商務網站的商品編號等)等。

3、將上面兩步收集的資料按預定義格式解析並拼接。

4、請求一個後端指令碼,將資訊放在http request引數中攜帶給後端指令碼。

這裡唯一的問題是步驟4,javascript請求後端指令碼常用的方法是ajax,但是ajax是不能跨域請求的。這裡ga.js在被統計網站的域內執行,而後端指令碼在另外的域(GA的後端統計指令碼是http://www.google-analytics.com/__utm.gif),ajax行不通。一種通用的方法是js指令碼建立一個Image物件,將Image物件的src屬性指向後端指令碼並攜帶引數,此時即實現了跨域請求後端。這也是後端指令碼為什麼通常偽裝成gif檔案的原因。通過http抓包可以看到ga.js對__utm.gif的請求:

\

圖3. 後端指令碼請求的http包

可以看到ga.js在請求__utm.gif時帶了很多資訊,例如utmsr=1280×1024是螢幕解析度,utmac=UA-35712773-1是_gaq中解析出的我的GA標識ID等等。

值得注意的是,__utm.gif未必只會在埋點程式碼執行時被請求,如果用_trackEvent配置了事件跟蹤,則在事件發生時也會請求這個指令碼。

由於ga.js經過了壓縮和混淆,可讀性很差,我們就不分析了,具體後面實現階段我會實現一個功能類似的指令碼。

後端指令碼執行階段

GA的__utm.gif是一個偽裝成gif的指令碼。這種後端指令碼一般要完成以下幾件事情:

1、解析http請求引數的到資訊。

2、從伺服器(WebServer)中獲取一些客戶端無法獲取的資訊,如訪客ip等。

3、將資訊按格式寫入log。

5、生成一副1×1的空gif圖片作為響應內容並將響應頭的Content-type設為image/gif。

5、在響應頭中通過Set-cookie設定一些需要的cookie資訊。

之所以要設定cookie是因為如果要跟蹤唯一訪客,通常做法是如果在請求時發現客戶端沒有指定的跟蹤cookie,則根據規則生成一個全域性唯一的cookie並種植給使用者,否則Set-cookie中放置獲取到的跟蹤cookie以保持同一使用者cookie不變(見圖4)。

\

圖4. 通過cookie跟蹤唯一使用者的原理

這種做法雖然不是完美的(例如使用者清掉cookie或更換瀏覽器會被認為是兩個使用者),但是是目前被廣泛使用的手段。注意,如果沒有跨站跟蹤同一使用者的需求,可以通過js將cookie種植在被統計站點的域下(GA是這麼做的),如果要全網統一定位,則通過後端指令碼種植在服務端域下(我們待會的實現會這麼做)。

系統的設計實現

根據上述原理,我自己搭建了一個訪問日誌收集系統。總體來說,搭建這個系統要做如下的事:

\

圖5. 訪問資料收集系統工作分解

下面詳述每一步的實現。我將這個系統叫做MyAnalytics。

確定收集的資訊

為了簡單起見,我不打算實現GA的完整資料收集模型,而是收集以下資訊。

wKiom1RHIymRbHK9AAIeLUHe2eY502.jpg

埋點程式碼

埋點程式碼我將借鑑GA的模式,但是目前不會將配置物件作為一個FIFO佇列用。一個埋點程式碼的模板如下:

<script type="text/javascript"> var _maq = _maq || []; _maq.push(['_setAccount', '網站標識']);   (function() {     var ma = document.createElement('script'); ma.type = 'text/javascript'; ma.async = true;     ma.src = ('https:' == document.location.protocol ? 'https://analytics' : 'http://analytics') + '.codinglabs.org/ma.js';     var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ma, s); })(); </script>

這裡我啟用了二級域名analytics.codinglabs.org,統計指令碼的名稱為ma.js。當然這裡有一點小問題,因為我並沒有https的伺服器,所以如果一個https站點部署了程式碼會有問題,不過這裡我們先忽略吧。

前端統計指令碼

我寫了一個不是很完善但能完成基本工作的統計指令碼ma.js:

(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://analytics.codinglabs.org/1.gif?' + args; })();

整個指令碼放在匿名函式裡,確保不會汙染全域性環境。功能在原理一節已經說明,不再贅述。其中1.gif是後端指令碼。

日誌格式

日誌採用每行一條記錄的方式,採用不可見字元^A(ascii碼0x01,Linux下可通過ctrl + v ctrl + a輸入,下文均用“^A”表示不可見字元0x01),具體格式如下:

時間^AIP^A域名^AURL^A頁面標題^AReferrer^A解析度高^A解析度寬^A顏色深度^A語言^A客戶端資訊^A使用者標識^A網站標識

後端指令碼

為了簡單和效率考慮,我打算直接使用nginx的access_log做日誌收集,不過有個問題就是nginx配置本身的邏輯表達能力有限,所以我選用了OpenResty做這個事情。OpenResty是一個基於Nginx擴展出的高效能應用開發平臺,內部集成了諸多有用的模組,其中的核心是通過ngx_lua模組集成了Lua,從而在nginx配置檔案中可以通過Lua來表述業務。關於這個平臺我這裡不做過多介紹,感興趣的同學可以參考其官方網站http://openresty.org/,或者這裡有其作者章亦春(agentzh)做的一個非常有愛的介紹OpenResty的slide:http://agentzh.org/misc/slides/ngx-openresty-ecosystem/,關於ngx_lua可以參考:https://github.com/chaoslawful/lua-nginx-module。

首先,需要在nginx的配置檔案中定義日誌格式:

log_format tick "$msec^A$remote_addr^A$u_domain^A$u_url^A$u_title^A$u_referrer^A$u_sh^A$u_sw^A$u_cd^A$u_lang^A$http_user_agent^A$u_utrace^A$u_account";

注意這裡以u_開頭的是我們待會會自己定義的變數,其它的是nginx內建變數。

然後是核心的兩個location:

location /1.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     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_utrace $arg_utrace;     set_unescape_uri $u_account $arg_account;       #開啟日誌     log_subrequest on;     #記錄日誌到ma.log,實際應用中最好加buffer,格式為tick     access_log /path/to/logs/directory/ma.log tick;       #輸出空字串     echo ''; }

要完全解釋這段指令碼的每一個細節有點超出本文的範圍,而且用到了諸多第三方ngxin模組(全都包含在OpenResty中了),重點的地方我都用註釋標出來了,可以不用完全理解每一行的意義,只要大約知道這個配置完成了我們在原理一節提到的後端邏輯就可以了。

日誌輪轉

真正的日誌收集系統訪問日誌會非常多,時間一長檔案變得很大,而且日誌放在一個檔案不便於管理。所以通常要按時間段將日誌切分,例如每天或每小時切分一個日誌。我這裡為了效果明顯,每一小時切分一個日誌。我是通過crontab定時呼叫一個shell指令碼實現的,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訊號令其重新開啟日誌檔案。

然後再/etc/crontab里加入一行:

59  *  *  *  * root /path/to/directory/rotatelog.sh

在每個小時的59分啟動這個指令碼進行日誌輪轉操作。

測試

下面可以測試這個系統是否能正常運行了。我昨天就在我的部落格中埋了相關的點,通過http抓包可以看到ma.js和1.gif已經被正確請求:


\

圖6. http包分析ma.js和1.gif的請求

同時可以看一下1.gif的請求引數:

\

圖7. 1.gif的請求引數

相關資訊確實也放在了請求引數中。

然後我tail開啟日誌檔案,然後重新整理一下頁面,因為沒有設access log buffer, 我立即得到了一條新日誌:

1351060731.360^A0.0.0.0^Awww.codinglabs.org^Ahttp://www.codinglabs.org/^ACodingLabs^A^A1024^A1280^A24^Azh-CN^AMozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4^A4d612be64366768d32e623d594e82678^AU-1-1

注意實際上原日誌中的^A是不可見的,這裡我用可見的^A替換為方便閱讀,另外IP由於涉及隱私我替換為了0.0.0.0。

看一眼日誌輪轉目錄,由於我之前已經埋了點,所以已經生成了很多輪轉檔案:

\

圖8. 輪轉日誌

關於分析

通過上面的分析和開發可以大致理解一個網站統計的日誌收集系統是如何工作的。有了這些日誌,就可以進行後續的分析了。本文只注重日誌收集,所以不會寫太多關於分析的東西。

注意,原始日誌最好儘量多的保留資訊而不要做過多過濾和處理。例如上面的MyAnalytics保留了毫秒級時間戳而不是格式化後的時間,時間的格式化是後面的系統做的事而不是日誌收集系統的責任。後面的系統根據原始日誌可以分析出很多東西,例如通過IP庫可以定位訪問者的地域、user agent中可以得到訪問者的作業系統、瀏覽器等資訊,再結合複雜的分析模型,就可以做流量、來源、訪客、地域、路徑等分析了。當然,一般不會直接對原始日誌分析,而是會將其清洗格式化後轉存到其它地方,如MySQL或HBase中再做分析。

分析部分的工作有很多開源的基礎設施可以使用,例如實時分析可以使用Storm,而離線分析可以使用Hadoop。當然,在日誌比較小的情況下,也可以通過shell命令做一些簡單的分析,例如,下面三條命令可以分別得出我的部落格在今天上午8點到9點的訪問量(PV),訪客數(UV)和獨立IP數(IP):

awk -F^A '{print $1}' ma-2012102409.log | wc -lawk -F^A '{print $12}' ma-2012102409.log | uniq | wc -lawk -F^A '{print $2}' ma-2012102409.log | uniq | wc -l

其它好玩的東西朋友們可以慢慢挖掘。

原文出處:http://blog.codinglabs.org/articles/how-web-analytics-data-collection-system-work.html

相關推薦

網站統計資料收集原理實現_統計

網站資料統計分析工具是網站站長和運營人員經常使用的一種工具,比較常用的有谷歌分析、百度統計和騰訊分析等等。所有這些統計分析工具的第一步都是網站訪問資料的收集。目前主流的資料收集方式基本都是基於javascript的。本文將簡要分析這種資料收集的原理,並一步一步實際搭建一個

網站統計資料收集原理實現(js實現)

網站資料統計分析工具是網站站長和運營人員經常使用的一種工具,比較常用的有谷歌分析、百度統計和騰訊分析等等。所有這些統計分析工具的第一步都是網站訪問資料的收集。目前主流的資料收集方式基本都是基於javascript的。本文將簡要分析這種資料收集的原理,並一步一步實際搭建一個實際的資料收集系統。資料收集原理分析簡

網站統計資料收集原理實現(openResty篇)

引言: 網站資料統計分析工具是網站站長和運營人員經常使用的一種工具,比較常用的有谷歌分析、百度統計和騰訊分析等等。所有這些統計分析工具的第一步都是網站訪問資料的收集。目前主流的資料收集方式基本都是基於javascript的。本文將簡要分析這種資料收集的

網站統計的數據收集原理實現

fun 美的 置配 客戶 etc 分析 獲取 固定 open 網站統計中的數據收集原理及實現 網站數據統計分析工具是網站站長和運營人員經常使用的一種工具,比較常用的有谷歌分析、百度統計和騰訊分析等等。所有這些統計分析工具的第一步都是網站訪問數據的收集。目前主流的數據收

深入理解Java的底層阻塞原理實現

更多 安全 posix pla static events time() 方便 原理 談到阻塞,相信大家都不會陌生了。阻塞的應用場景真的多得不要不要的,比如 生產-消費模式,限流統計等等。什麽 ArrayBlockingQueue、 LinkedBlockingQueue、

轉 vue實現雙向資料繫結之原理實現篇 vue的雙向繫結原理實現

轉自:canfoo#! vue的雙向繫結原理及實現 前言 先上個成果圖來吸引各位: 程式碼:                          &nb

資料正規化 (data normalization) 的原理實現 (Python sklearn)

原理 資料正規化(data normalization)是將資料的每個樣本(向量)變換為單位範數的向量,各樣本之間是相互獨立的.其實際上,是對向量中的每個分量值除以正規化因子.常用的正規化因子有 L1, L2 和 Max.假設,對長度為 n 的向量,其正規化因子 z 的計算公式,如下所示:

資料結構和演算法 | 氣泡排序演算法原理實現和優化

氣泡排序(Bubble Sort)是排序演算法裡面比較簡單的一個排序。它重複地走訪要排序的數列,一次比較兩個資料元素,如果順序不對則進行交換,並一直重複這樣的走訪操作,直到沒有要交換的資料元素為止。 氣泡排序的原理 為了更深入地理解氣泡排序的操作步驟,我們現在

資料結構和演算法 | 簡單選擇排序演算法原理實現

選擇排序是一種非常簡單的排序演算法,就是在序列中依次選擇最大(或者最小)的數,並將其放到待排序的數列的起始位置。 簡單選擇排序的原理 簡單選擇排序的原理非常簡單,即在待排序的數列中尋找最大(或者最小)的一個數,與第 1 個元素進行交換,接著在剩餘的待排序的數列

資料結構和演算法 | 插入排序演算法原理實現和優化

插入排序演算法是所有排序方法中最簡單的一種演算法,其主要的實現思想是將資料按照一定的順序一個一個的插入到有序的表中,最終得到的序列就是已經排序好的資料。 直接插入排序是插入排序演算法中的一種,採用的方法是:在新增新的記錄時,使用順序查詢的方式找到其要插入的位置,

資料結構和演算法 | 歸併排序演算法原理實現和優化

歸併排序(MERGE-SORT)是建立在歸併操作上的一種有效的排序演算法,該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。 歸併排序的原理 歸

資料結構】HashTable原理實現學習總結

有兩個類都提供了一個多種用途的hashTable機制,他們都可以將可以key和value結合起來構成鍵值對通過put(key,value)方法儲存起來,然後通過get(key)方法獲取相對應的value值。一個是前面提到的HashMap,還有一個就是馬上要講解的HashTa

資料結構】LinkedList原理實現學習總結

一、LinkedList實現原理概述 LinkedList 和 ArrayList 一樣,都實現了 List 介面,但其內部的資料結構有本質的不同。LinkedList 是基於連結串列實現的(通過名字也能區分開來),所以它的插入和刪除操作比 ArrayList

閱讀理解任務的Attention-over-Attention神經網路模型原理實現

本文是“Attention-over-Attention Neural Networks for Reading Comprehension”的閱讀筆記。這篇論文所處理的任務是閱讀理解裡面的完形填空問題。其模型架構是建立在“Text Understanding

資料結構--並查集的原理實現

一,並查集的介紹  並查集(Union/Find)從名字可以看出,主要涉及兩種基本操作:合併和查詢。這說明,初始時並查集中的元素是不相交的,經過一系列的基本操作(Union),最終合併成一個大的集合。 而在某次合併之後,有一種合理的需求:某兩個元素是否已經處在同一個集合中了?因此就需要Find操作。 並

資料壓縮】LZ77演算法原理實現

1. 引言 LZ77演算法是採用字典做資料壓縮的演算法,由以色列的兩位大神Jacob Ziv與Abraham Lempel在1977年發表的論文《A Universal Algorithm for Sequential Data Compression》中提出。 基於統計的資料壓縮編碼,比如Huffman編

資料壓縮】LZ78演算法原理實現

在提出基於滑動視窗的LZ77演算法後,兩位大神Jacob Ziv與Abraham Lempel於1978年在發表的論文 [1]中提出了LZ78演算法;與LZ77演算法不同的是LZ78演算法使用動態樹狀詞典維護歷史字串。 1. 原理 壓縮 LZ78演算法的壓縮過程非常簡單。在壓縮時維護一個動態詞典Dictio

值濾波原理MATLAB演算法實現

中值濾波是一種非線性濾波方式,它依靠模板來實現。 對於一維中值濾波,設模板的尺寸為 M ,M=2*r+1,r為模板半徑,給定一維訊號f(i),i = 1,2,3……N,則中值濾波輸出為: g(i) = median[ f(j-r),f(j-r+1),…………,f(j),f(

[從今天開始修煉資料結構]佇列、迴圈佇列、PriorityQueue的原理實現

[從今天開始修煉資料結構]基本概念 [從今天開始修煉資料結構]線性表及其實現以及實現有Itertor的ArrayList和LinkedList [從今天開始修煉資料結構]棧、斐波那契數列、逆波蘭四則運算的實現 [從今天開始修煉資料結構]佇列、迴圈佇列、PriorityQueue的原理及實現 一、什麼是佇列  

關於base64編碼的原理實現

一個 replace 編碼範圍 func nco 都是 style bit 如果 我們的圖片大部分都是可以轉換成base64編碼的data:image。 這個在將canvas保存為img的時候尤其有用。雖然除ie外,大部分現代瀏覽器都已經支持原生的基於base64的enco