背景: 抓取商品的累计销量/库存数据, 需要基于此类数据推算每个时间段内的新增销量数据.
其实指标不限于销量, 类似的需求有对于视频点赞/用户粉丝数/…等数值随时间变化趋势的反推.
此类采集数据特点:
- 维度高, 商品/SKU/视频/推文/应用榜单/…
- 时序性强, 跟据爬虫的抓取策略, 同对象数据的抓取间隔从每秒一条 (直播) 到天级别不等
- 数据质量:
- 数据不一定完整, 即使爬虫对目标定时抓取, 由于种种反爬策略或者风控手段, 不能保证每个时间段内一定抓到数据
- 抓回数据会有异常, 对于爬虫的数据”投毒”需要二次加工排除掉
指标日志去重
基于基本的数据观察, 大部分时候抓取的数据指标并未发生变化. 可以基于此做最基本的增量日志表:
- 原始日志:
<ts, id, val>
- 增量日志:
<ts, id, diff=val-last_val>0, duration=ts-last_ts, val>
- 类似于做一次DIFF压缩
- 如果不发生变更, 那么只记录一条
- 增量日志里面的
duration
是必要的, 用于区分两次增量日志之间到底是确实原始日志, 还是去重的结果
日志去重后, 可以极大减少数据量级. 对于不需要磨数据的场合, 就可以直接线上对增量日志标汇总做功能了.
快照表
各别日志, 由于两次间隔太久, 查询上次记录需要遍历的时间段过长. 为了单次查询上次记录涉及数据范围可控, 需要定期做快照表, 记录到改时刻为止的最后观测值. 也可以通过定期写入一条增量值为0的日志, 来确保两次日志间隔有上限.
缺点是随着主体越来越多, 每次快照表的数据量越来越大 (想象一张每月商品累计销量榜). 可以通过业务上砍需求, 假定主体的生命周期, 来确保有数据淘汰. 如3个月销量没变过的商品, 基本认为凉凉了, 不再算累计销量榜了.
数据清理
一些指标值有内在的约束, 比如说新增销量不能为负等. 最简单的做法, 就是当前累计销量值比上一次少时, 就当作没看见. 但是这里是建立在上次数值正确的前提下. 可能上个点本身就是异常的. 数据是否排除需要看最近几次抓取值来判定. 演化成一个常见的算法题: 一个数据序列中找到最长单增子序列.
此外, 可以做一些业务上安全的假定或者约束, 比如说认为增长速率不能超过R, 那么增量日志中 duration * R > inc
的点就可以刨掉了.
但这个很难, 直播卖货秒光, 你能说是异常点么? 只能通过其他因素来辅助判定.
数据清理最好只依赖之前日志计算, 这样可以确保计算后的结果是只追加不需要修改的. 依赖前后日志来做清理的逻辑, 确实会把数据磨得更合理一些, 但是会把清理流程变得非常复杂, 得不偿失.
具体实施
- 实时处理:
- 查询最近几次的日志并做计算逻辑
- 缺点, 每个日志都要查询并计算, 不好批量化, 性能跟不上
- 批处理:
- 一开始是在Clickhouse里面做, 行变数组聚合后各种map/filter/reduce骚操作, 问题是计算内存开销太高
- 基于窗口函数, 理论上按照时间顺序的窗口计算的空间开销可以和开窗长度相关, 资源消耗相对可控
- presto 不支持 range https://github.com/prestodb/presto/issues/3301 比较麻烦
- max_by 函数如果支持的话, 有些场合不需要走窗口计算
抓取有效性策略
抓取有效性指标 = 增量日志数据量 / 原始日志数据量.
简而言之, 每次抓取都是有成本的 (代理, 计算, 流量等). 需要在单位次数内观测到尽可能多的变化.
这里需要对目标指标的变化率做关联因素分析. 如商品销量, 销量变化概率认为和商品上架时间, 及累计销量有关 (当然只是表征, 具体原因还是要具体分析). 先基于定频的抓取计算结果, 基于这些因素的数据统计分布分析/预测, 可以做有效提升抓取有效性的策略.