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(); });
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