1. 程式人生 > >JavaScript實現使用者行為跟蹤收集

JavaScript實現使用者行為跟蹤收集

收集使用者隱私的行徑眼下已不再是什麼新鮮的話題(與其說是收集,不如說是偷窺),就連G、MS也屢出風頭,當然事出有因,企業通過無法八門的各種手段瞭解使用者的行為,進而作為決策支援的依據;通常表現為跨領域的收集(瀏覽器、智慧系統、OS etc.)、業務、產品資料的收集,當然收集的方式也不一而論。以下展示通過客戶端指令碼方式收集Web站點使用者行為資料的實現方式,以此來判斷頁面載入過程、執行時間、使用者停留時間、頁面提交率、成功率、頁面錯誤等(本示例實現較為簡單,在特定複雜的業務資料收集時,可適當重構與改造)。

1、收集基礎資料

基礎資料涵蓋

(1)  業務類:業務流頁面地址、使用者停留時間、開啟次數、

會話ID、客戶端IP、業務流步驟等

(2)  輔助類:瀏覽器、OS、COOKIE等

(3)  示例 

/* 
    記錄構造基礎資料 
*/  
var Logger = {  
    Debug: false,  
    UserControl: 'inputUser',  
    HistoryControl: 'inputHistory',  
    TimerControl: 'inputTimer',  
    GetUser: function() {  
        if (!$f(this.UserControl)) return ",,";  
        return $f(this.UserControl).value;  
    },  
    /*-- attribute --*/  
    Guid: function() {  
        return this.GetUser().split(',')[0];  
    },  
    SessionID: function() {  
        return this.GetUser().split(',')[1];  
    },  
    GetStep: function() {  
        return this.GetUser().split(',')[2];  
    },  
    ProcessTime: function() {  
        if (!$f(this.TimerControl)) return "0";  
        return $f(this.TimerControl).value;  
    },  
    AppSys: 1, //不同系統編號  
    Environment: '', //Environment.Dev:開發Dev、測試Test、正式Official  
    IsNewSite: 1,  
    //是否歷史返回  
    IsHistory: function() {  
        if (!$f(this.HistoryControl)) return false;  
        if ($f(this.HistoryControl).value.length > 0)  
            return true;  
        //if (this.IsSuccReturn()) return true; //成功返回 非歷史  
        return false;  
    },  
    IsStep2History: function() { //是否為第2步返回歷史  
        if (!$f(this.HistoryControl)) return false;  
        var history = $f(this.HistoryControl).value;  
        if (history.length == 0)  
            return false;  
        if (history.split(',').length > 1)  
            return true;  
        return false;  
    },  
    //是否為頁面reload返回  
    IsReturn: function() {  
        if (typeof getUrlParam != "function") return false;  
        var para = getUrlParam("return");  
        if (para == "1") return true;  
        return false;  
    },  
    //是否成功返回  
    IsSuccReturn: function() {  
        var para = getUrlParam("succret");  
        if (para == "1") return true;  
        return false;  
    },  
    //tracetype,guid,sessionid,processtime,description  
    WriteStepLog: function() {  
        var argc = arguments.length;  
        var traceType = (argc > 0) ? arguments[0] : "";  
        var guid = (argc > 1) ? arguments[1] : "";  
        var sessionID = (argc > 2) ? arguments[2] : "";  
        var processTime = (argc > 3) ? (arguments[3] == "" ? "0" : arguments[3]) : "0";  
        var description = (argc > 4) ? arguments[4] : "";  
        var url = (argc > 5) ? arguments[5] : "";  
        /*with (Trace.Parameter) 
        { 
        TraceType = traceType; 
        Guid = guid; 
        SessionID = sessionID; 
        PageUrl = window.location.href; 
        ProcessTime = processTime; 
        //set const value 
        AppSys = 1; 
        Environment = Environment.Dev; //Offical 
        IsNewSite = 1; 
        Description = description; 
        }*/  
        Trace.Parameter.TraceType = traceType;  
        Trace.Parameter.Guid = guid;  
        Trace.Parameter.SessionID = sessionID;  
        if (url.length == 0) {  
            url = window.location.href;  
            //alert("self:" + window.location.href + ",refer:" + self.document.referrer);  
            if (url.toLowerCase().indexOf('errorpage.aspx') > -1 && traceType.indexOf('ret') == -1) {  
                if (document.referrer != null && document.referrer != "") url = document.referrer;  
            }  
        }  
        Trace.Parameter.PageUrl = url;  
        Trace.Parameter.ProcessTime = processTime;  
        Trace.Parameter.AppSys = this.AppSys;  
        if (this.Environment.length == 0) this.Environment = Environment.Official;  
        var curUrl = window.location.href.toLowerCase();  
        if (curUrl.indexOf('https://') > -1) {  
            this.Environment = this.Environment.replace('http://', 'https://');  
        }  
        Trace.Parameter.Environment = this.Environment;  
        Trace.Parameter.IsNewSite = this.IsNewSite;  
        Trace.Parameter.Description = escape(description);  
        if (this.Debug) {  
            alert(Trace.Parameter.TraceType + "," + Trace.Parameter.Guid + "," + Trace.Parameter.SessionID + ","  
            + Trace.Parameter.ProcessTime + "," + Trace.Parameter.Description);  
        }  
        Trace.Submit(Trace.Parameter, null, 'img');  
    },  
    WriteOpenLog: function() {  
        try {  
            var argc = arguments.length;  
            var step = (argc > 0) ? arguments[0] : "";  
            var desc = (argc > 1) ? arguments[1] : "";  
            if (typeof PTID != "undefined" && PTID.length > 0) {  
                desc += ",PTID:" + PTID;  
            }  
            var loginstep = this.GetStep();  
            /*if (this.IsSuccReturn()) { //成功返回 
            Logger.WriteStepLog(Step.succret, this.Guid(), this.SessionID(), this.ProcessTime(), desc); 
            this.SetTimer(); 
            }*/  
            if (step == "step1" && !this.IsHistory() && typeof loginstep != "undefined" && loginstep.length > 0) { //登入返回(第一步發生)  
                Logger.WriteStepLog(loginstep, this.Guid(), this.SessionID(), this.ProcessTime(), desc);  
            }  
            else if (step == "step1" && !this.IsHistory() && !this.IsReturn()) //not history back,not page reload  
            {  
                Logger.WriteStepLog(step, this.Guid(), this.SessionID(), this.ProcessTime(), desc);  
            }  
            else if ((step == "step2" && !this.IsStep2History()) || step == "step3") { //第2步、第3步  
                Logger.WriteStepLog(step, this.Guid(), this.SessionID(), this.ProcessTime(), desc);  
                this.SetTimer();  
            }  
            else if (step == "password" || step == "mobile" || step == "cancelbind") { //一點充沒有歷史返回等屬性  
                Logger.WriteStepLog(step, this.Guid(), this.SessionID(), this.ProcessTime(), desc);  
                this.SetTimer();  
            }  
            else if (this.IsHistory() || this.IsStep2History() || this.IsReturn()) { //歷史返回  
                Logger.WriteStepLog(step + "ret", this.Guid(), this.SessionID(), "0", desc);  
                this.SetTimer();  
            }  
            else {  
                Logger.WriteStepLog(step, this.Guid(), this.SessionID(), "0", desc);  
            }  
        }  
        catch (e) {  
  
        }  
    },  
    WriteSubmitLog: function() {  
        try {  
            var argc = arguments.length;  
            var step = (argc > 0) ? arguments[0] : "";  
            var desc = (argc > 1) ? arguments[1] : "";  
            var url = (argc > 2) ? arguments[2] : "";  
            if (typeof PTID != "undefined" && PTID.length > 0) {  
                desc += ",PTID:" + PTID;  
            }  
            Logger.WriteStepLog(step, this.Guid(), this.SessionID(), this.ProcessTime(), desc, url);  
            $f(this.HistoryControl).value = "1";  
            //set step2  
            if (step == "step2submit") {  
                $f(this.HistoryControl).value = "1,1";  
            }  
            this.SetTimer();  
        }  
        catch (e) {  
        }  
    },  
    SetTimer: function() { //reset timer  
        if (Timer && typeof Timer != "undefined") {  
            Timer.Reset();  
        }  
    },  
    DirectOpenLog: function() {  
        try {  
            var argc = arguments.length;  
            var step = (argc > 0) ? arguments[0] : "";  
            var desc = (argc > 1) ? arguments[1] : "";  
            if (typeof PTID != "undefined" && PTID.length > 0) {  
                desc += ",PTID:" + PTID;  
            }  
            this.AppSys = 2;  
            Logger.WriteStepLog(step, this.Guid(), this.SessionID(), this.ProcessTime(), desc);  
            if (step != Step.step1) {  
                this.SetTimer();  
            }  
        }  
        catch (e) {  
        }  
    }  
};  
var $f = function(name) {  
    return document.getElementById(name);  
}  
//記錄客戶端指令碼錯誤  
window.onerror = function GetErrors(error) {  
    try {  
        var msg;  
        for (var i = 0; i < arguments.length; i++) {  
            if (i == 0 || i == 2) {  
                msg += " | " + arguments[i];  
            }  
        }  
        if (msg.length > 0 && typeof Logger != 'undefined') {  
            Logger.WriteStepLog('syserror', '-', '-', '', msg);  
        }  
        window.onerror = null;  
        return true;  
    } catch (e) { };  
} 

2、時間統計

通過在頁面放置計時器,計算當前檢視下使用者的停留時間。

示例:

/*--------Timer Script------------- 
*  
* 頁面計時器控制元件 
* 1、Timer.BindControl = 'inputTimer'; 
* 2、<input id="inputTimer" type="hidden" class="timer" /> 
* 不寫Cookie、不顯示定時器時,採用(EndTime - StratTime)即可 
*/  
var up, down;  
var cmin1, csec1, clock;  
var Timer = {  
    Debug: false,  
    BindControl: 'inputTimer',  
    StartTime: '',  
    EndTime: '',  
    StartTimer: function() {  
        if (!$f(this.BindControl)) return;  
        if ($f(this.BindControl).value != "") return;  
        //$("#" + this.BindControl).val("");  
        cmin1 = 0;  
        csec1 = 0;  
        //每個頁面單獨記錄,不需要採用Cookie,遮蔽  
        //        var cookie = GetCookie("Timer");  
        //        if (cookie) {  
        //            cmin1 = parseInt(this.Minutes(cookie));  
        //            csec1 = parseInt(this.Seconds(cookie));  
        //            DeleteCookie("Timer");  
        //        }  
        //        else {  
        //            cmin1 = csec1 = 0;  
        //        }  
        this.Repeat();  
    },  
    SetValue: function() {  
        var html = $f(this.BindControl).value;  
        if (html != null && html.length > 0) SetCookie("Timer", html);  
    },  
    Minutes: function(data) {  
        for (var i = 0; i < data.length; i++) if (data.substring(i, i + 1) == ":") break;  
        return (data.substring(0, i));  
    },  
    Seconds: function(data) {  
        for (var i = 0; i < data.length; i++) if (data.substring(i, i + 1) == ":") break;  
        return (data.substring(i + 1, data.length));  
    },  
    Display: function(min, sec) {  
        var disp = "";  
        if (min <= 9) disp += "0" + min + ":";  
        else disp += min + ":";  
        if (sec <= 9) disp += "0" + sec;  
        else disp += sec;  
        return (disp);  
    },  
    Repeat: function() {  
        csec1++;  
        if (csec1 == 60) { csec1 = 0; cmin1++; }  
        $f(this.BindControl).value = this.Display(cmin1, csec1);  
        if (this.Debug) $f("inputDebug").value = this.Display(cmin1, csec1);  
        clock = window.setTimeout(function() { Timer.Repeat() }, 1000);  
    },  
    //重新開始計時  
    Reset: function() {  
        $f(this.BindControl).value = "";  
        window.clearTimeout(clock);  
        Timer.StartTimer();  
    },  
    AddTrigger: function() {  
        var list = document.getElementsByTagName("INPUT");  
        for (var i = 0; i < list.length; i++) {  
            if (list[i].type.toUpperCase() == 'TEXT') {  
                if (list[i].addEventListener) {  
                    list[i].addEventListener("keyup", function() { Timer.StartTimer(); }, false);  
                }  
                else {  
                    list[i].attachEvent("onkeyup", function() { Timer.StartTimer(); });  
                }  
            }  
        }  
    }  
};  
if (document.all) {  
    window.attachEvent("onload", function() { Timer.AddTrigger() });  
}  
else {  
    window.addEventListener("load", function() { Timer.AddTrigger() }, false);  
}  
if (Timer.Debug) {  
    if (!document.getElementById("inputDebug")) {  
        document.write("<input type='text' id='inputDebug' />");  
    }  
}  
/* 相容兩種模式設定Cookie */  
//$(window).unload(function() { Timer.SetValue(); });  
//$("form").submit(function() { Timer.SetValue(); });  
  1.  

3、非同步記錄

將收集到的資料通過GET、POST非同步的方式傳送到目標伺服器

示例: 

/*--------Trace Script-------------*/  
var Step =  
{  
    /* step */  
    step1: "step1", //第一步開啟,不含歷史返回、成功返回  
    step2: "step2",  
    step3: "step3",  
    /* post */  
    step1submit: "step1submit", //第一步提交  
    step2submit: "step2submit",  
    step3submit: "step3submit",  
    step3resubmit: "step3resubmit", //第三步重新提交  
    /* success */  
    success:"success", //操作成功  
    succret: "succret", //成功返回  
    step1ret: "step1ret", //返回第一步  
    step2ret: "step2ret",  
    step3ret: "step3ret",  
    /* error */  
    error:"error", //操作失敗  
    errstep1: "errstep1", //第一步出錯 錯誤頁面記錄  
    errstep2: "errstep2",  
    errstep3: "errstep3",  
    errstep1ret: "errstep1ret", //出錯頁返回到第一步  
    errstep2ret: "errstep2ret",  
    errstep3ret: "errstep3ret",  
    /* login */  
    loginb1: "loginb1", //銀行卡登入返回  
    loginc1: "loginc1", //實物卡登入返回  
    loginbind: "loginbind", //一點充登入返回  
    step1login: "step1login", //第一步登入介面  
    /* other */  
    bind: "bind", //使用者繫結一點充  
    mobile: "mobile", //修改手機號  
    password: "password", //修改密碼  
    cancelbind: "cancelbind", //取消服務  
    Login: "Login", //使用者登入日誌  
    querydeposit: "querydeposit", //充值記錄  
    querycardbalance: "querycardbalance", //實物卡餘額  
    clickkf: "clickkf", //點選線上客服  
    closekf: "closekf", //關閉線上客服  
    clickaccount1: "clickaccount1", //點選新增常用賬號  
    clickaccount2: "clickaccount2" //點選新增確定按鈕  
};  
var Environment = { Dev: "http://dev.xxx.com", Test: "http://test.xxx.com", Official: "http://www.xxx.com" }  
var Trace = {  
    AutoSubmit: false, //是否在提交表單時自動處理  
    Parameter: {  
        TraceType: '', //TraceType.open  
        Guid: '0',  
        SessionID: '',  
        PageUrl: '',  
        Description: '',  
        ProcessTime: '',  
        IsNewSite: false,  
        AppSys: 1,  
        ClientIP: '',  
        Environment: Environment.Official,  
        Extend: {}  
    },  
    MyAjax: function() {  
        this.xml = false;  
        this.GetXmlHttp = function() {  
            if (!this.xml && typeof XMLHttpRequest != 'undefined') {  
                this.xml = new XMLHttpRequest();  
            }  
            else {  
                var MSXML = ['MSXML2.XMLHTTP.5.0', 'MSXML2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP'];  
                for (var i = 0; i < MSXML.length; i++) {  
                    try {  
                        this.xml = new ActiveXObject(MSXML[i]);  
                        break;  
                    }  
                    catch (e) {//alert(e.message);  
                    }  
                }  
            }  
        }  
        this.GetXmlHttp();  
        var xmlHttp = this.xml;  
        var ajax = this;  
        var callBack = null;  
        this.updatePage = function() {  
            if (xmlHttp.readyState == 4) {  
                var response = xmlHttp.responseText;  
                if (callBack != null && typeof callBack == "function") {  
                    callBack(response);  
                }  
            }  
        }  
        this.toQueryString = function(json) {  
            var query = "";  
            if (json != null) {  
                for (var param in json) {  
                    query += param + "=" + escape(json[param]) + "&"  
                }  
            }  
            return query;  
        }  
        //提交引數,回撥函式, post、get方法  
        this.invoke = function(params, pageCallBack, method) {  
            if (xmlHttp) {  
                var query = "";  
                query += this.toQueryString(params);  
                query = query.substring(0, query.length - 1);  
                //var thisReg = new RegExp(/'|"/gi);  
                //query = query.replace(thisReg, "");  
                callBack = pageCallBack;  
                if (method != null && method.toUpperCase() == "GET") {  
                    var url = params.Environment + "/Trace.aspx?" + query;  
                    xmlHttp.onreadystatechange = ajax.updatePage;  
                    xmlHttp.open("GET", url, true);  
                    xmlHttp.setRequestHeader("TraceAjax-Ver", "ver1.0");  
                    xmlHttp.send(null);  
                }  
                else if (method != null && method.toUpperCase() == "POST") {  
                    var url = params.Environment + "/Trace.aspx";  
                    //  xmlHttp.setRequestHeader("Content-Length",query);   
                    xmlHttp.onreadystatechange = ajax.updatePage; //new CallClient(this);  
                    xmlHttp.open("POST", url, true);  
                    xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");  
                    xmlHttp.setRequestHeader("TraceAjax-Ver", "ver1.0");  
                    xmlHttp.send(query);  
                }  
                else { //跨域  
                    Trace.CreateTraceImg();  
                    document.getElementById("traceImg").src = params.Environment + "/Trace.aspx?a=" + Math.random(1000000000) + "&" + query;  
                }  
            }  
        }  
    },  
    CreateTraceImg: function() {  
        if (!document.getElementById("traceImg")) {  
            document.write("<img id=\"traceImg\" style='display:none' />");  
            //            var imgNode = document.createElement("img")  
            //            imgNode.setAttribute("id", "traceImg")  
            //            imgNode.style.display = "none";  
            //            document.body.appendChild(imgNode);  
        }  
    },  
    Submit: function(params, pageCallBack, method) {  
        try {  
            var ajax = new Trace.MyAjax();  
            //校驗step是否正確  
            //        for (var i = 0; i < Step.length; i++) {  
            //            if (params.TraceType == Step[i]) {  
            //                alert(Step[i]);  
            //            }  
            //        }  
            ajax.invoke(params, pageCallBack, method);  
        }  
        catch (e) {  
        }  
    }  
};  
Trace.CreateTraceImg();

4、後端資料分析

結合前臺收集的使用者資料,後臺初步可以完成,每一個頁面的開啟次數、佔用時長,提交率、以及後臺業務流的成功率等,通過報表形式展示資料分析介面,這樣就可以監控使用者行為異常、監控系統波動以及影響區域。

5、擴充套件

通過記錄來源地址,可以更詳盡的分析使用者行為的流程。

通過追加頁面元素的點選情況,從而計算點選率。

 

雖然伺服器端也可以做到這些過程,但需求在不同的過程中插入眾多的日誌記錄,進而POST到伺服器端,過於繁冗!

另外跟蹤伺服器可對來源的資料請求驗證其合法性,以及是否授權。

*注意:在Tracer的過程中,如遇採用系統方法如:繫結事件window.onload,需要注意不用覆蓋了方法,避免與應用的頁面衝突,可以通過來新增事件監聽,eg:

 

[javascript] view plaincopy

AddTrigger: function() {  
    var list = document.getElementsByTagName("INPUT");  
    for (var i = 0; i < list.length; i++) {  
        if (list[i].type.toUpperCase() == 'TEXT') {  
            if (list[i].addEventListener) {  
                list[i].addEventListener("keyup", function() { Timer.StartTimer(); }, false);  
            }  
            else {  
                list[i].attachEvent("onkeyup", function() { Timer.StartTimer(); });  
            }  
        }  
    }  
}  

6、關於指令碼壓縮

推薦採用JSA壓縮工具,原因:穩定、可靠、壓縮質量有保證

不過需要大家安裝一下Java RUNTIME