作为系列文章的第三篇文章,本文将重点关注数据采集层中的用户行为数据采集系统。这里的用户行为是指用户和产品UI交互行为主要表现在Android App、iOS App与Web页面。这些交互行为,有的会与后端服务通信,有的只会导致前端UI变化,但无论哪种行为,它总是伴随着一组属性数据。对于与后端交互的行为,我们可以从后端服务日志和业务数据库中获取相关数据;对于那些只发生在前端的行为,我们需要主动向后端报告。用户行为数据收集系统负责从前端收集所需的完整用户行为信息,用于数据分析和其他业务。 例如,下图显示了一个营销活动(简化版本)的注册过程。如果我们只依赖后端业务数据库,我们只能知道该活动带来了多少新的注册用户。通过收集前端用户的操作行为,我们可以分析整个活动的转换:海报页面的浏览量—>>点击立即注册跳转注册页面量—>>点击“获取验证码”数量—>>注册信息数量提交—>>实际注册用户数量。前端用户行为数据的价值不限于转化率分析,它还可以挖掘出更有用的信息,甚至与产品业务相结合。例如,作者最近正在制作的用户评分系统将从用户行为中提取部分数据作为评分依据。
在早期的产品开发中,每个后端研发人员都负责一个摊位。虽然他们也会做一些数据收集,但他们基本上只为自己的功能做自己的事情。通常,根据产品经理提出的数据需求,设计一个结构化的数据表来存储数据,然后打开它REST API向前端报告数据;前端负责将点埋在相应的位置,并以协商的数据格式向后端报告。随着业务的发展,这种做法暴露了许多问题,给前端和后端带来了混乱,主要体现在:埋在前端,并在报告中呼叫API不统一,报告的数据格式不统一;后端数据分散在多个数据表中,与业务逻辑严重耦合。 因此,我们考虑建立一个统一的用户行为数据采集系统,其基本原则是:统一的报告模式、统一的数据格式、集中的数据存储和尽可能全面的收集。在实现方面,主要解决了三个问题:
收集什么。找出你需要什么数据,并抽象出一个统一的数据格式。如何在前端采集它。解决前端如何有效地埋藏和全面收集的问题。如何存储后端。解决数据集中存储和易于分析的问题。采集什么用户在前端UI大多数操作分为两类:第一类,打开页面,浏览信息,然后点击感兴趣的内容进一步浏览;第二类,根据UI提示输入相关信息,然后单击提交。它的行为可以概括为三种:浏览、输入和点击(有时在移动终端上滑动)。其中,浏览和点击是导致页面变化和逻辑处理的重要事件,输入总是与点击事件有关。 因此,浏览和点击是我们想要收集的对象。对于浏览,我们关注的是浏览哪个页面,以及相关的元数据;对于点击,我们关注的是点击哪个页面的元素,与元素相关的其他元素的信息,以及相关的元数据。页面,在Android与IOS上使用View名字表示,在Web页面上使用URL(hostname pathname)表示。元素,在前端开发中使用UI元素id表示。与元素相关的其他元素信息是指与点击相关的输入/选择信息。例如,在上述注册页面中,与提交按钮相关的信息包括手机号码、验证码和姓名。元数据是指页面可以提供的其他有用信息,如URL中的参数、App这些数据通常是非常重要的维度信息,如中跳转页面时传递的参数。
除了这些页面中的数据信息外,还有两个重要的维度信息:用户和时间。用户维度用于关联同一用户在客户端上的行为。该方案由后端生成UUID,如果是登录用户,可以通过元数据中的用户进行缓存id关联;时间维度主要用于数据统计。考虑到前端可能会延迟报告,事件的发生时间将添加到前端报告中(大多数正常使用的移动终端应自动同步)。 将前端报告的数据格式定义如下。uuid、event_time、page必填字段,element点击事件的必填字段,attrs包含上述元素数据和与元素相关的其他元素的信息是动态变化的。
{ "uuid": "2b8c376e-bd20-11e6-9ebf-525499b45be6", "event_time": "2016-12-08T18:08:12", "page": " ** .example.com/poster.html", "element": "register", "attrs": { "title": "test"、 "user_id": 1234 }通过不同的方式,针对不同客户端的不同事件REST API为了报告,每个客户端只需调用两个与自己相关的客户端API即可。
REST API说明/user_action/web/pv上报Web页面浏览事件/user_action/ios/pv上报IOS页面浏览事件/user_action/android/pv上报Android页面浏览事件/user_action/web/click上报Web页面点击事件/user_action/ios/click上报IOS页面点击事件/user_action/android/click上报Android页面点击事件前端如何采集?整理数据格式和报告方式后,前端的关键工作是如何埋点。传统的埋点方法是在需要报告的位置组织和调用数据API,百度统计等google ** ysis都是这样做的。这是最常用的方法。缺点是需要嵌入代码并与业务逻辑耦合。近年来,一些新的数据公司通过底层提出了无埋点的概念hook所有的点击事件可以尽可能多地收集用户的操作,因此也可以称为全埋点。该方法不需要嵌入调用,代码耦合较弱,但将收集更多无用的数据,可控性较差。经过一些研究,结合我们自己的业务,形成了以下设计理念:
hook底层点击事件报告数据,统一报告地点的数据整理。UI设置元素的属性值是否报告该元素的点击事件。UI元素的属性值用于获取上述与元素相关的其他元素的信息。我们先在Web的H实践了5页,核心代码非常简单。首先,在加载页面时绑定所有页面click事件,报告页面浏览事件数据。第二,通过user_action_id通过属性来表示一个元素是否需要通过点击事件报告user_action_relation属性很容易说明当前元素与哪个元素相关,具体代码没有解释。
$(d).ready(function() / 页面浏览报告pvUpload({page: getPageUrl()}$.extend({title: getTitle()},getUrlParams())$(d).bind('click',function(event) var $target = $(event.target); var $ua = $target.closest('[user_action_id]';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;if ($ua.length > 0) var userActionId = $ua.attr('user_action_id';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; var userActionRelation = $("[user_action_relation=" userActionId "]";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;var relationData = []; / 发现相关元素的数据信息 if (userActionRelation.length > 0) userActionRelation.each(function() var jsonStr = JSON.stringify({ "r_placeholder_element": $(this).get(0).tagName, 'r_placeholder_text': $(this).text() ); jsonStr = jsonStr.replace(/placeholder/g,$(this).attr('id');;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;jsonStr = JSON.parse(jsonStr); relationData.push(jsonStr); ); clickUpload({page: getPageUrl(),element: userActionId}, $.extend({title: getTitle()},getUrlParams(),relationData)););;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;上述代码可以嵌入任何代码HTML页面,然后在相应的元素中说明。
<div> <div> <textarea id="answer" cols="30" rows="10" user_action_relation="answer-submit"></textarea> </div> <button user_action_id="answer-submit">提 交</button></div>后端怎么存数据进入后台后,首先访问Kafka在队列中,采用生产消费者模式进行处理。这样做的好处是:第一,功能分离,报告API接口不关心数据处理功能,只负责访问数据;第二,数据缓冲,数据报告速度不可控,取决于用户使用频率,该模式可以在一定程度上缓冲数据;第三,数据量大时容易扩展和增加数据处理Worker扩大,提高处理率。
{ "uuid": "2b8c376e-bd20-11e6-9ebf-525499b45be6", "event_time": "2016-12-08T18:08:12", "page": " ** .example.com/poster.html", "element": "register", "client_type": 0, "event_type": 0, "user_agent": "Mozilla/5.0 (Linux; Android 5.1; m3 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/37.0.0.0 Mobile MQQBrowser/6.8 TBS/036887 Safari/537.36 MicroMessenger/6.3.31.940 NetType/WIFI Language/zh_CN", "ip": "59.174.196.123", "timestamp": 1481218631, "event_id": 12, "attrs": { "title": "test", "user_id": 1234 }}再来看event_id的含义。前端传过来的一组组数据中,通过page和element可以区分出究竟是发生了什么事件,但是这些都是前端UI的名称,大部分是开发者才能看懂的语言,因此我们需要为感兴趣的事件添加一个通俗易懂的名称,比如上面的数据对应的事件名称为“在海报页面中注册”。将page+element、事件名称进行关联映射,然后将相应的数据记录id作为event id添加到上述的数据中,方便后期做数据分析时根据跟event id来做事件聚合。做这件事有两种方式:一种是允许相关人员通过页面进行配置,手动关联;一种是前端上报时带上事件名称,目前这两种方式我们都在使用。 最后,来看看数据存储的问题。传统的关系型数据库在存储数据时,采用的是行列二维结构来表示数据,每一行数据都具有相同的列字段,而这样的存储方式显示不适合上面的数据格式,因为我们无法预知attrs中有哪些字段数据。象用户行为数据、日志数据都属于半结构化数据,所谓半结构化数据,就是结构变化的结构化数据(WIKI中的定义),适合使用NoSQL来做数据存储。我们选用的是ElasticSearch来做数据存储,主要基于这么两点考虑:
Elasticsearch是一个实时的分布式搜索引擎和分析引擎,具有很强的数据搜索和聚合分析能力。在这之前我们已经搭建了一个ELK日志系统,可以复用Elasticsearch集群做存储,也可以复用Kibana来做一些基础的数据分析可视化。Elasticsearch的使用方法可以参考Elasticsearch使用总结一文,这里不做过多讲解。使用Elasticsearch来做数据存储,最重要的是两件事:建立Elasticsearch的映射模板、批量插入。Elasticsearch会根据插入的数据自动建立缺失的index和doc type,并对字段建立 ** pping,而我们要做的创建一个dynamic template,告诉Elasticsearch如何自动建立,参考如下。批量插入,可以通过Elasticsearch的bulk API轻松解决。
"user_action_record": { "order": 0, "template": "user_action_record_*", "settings": { }, " ** ppings": { "_default_": { "dynamic_templates": [{ "string_fields": { " ** pping": { "type": "string", "fields": { "raw": { "index": "not_ ** yzed", "ignore_above": 256, "type": "string" } } }, " ** tch_ ** pping_type": "string" } }], "properties": { "timestamp": { "doc_values": true, "type": "date" } }, "_all": { "enabled": false } } }}文 | Bruce
系列文章源于:创业公司做数据分析(三)用户行为数据采集系统
往期文章推荐:
创业公司做数据分析(一):先来捋捋思路!
创业公司做数据分析(二): 搭建数据运营系统
大数据平台在互联网行业的应用
互联网数据分析的底层应用架构
扫码咨询与免费使用
申请免费使用