2019独角兽企业重金招聘Python工程师标准>>>
由于工作需要,需要采集用户的操作轨迹,弹出框日志,界面加载时间等信息,经过网上资料查阅后,自己写了个js来采集用户行为,大家可以看下我的实现方式,有问题的可以给我留言,一起探讨和改正。
Web项目分析用户的操作行为主要有两种方式,一种较为宏观,主要是通过分析后台日志,分析用户调用后台的那些服务,各个页面之间的跳转关系,
还有一种较为微观,主要是记录用户对界面的操作行为,鼠标点击,键盘操作等分析用户。 通过在页面上的 JavaScript来记录,一线互联网的的界面操作轨迹就是通过js来实现 。
用户在网页上的行为是复杂的,但是大多数情况下,能直接取到的最有价值的信息是用户鼠标的行为。鼠标行为中,处理起来最为简单的是鼠标的点击。用户在界面上操作,点击并不是随机的,除了部分无意的点击外,大部分点击都是有意义的,表示用户对当前的区域有兴趣(哪怕是误解) 所以记录下用户的点击行为并进行分析是有意义的,通过分析用户的点击记录,可以找出页面上吸引用户的区域,这些区域不妨称之为热区。
实现方式,使用 JavaScript,监听鼠标的点击事件,触发时获取当前点击的鼠标位置,对应的html标签 等相关信息后 ,存储该信息于对应界面的js对象中数组中,待该界面关闭时把存储数据的数据通过“打点的形式”发送到指定的服务器,从而获取相关用户行为信息的过程。
1 用户界面操作轨迹获取,存储与界面的初始化js对象数组中
2 界面请求系统服务器访问其他界面
3 界面发生跳转
4 当界面跳转前 调用window.onbeforeunload方法
5 onbeforeunload 中 通过打点形式把数据发送到打点服务器
注: 这里为什么不用ajax来实现数据发送,而是用打点的形式,原因如下:
打点:把要发送的信息加到 URL 参数中,请求打点服务器上的一个非常小的空图片,这样,信息就将记录在打点服务器的,之后再用专门的程序从日志中读取并分析相关信息。而且,对于打点服务器而言,由于只需要提供一个非常小的静态图片的访问,所以一台普通的服务器经过适当配置后也可以应对很高的访问量。
Ajax方式的话,界面需要等待消息的返回,这样会造成比较差的用户体验,通过打点不需要等待对方数据的返回
1.1 实现
实现以js引入的形式,不需要改变业务系统所有的前端代码都在po.js中
1.1.1 取得用户在页面上的点击事件的位置坐标。
用jquery获取鼠标点击事件采集数据
$(function(){
/*鼠标下压采集用户行为*/
$(document).mousedown(function(e){
pv.mouseEvent(e);
});
});
1.1.2 打点方式
js生成img对象,调用对应的 img.src 形式发送数据,发送代码:
var _rometUlr="http://localhost:8089/ibop/images/po.gif";
var _img = new Image();
var _rnd_id = "_img_" + Math.random();
window[_rnd_id] = _img;
// 全局变量引用
_img.onload = _img.onerror = function(){
window[_rnd_id]= null;// 删除全局变量引用
};
_img.src=_rometUlr+"?data=" + data + '&rnd=' + Math.random();
1.1.3 打点服务器数据获取
打点服务器端数据获取使用java的过滤器,直接新建过滤器,监听po.gif请求
BehaviorCollectionFilter
com.newland.sys.utils.BehaviorCollectionFilter
BehaviorCollectionFilter
/images/po.gif
Java代码:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String uri = httpRequest.getRequestURI();
String strAllData = httpRequest.getQueryString(); //显示所有请求参数
String strData = httpRequest.getParameter("data");
//测试
if(log.isDebugEnabled()){
log.debug("user behavior uri:>>"+uri);
log.debug("user behavior data:>>"+strData);
}
//取到数据该做啥做啥
chain.doFilter(request, response);
}
1.1.4 采集数据元素的封装
为满足一线互联网的数据采集的要求,数据对象分为3种,json格式
1.1.4.1 界面操作,统一归纳为鼠标操作,封装的数据报文格式为:
{
"id": _pageId, //界面ID
"type": "mouse", //mouse 鼠标操作
"tag": tname, // HTML 标签
"tagid": targ.id, // HTML 标签 ID
"value": targ.value, //HTML标签值
"e.x": event.x, // 坐标X
"e.y": event.y, // 坐标Y
"timestamp": getTIme() //时间戳
}
1.1.4.2 DOM加载,为界面初始化的行为,数据报文格式为:
{
"id": _pageId, //界面ID
"type": "dom", //DOM加载
"start": _load_start, //加载开始时间
"end": _load_end, //加载结束时间
"load_time": _load_end - _load_start, //加载耗时
"url": url, //加载界面地址
"timestamp": getTIme() //时间戳
}
1.1.4.3 调用服务时的等待框行为,数据报文格式为:
{
"id": _pageId, //界面ID
"type": "service", //服务加载
"serviceName": serviceName, //服务名称
"start": start, //开始时间
"end": end, //结束时间
"timestamp": getTIme() //时间戳
}
实现的代码如下 po.js
/**
实例化用户行为对象
**/
var pv = (function(){
var _load_start = new Date().getTime();
var _pageName = document.location.href; //文件路径
var _pageId=null; //页面流水
var _rometUlr="http://localhost:8089/xxx/images/po.gif";
var _THRESHOLD = 10;
/**
* 获取无格式化时间
*/
var getTIme= function(){
var now = new Date();
var year = now.getFullYear(); //年
var month = now.getMonth() + 1; //月
var day = now.getDate(); //日
var hh = now.getHours(); //时
var mm = now.getMinutes(); //分
var ss = now.getSeconds(); //秒
var clock = year + "";
if(month < 10) clock += "0";
clock += month + "";
if(day < 10) clock += "0";
clock += day + "";
if(hh < 10) clock += "0";
clock += hh + "";
if (mm < 10) clock += '0';
clock += mm+ "";
if(ss<10) clock += '0';
clock += ss;
return(clock);
}
/**
* 获取格式化时间
*/
var getFormatTime=function()
{
var now = new Date();
var year = now.getFullYear(); //年
var month = now.getMonth() + 1; //月
var day = now.getDate(); //日
var hh = now.getHours(); //时
var mm = now.getMinutes(); //分
var ss = now.getSeconds();
var clock = year + "-";
if(month < 10) clock += "0";
clock += month + "-";
if(day < 10) clock += "0";
clock += day + " ";
if(hh < 10) clock += "0";
clock += hh + ":";
if (mm < 10) clock += '0';
clock += mm+ ":";
if(ss<10) clock += '0';
clock += ss;
return(clock);
}
/**
获取指定位数的随机数
**/
var mathRand = function(n)
{
var _num="";
if(n==null) n=1;
for(var i=0;i0){
var data = JSON.stringify(gatheringPoll);
_img.src=_rometUlr+"?data=" + encodeURI(data) + '&rnd=' + Math.random();
}
};
var _m = function(e){
var targ ;
if (!e) var e = window.event ;
if (e.target) targ = e.target ;
else if (e.srcElement) targ = e.srcElement ;
if (targ.nodeType == 3) // defeat Safari bug
targ = targ.parentNode ;
var tname ;
tname=targ.tagName ;
var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
var x = e.pageX || e.clientX + scrollX;
var y = e.pageY || e.clientY + scrollY;
//console.log("你点击了 " + tname + "元素"+"||value="+targ.value);
var unit ={"id":_pageId,"type":"mouse","url":_pageName,"tag":tname,"tagid":targ.id, "value":targ.value,"e.x":x ,"e.y":y,"timestamp":getTIme()};
gatheringPoll.push(unit);
if(gatheringPoll.length>=_THRESHOLD){
_s();
gatheringPoll=[];
}
};
var _d=function(){
if(document.readyState == "complete") //当页面加载状态
{
var _load_end = new Date().getTime();
var url = document.location.href; //文件路径
var unit ={"id":_pageId,"type":"dom","url":_pageName,"start":_load_start,"end":_load_end,"load_time":_load_end-_load_start,"url":url,"timestamp":getTIme()};
gatheringPoll.push(unit);
}
};
var _t =function(msg){
var unit ={"id":_pageId,"type":"tip","url":_pageName,"msg":msg,"timestamp":getTIme()};
gatheringPoll.push(unit);
};
var _sc=function(start,end,serviceName){
var unit ={"id":_pageId,"type":"service","serviceName":serviceName,"url":_pageName,"start":start,"end":end,"timestamp":getTIme()};
gatheringPoll.push(unit);
if(gatheringPoll.length>=_THRESHOLD){
_s();
gatheringPoll=[];
}
};
return {pageId : _pageId,pageName:_pageName,send : _s, mouseEvent:_m,domReady:_d,tipLog:_t, serviceLoding:_sc};
})(pv);
$(function(){
/*鼠标下压采集用户行为*/
$(document).mousedown(function(e){
pv.mouseEvent(e);
});
});
/*页面关闭时发送数据*/
window.onbeforeunload =function(e){
pv.send();
}
//当页面加载完成时采集
document.onreadystatechange =function(e){pv.domReady();}
只要在页面中引用就可以了,侵入性很小,而且采集到数据时,用户体验不降低