WebWalker

阅读-思考-实践-总结

Web用户行为跟踪收集

      收集用户隐私的行径眼下已不再是什么新鲜的话题(与其说是收集,不如说是偷窥),就连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:

    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




阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/webwalker/article/details/2587172
个人分类: Web
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭