案例:某Feed流产品的数据采集历程
业务背景
一家专注于互联网行业学习交流的社区媒体,产品之前的首页feed主要来自于用户关注的好友产生的数据。但存在问题:
- 关注的好友不够多,刷不出新内容;
- 关注的好友没动作,也刷不出新内容。
【问题】如何才能让用户的阅读范围跳出他关注圈子的限制?
【解决】上个性化推荐feed,基于用户的阅读历史、好友关系、关注话题、当下热门进行推荐。
数据采集流程
1、明确需求**
产品自身的指标建模 | 业务部门的分析需求 | |
---|---|---|
内容推荐模块 | 运营部门的需求 | 广告部门的需求 |
- content impression by user - 各个内容的CTR - 各个内容类型的曝光和点击需要能够分开查看 - 评论数和点赞数是否对阅读产生影响 |
- 每个卡片配[不感兴趣]按钮 - 用户主动刷新/加载内容 - 区分不同推荐理由 |
- 每天广告的曝光量及CTR效果 - 广告曝光时长(focus duration) |
2、撰写埋点文档
2.1 埋点事件(指标)确认
需求 | 指标 | 埋点 |
---|---|---|
- 内容卡片曝光量(可按内容类型和话题拆开) - 内容卡片点击数(可按内容类型和话题拆开) - 广告曝光量 - 广告点击数 - 用户看到广告卡片的时长 - 主动刷新推荐feed的次数 - 主动滚动到底部加载新内容的次数 - 点击不感兴趣的次数和原因 |
- cardShow - cardClick - adShow - adClick - pullToRefresh - loadMore - notInterested |
2.2 属性拆解
属性拆解的原则:WWWHW(WHO WHEN WHERE HOW WHAT),某个用户在某时某地以某种方式完成了某个具体的行为。
其中WHO、WHEN、WHERE属于公共属性,HOW和WHAT属于专用属性,需要手动埋点。
- 公共属性
定义:该文档内所有事件都会包含的属性。
说明:通常是公司数据组规定,所有业务线产品均需遵守的属性。
展示:为便于研发同学区分,通常将公共属性字段写在前面,且与各事件中的公共属性保持同一单元格背景色。
字段名称 | 类型 | 说明 | 上线版本 | 上线时间 | 当前状态 | 备注 |
---|---|---|---|---|---|---|
distinct_id | 字符串 | 用户ID | 2.1.0 | 2018/9/12 | 上线 | |
time | 日期 | 时间,事件触发的时间 | 2.1.0 | 2018/9/12 | 上线 | |
$app_version | 字符串 | 应用的版本 | 2.1.0 | 2018/9/12 | 上线 | |
$ip | 字符串 | IP,自动获取 | 2.1.0 | 2018/9/12 | 上线 | |
$country | 字符串 | 国家,通过IP解析 | 2.1.0 | 2018/9/12 | 上线 | |
$province | 字符串 | 省份,通过IP解析 | 2.1.0 | 2018/9/12 | 上线 | |
$city | 字符串 | 城市,通过IP解析 | 2.1.0 | 2018/9/12 | 上线 | |
… | … | … | … | … | … |
- 事件(专用属性)
定义:需要手动埋点的具体事件,比如页面曝光量、广告点击量等等。
说明:确认需查看的数据需求后,由产品定义,跟随本期产品上线。
具体字段: (1)事件编号:不重要,就一序号,不过最好把同一页面上的事件放在一起,方便研发使用 (2)事件显示名:在数据平台查询时显示的名称(需要研发设置,默认显示英文变量名) (3)事件英文变量名:通常大公司数据组会根据各端业务情况,对部分事件进行抽象和固化,形成固定事件,各端统一使用这些固定事件。超出固定事件范围之外的事件,由我们自定义命名,不过一般会有一个统一的命名规则,方便沟通管理。 (4)属性显示名:同上 (5)属性英文变量名:同上(属性指的是这个事件的一些性质,比如一个广告点击事件,记录的是某个轮播广告位的点击量,那么可以加上“广告id”的属性,通过“广告id”字段,确认具体某个广告的点击量)
(6)属性值类型:数值、字符串等等
(7)属性值示例/说明:说明具体参数的对应内容,或是参数示例(比如还是那个广告点击事件,假如我加上“广告id”属性,对应的示例就是某串广告id,例如E23787094NME;假如我加上的是“广告位置”属性,对应的示例可能就是“1代表Banner1、2代表Banner2,以此类推,上限为10”)
(8)事件类别/上报时机:说明记录数据的时机,通常曝光量都是XX页面加载时上报,点击量都是XX(按钮、图片等等)点击时上报。
事件编号 | 事件变量 | 事件显示名 | 事件定义 | 属性变量 | 属性显示名 | 属性值类型 |
---|---|---|---|---|---|---|
1 | cardShow | 卡片展示 | contentId | 内容ID | 字符串 | |
contentType | 内容类型 | 字符串 | ||||
position | 列表位置 | 数值 | ||||
recommandReason | 推荐理由 | 字符串 | ||||
channel | 内容频道 | 字符串 | ||||
withPicture | 有无图片 | 布尔值 | ||||
2 | cardclick | 卡片点击 | contentId | 内容ID | 字符串 | |
contentType | 内容类型 | 字符串 | ||||
position | 列表位置 | 数值 | ||||
recommandReason | 推荐理由 | 字符串 | ||||
channel | 内容频道 | 字符串 | ||||
withPicture | 有无图片 | 布尔值 | ||||
comments | 评论数 | 数值 | ||||
likes | 点赞数 | 数值 | ||||
3 | adShow | 广告展示 | adId | 广告ID | 字符串 | |
adType | 广告类型 | 字符串 | ||||
position | 列表位置 | 数值 | ||||
duration | 停留时长 | 数值 | ||||
4 | adClick | 广告点击 | adId | 广告ID | 字符串 | |
adType | 广告类型 | 字符串 | ||||
position | 列表位置 | 数值 | ||||
5 | pullToRefresh | 下拉刷新 | times | 单次访问刷新次数 | 数值 | |
interval | 刷新间隔时长 | 数值 | ||||
6 | loadMore | 加载更多 | depth | 加载深度 | 数值 | |
interval | 加载间隔时长 | 数值 | ||||
7 | notInterested | 不感兴趣 | contentId | 内容ID | 字符串 | |
reason | 不感兴趣理由 | 字符串 |
2.3 触发时机
事件编号 | 事件变量 | 事件显示名 | 事件定义 |
---|---|---|---|
1 | cardShow | 卡片展示 | 1.卡片在用户可见的屏幕范围内出现就算一次展示 |
2 | cardclick | 卡片点击 | 1.用户点击卡片可跳转的区域即算作点击一次 |
3 | adShow | 广告展示 | 1.广告卡片在用户可见的屏幕范围内出现就算一次展示 |
4 | adClick | 广告点击 | 1.用户点击广告卡片可跳转的区域即算作点击一次 |
5 | pullToRefresh | 下拉刷新 | 1.用户在顶部下拉,触发接口刷新后,新内容渲染出来算作一次行为 |
6 | loadMore | 加载更多 | 1.用户滚动到页面底部,触发了底部的加载接口,新内容渲染出来算作一次行为 |
7 | notInterested | 不感兴趣 | 1.用户点击X,并选择不感兴趣的理由后触发该行为 2.如果用户只点击X,而没选择理由视为没触发,避免误点 |
2.4 属性来源
事件编号 | 事件变量 | 事件显示名 | 事件定义 | 属性变量 | 属性显示名 | 属性值类型 | 属性定义 |
---|---|---|---|---|---|---|---|
1 | cardShow | 卡片展示 | 1.卡片在用户可见的屏幕范围内出现就算一次展示 | contentId | 内容ID | 字符串 | 从API返回的数据中提取 |
contentType | 内容类型 | 字符串 | 从API返回的数据中提取 | ||||
position | 列表位置 | 数值 | 按照用户实际加载的列表顺序赋值 | ||||
recommandReason | 推荐理由 | 字符串 | 从API返回的数据中提取 | ||||
channel | 内容频道 | 字符串 | 从API返回的数据中提取 | ||||
withPicture | 有无图片 | 布尔值 | 从API返回的数据中提取 | ||||
2 | cardclick | 卡片点击 | 1.用户点击卡片可跳转的区域即算作点击一次 | contentId | 内容ID | 字符串 | 从API返回的数据中提取 |
contentType | 内容类型 | 字符串 | 从API返回的数据中提取 | ||||
position | 列表位置 | 数值 | 按照用户实际加载的列表顺序赋值 | ||||
recommandReason | 推荐理由 | 字符串 | 从API返回的数据中提取 | ||||
channel | 内容频道 | 字符串 | 从API返回的数据中提取 | ||||
withPicture | 有无图片 | 布尔值 | 从API返回的数据中提取 | ||||
comments | 评论数 | 数值 | 从API返回的数据中提取 | ||||
likes | 点赞数 | 数值 | 从API返回的数据中提取 | ||||
3 | adShow | 广告展示 | 1.广告卡片在用户可见的屏幕范围内出现就算一次展示 | adId | 广告ID | 字符串 | 从API返回的数据中提取 |
adType | 广告类型 | 字符串 | 从API返回的数据中提取 | ||||
position | 列表位置 | 数值 | 按照用户实际加载的列表顺序赋值 | ||||
duration | 停留时长 | 数值 | 从API返回的数据中提取 | ||||
4 | adClick | 广告点击 | 1.用户点击广告卡片可跳转的区域即算作点击一次 | adId | 广告ID | 字符串 | 从API返回的数据中提取 |
adType | 广告类型 | 字符串 | 从API返回的数据中提取 | ||||
position | 列表位置 | 数值 | 按照用户实际加载的列表顺序赋值 | ||||
5 | pullToRefresh | 下拉刷新 | 1.用户在顶部下拉,触发接口刷新后,新内容渲染出来算作一次行为 | times | 单次访问刷新次数 | 数值 | 从API返回的数据中提取 |
interval | 刷新间隔时长 | 数值 | 从API返回的数据中提取 | ||||
6 | loadMore | 加载更多 | 1.用户滚动到页面底部,触发了底部的加载接口,新内容渲染出来算作一次行为 | depth | 加载深度 | 数值 | 从API返回的数据中提取 |
interval | 加载间隔时长 | 数值 | 从API返回的数据中提取 | ||||
7 | notInterested | 不感兴趣 | 1.用户点击X,并选择不感兴趣的理由后触发该行为 2.如果用户只点击X,而没选择理由视为没触发,避免误点 |
contentId | 内容ID | 字符串 | 从API返回的数据中提取 |
reason | 不感兴趣理由 | 字符串 | 从API返回的数据中提取 |
3、技术埋点(与RD沟通)
在屏幕里出现,怎样叫出现?出现多次怎么办?只出现了一半的高度怎么办?你这样把前端逻辑搞得很复杂,为什么非得做这个可见屏幕内的出现啊。就不能一次API拉回来的20条数据都算作展现了么? | ||
---|---|---|
首先,我们这个推荐feed刚上线,需要大量的数据来调校,这依赖于对推荐出来的内容精准核算曝光和CTR。 - 如果API拉回本地就算展现,那第一屏往往就只能显示3、4条资讯,后面的十几条资讯从来没在用户这曝过光,算作已展现的话会大大影响我们CTR的计算。 - 因此还得麻烦你们来实现一下监测卡片出现的前端逻辑。 |
||
另外,我们其实是想统计精准的CTR,因此对于「出现」这个事件的定义还是希望更加严谨一些。 - 如果用户是在快速滚动feed过程中,那么其实是看不清卡片上的内容的,这样的也不要记录下来。 - 为了方便你们研发实施,这里我们就定一个数吧,只有在feed流处于静止状态超过0.5s后,才开始进行「展现」这个事件的触发。 - 另外就是这种展现为了统一统计口径,单一卡片在一次推荐列表刷新中,只计算一次「展现」,多次展现不累加。 |
||
(此处省略一千字)我对事件的定义进行了完善。 |
||
那做呗 |
4、数据检验
4.1 数据检验
4.2 发现的坑
- 「卡片展现」、「卡片点击」的位置属性( position )时常出现0
- 「加载更多」的加载间隔时长有时候会出现极大的值(几万秒)
4.3 版本信息更新
| 事件编号 | 事件变量 | 事件显示名 | 事件定义 | 属性变量 | 属性显示名 | 属性值类型 | 属性定义 | 当前状态 | 埋点形式 | 上线版本 | 上线时间 | 备注 | | :—-: | :—-: | :—-: | :—-: | :—-: | :—-: | :—-: | :—-: | :—-: | :—-: | :—-: | :—-: | :—-: | | 1 | cardShow | 卡片展示 | 1.卡片在用户可见的屏幕范围内出现就算一次展示 | contentId | 内容ID | 字符串 | 从API返回的数据中提取 | 上线 | 前端 | 2.1.0 | 2018-9-12 | | | | | | | contentType | 内容类型 | 字符串 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | | | | | | | position | 列表位置 | 数值 | 按照用户实际加载的列表顺序赋值 | 上线 | | 2.1.0 | 2018-9-12 | | | | | | | recommandReason | 推荐理由 | 字符串 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | | | | | | | channel | 内容频道 | 字符串 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | | | | | | | withPicture | 有无图片 | 布尔值 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | | | 2 | cardclick | 卡片点击 | 1.用户点击卡片可跳转的区域即算作点击一次 | contentId | 内容ID | 字符串 | 从API返回的数据中提取 | 上线 | 前端 | 2.1.0 | 2018-9-12 | | | | | | | contentType | 内容类型 | 字符串 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | | | | | | | position | 列表位置 | 数值 | 按照用户实际加载的列表顺序赋值 | 上线 | | 2.1.0 | 2018-9-12 | | | | | | | recommandReason | 推荐理由 | 字符串 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | | | | | | | channel | 内容频道 | 字符串 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | | | | | | | withPicture | 有无图片 | 布尔值 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | | | | | | | comments | 评论数 | 数值 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | | | | | | | likes | 点赞数 | 数值 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | | | 3 | adShow | 广告展示 | 1.广告卡片在用户可见的屏幕范围内出现就算一次展示 | adId | 广告ID | 字符串 | 从API返回的数据中提取 | 上线 | 前端 | 2.1.0 | 2018-9-12 | | | | | | | adType | 广告类型 | 字符串 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | | | | | | | position | 列表位置 | 数值 | 按照用户实际加载的列表顺序赋值 | 上线 | | 2.1.0 | 2018-9-12 | | | | | | | duration | 停留时长 | 数值 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | | | 4 | adClick | 广告点击 | 1.用户点击广告卡片可跳转的区域即算作点击一次 | adId | 广告ID | 字符串 | 从API返回的数据中提取 | 上线 | 前端 | 2.1.0 | 2018-9-12 | | | | | | | adType | 广告类型 | 字符串 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | | | | | | | position | 列表位置 | 数值 | 按照用户实际加载的列表顺序赋值 | 上线 | | 2.1.0 | 2018-9-12 | | | 5 | pullToRefresh | 下拉刷新 | 1.用户在顶部下拉,触发接口刷新后,新内容渲染出来算作一次行为 | times | 单次访问刷新次数 | 数值 | 从API返回的数据中提取 | 上线 | 前端 | 2.1.0 | 2018-9-12 | | | | | | | interval | 刷新间隔时长 | 数值 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | | | 6 | loadMore | 加载更多 | 1.用户滚动到页面底部,触发了底部的加载接口,新内容渲染出来算作一次行为 | depth | 加载深度 | 数值 | 从API返回的数据中提取 | 上线 | 前端 | 2.1.0 | 2018-9-12 | | | | | | | interval | 加载间隔时长 | 数值 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | | | 7 | notInterested | 不感兴趣 | 1.用户点击X,并选择不感兴趣的理由后触发该行为
2.如果用户只点击X,而没选择理由视为没触发,避免误点 | contentId | 内容ID | 字符串 | 从API返回的数据中提取 | 上线 | 前端 | 2.1.0 | 2018-9-12 | | | | | | | reason | 不感兴趣理由 | 字符串 | 从API返回的数据中提取 | 上线 | | 2.1.0 | 2018-9-12 | |
5、新需求
一个多月过去了。推荐feed大受好评,人均阅读数和内容曝光量都大涨,相应的广告曝光及点击也随之增长。 看到如此正面的效果后,产品团队决定继续优化推荐功能。 【新功能】查看当前资讯,返回推荐feed时,在该卡片下方直接推荐和这个卡片相关联的3个卡片。
新推荐的三个卡片,都没有position | |
---|---|
属性变量 | 属性显示名 | 属性值类型 | 属性定义 | 备注 |
---|---|---|---|---|
contentId | 内容ID | 字符串 | 从API返回的数据中提取 | |
contentType | 内容类型 | 字符串 | 从API返回的数据中提取 | |
position | 列表位置 | 数值 | 按照用户实际加载的列表顺序赋值 | 从1开始 2018-10-23更新:因为加入了从文章页返回推荐feed自动在列表中插入相关推荐的功能,新插入的3条内容会打乱之前的列表排序值,因此每次插入都会重新计算整个列表各个卡片的排序值 |
recommandReason | 推荐理由 | 字符串 | 从API返回的数据中提取 | |
channel | 内容频道 | 字符串 | 从API返回的数据中提取 | |
withPicture | 有无图片 | 布尔值 | 从API返回的数据中提取 |