内容采集系统(采什么用户在前端UI上的操作,大多数表现为两类 )
优采云 发布时间: 2022-04-09 06:25内容采集系统(采什么用户在前端UI上的操作,大多数表现为两类
)
选什么
大部分用户对前端UI的操作分为两类:第一类,打开某个页面,浏览其中的信息,然后点击感兴趣的内容进一步浏览;第二类,打开某个页面,根据UI提示输入相关信息,然后点击提交。它的行为可以分为三种类型:浏览、打字和点击(有时在移动设备上滑动)。其中,浏览和点击是引起页面变化和逻辑处理的重要事件,输入始终与点击事件相关联。
所以浏览和点击是我们想要的采集。对于浏览,我们关注查看了哪个页面,以及与之关联的元数据;对于点击,我们关注点击了哪个页面的哪个元素、与该元素相关的其他元素的信息以及相关的元数据数据。该页面在 Android 和 IOS 上由 View 名称表示,在 Web 页面上由 URL(主机名+路径名)表示。元素,在前端开发中以 UI 元素 id 表示。与元素相关的其他元素信息是指与“点击”相关的输入/选择信息。例如,在上面的注册页面中,与“提交”按钮相关的信息包括手机号、验证码和姓名。元数据是指页面可以提供的其他有用信息,
除了这些页面中的数据信息外,还有两个重要的信息维度:用户和时间。用户维度用于关联同一用户在客户端上的行为。采用的方案是从后端生成一个随机的UUID,前端会自己缓存。如果是登录用户,可以使用元数据中的用户标识。时间维度主要用于数据统计。考虑到前端可能会延迟上报,所以前端上报会加上事件发生时间(目前大部分正常使用的移动终端,时间信息应该是自动同步的)。
综上所述,前端上报的数据格式定义如下。uuid、event_time、page 是必填字段,element 是点击事件的必填字段,attrs 收录上述元数据和与该元素关联的其他元素的信息,这些信息是动态变化的。
{
"uuid": "2b8c376e-bd20-11e6-9ebf-525499b45be6",
"event_time": "2016-12-08T18:08:12",
"page": "www.example.com/poster.html",
"element": "register",
"attrs": {
"title": "test",
"user_id": 1234
}
}
不同客户端的不同事件通过不同的 REST API 上报,每个客户端只需要调用与自己相关的两个 API。
如何使用前端
梳理好数据格式和上报方式之后,前端的重点工作就是如何埋点。传统的埋点方式是在需要上报的位置组织数据,调用API,将数据传输到后台,如百度统计、谷歌分析等。这是最常用的方法。缺点是需要在代码中嵌入调用,与业务逻辑耦合。近年来,一些新的数据公司提出了“无埋点”的概念。通过在底层钩住所有的点击事件,可以将用户的操作尽可能的往下采集,所以也可以称为“全埋”。点”。这种方法不需要嵌入式调用,代码耦合性弱,但是会采集 大量无用数据,可控性差。经过一番研究,结合我们自己的业务,我们形成了以下设计思路:
在底层钩住click事件做数据上报,在上报的地方做数据整理。
通过UI元素的属性值设置是否上报元素的点击事件。
元素的关联关系由UI元素的属性值设置,用于获取上述“与该元素关联的其他元素的信息”。
我们首先在Web的H5页面进行了实践,核心代码很简单。首先,绑定页面加载时的所有点击事件,并上报页面浏览事件数据。其次,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 页面中,然后在对应的元素中声明即可。例如:
提 交
如何保存后端
数据进入后台后,首先连接到Kafka队列,以生产-消费者模式进行处理。这样做的好处是:一是功能分离,上报的API接口不关心数据处理功能,只负责访问数据;第二,数据缓冲,数据上报率不可控,取决于用户的使用频率,使用这种模式可以在一定程度上缓冲数据;第三,易于扩展。当数据量较大时,可以通过增加数据处理工人进行扩展,提高处理速度。
除了前端上报的数据内容,我们还需要在后端添加一些其他必要的信息。在将数据插入Kafka队列之前,需要添加五个维度的信息:客户端类型(Web/Android/IOS)、事件类型(浏览/点击)、时间、客户端IP和用户代理。Consumer Worker 从 Kafka 获取数据后,需要添加一个名为 event_id 的字段数据。具体含义将在后面解释。因此,最终存储的数据格式如下:
{
"uuid": "2b8c376e-bd20-11e6-9ebf-525499b45be6",
"event_time": "2016-12-08T18:08:12",
"page": "www.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的含义。在前端发来的一组数据中,你可以通过页面和元素来区分发生了什么,但是这些都是前端UI的名称,大部分都是开发者能看懂的语言,所以我们需要有兴趣给活动添加一个通俗易懂的名字,比如上面数据对应的活动名字是“在海报页面注册”。页面+元素和事件名称关联映射,然后将对应的数据记录id作为事件id添加到上面的数据中,方便后面的数据分析根据事件id做事件聚合。有两种方法:一种是让相关人员通过页面进行配置和手动关联;另一种是前端上报时带上事件名称。
最后,我们来看看数据存储的问题。传统的关系型数据库在存储数据时,采用行列的二维结构来表示数据。每一行数据都有相同的列字段,这种存储方式不适合上面的数据格式,因为我们无法预测 attrs 中的数据。有哪些现场数据。用户行为数据和日志数据属于半结构化数据。所谓半结构化数据,就是结构发生变化的结构化数据,适合使用NoSQL进行数据存储。我们选择 ElasticSearch 进行数据存储,主要基于两个考虑:
Elasticsearch的使用请参考文章Elasticsearch使用总结,这里不再过多解释。在使用 Elasticsearch 进行数据存储时,最重要的有两件事:为 Elasticsearch 建立映射模板和批量插入。Elasticsearch 会根据插入的数据自动创建缺失的索引和文档类型,并为字段创建映射。我们需要做的是创建一个动态模板来告诉 Elasticsearch 如何自动创建它。请参阅以下内容。批量插入可以通过 Elasticsearch 的批量 API 轻松解决。
"user_action_record": {
"order": 0,
"template": "user_action_record_*",
"settings": {
},
"mappings": {
"_default_": {
"dynamic_templates": [{
"string_fields": {
"mapping": {
"type": "string",
"fields": {
"raw": {
"index": "not_analyzed",
"ignore_above": 256,
"type": "string"
}
}
},
"match_mapping_type": "string"
}
}],
"properties": {
"timestamp": {
"doc_values": true,
"type": "date"
}
},
"_all": {
"enabled": false
}
}
}
}