作者:人月神话,新浪博客同名
最近大家可能都注意到,今日头条App端和后台管理端都增加了文章统计分析的详细数据,可以看到展现浏览详细数据,点击率,评论转发量,用户平均流量时长,数据按天的时间趋势图等详细数据分析。
因此今天准备先从今日头条的推荐机制分析开始,谈下从文章推送到文章详细数据统计的技术实现机制和关键点。在分析前我们还是先看下电商平台推荐引擎的内容,要明白文章本身也是一个商品,实际上核心的推荐引擎思路和电商推荐引擎基本还是一致的。
推荐引擎在当前电商平台用的相当多,推荐引擎的分类一般可以分为基于大众行为的推荐引擎和个性化推荐引擎。
大众行为的推荐引擎,对每个用户都给出同样的推荐,这些推荐可以是静态的由系统管理员人工设定的,或者基于系统所有用户的反馈统计计算出的当下比较流行的物品。
个性化推荐引擎,对不同的用户,根据他们的口味和喜好给出更加精确的推荐,这时,系统需要了解需推荐内容和用户的特质,或者基于社会化网络,通过找到与当前用户相同喜好的用户,实现推荐。
这是一个最基本的推荐引擎分类,其实大部分人们讨论的推荐引擎都是将个性化的推荐引擎,因为从根本上说,只有个性化的推荐引擎才是更加智能的信息发现过程。
大部分推荐引擎的工作原理还是基于物品或者用户的相似集进行推荐。那么参考图 1 给出的推荐系统原理图,根据不同的数据源发现数据相关性的方法可以分为以下几种:
下面根据我重新画的图来说明下常见的一些模式:
1. 基于用户画像后的推荐
注意基于用户画像的针对性营销本身也是一种推荐引擎,要做到这样首先是要对用户进行精确画像,然后再根据用户画像+商品营销策略进行针对性推荐。
举例来说我们要对一款发烧耳机进行针对性营销,那么我们首先要从用户画像里面找到20岁以下,爱好音乐,发烧友这几个关键用户标签,然后再进行针对性推荐。
2. 基于人口统计学的推荐
简单来说找到用户A和用户B的相关性和相似性,然后将用户A的购买行为推荐给用户B。可以看到该模式下只分析用户相关性,没有分析物品相关性,也没有分析用户购买行为间的相关性。这种模式本身的推荐准确度个人感觉偏低,但是好处就是解决新用户冷启动问题。
比如当一个18岁的女大学生注册成功后,虽然这个用户还没有发生任何购买,但是根据用户相关性分析已经可以马上就可以进行相关物品的推荐。
3. 基于内容的推荐
根据推荐物品或内容的元数据,发现物品或者内容的相关性。举个简单例子即当我看了一部爱情浪漫影片后,可以马上给我推荐类似的影片。
主要什么影片是类似影片?要基于内容推荐首先就要对物品的相关性或相似性进行建模,这种相关性是静态的关系,物品相似度的分析仅仅依赖于物品本身的特征,这里没有考虑人对物品的态度。
举例来说,最近几天我都在某电商网站浏览某类的产品,这个时候我们根本没有登陆电商系统,只是做了浏览和加购物车的操作等。可能过2两天你就会收到电商平台发出的EDM营销或推荐邮件。在这种模式下即基本是基于内容推荐。当然推荐的时候电商平台会优先帮你选择销量高,评价好或性价比高的相似产品。
4. 用户相关性+用户购买行为协同过滤推荐
注意不是单纯静态用户相关性推荐,也不是单纯的用户购买行为推荐,而是将两者结合起来进行协同过滤推荐。简单来说就是首先根据用户购买行为发现了用户A和用户C是相关或相似用户,而不是根据静态用户属性简单判断用户相似,然后再根据相关性特点将用户A的购买推荐给用户C。
5. 物品相关性+用户购买行为的协同过滤推荐
即物品相关性不是简单进行物品静态相关性建模,而是根据用户购买行为来发现物品相关性。即我们常说的购买了物品A的也同时购买了物品B。比如购买了苹果手机的用户也同时购买了苹果手机保护膜。
这种推荐模式是我们经常看到的模式,比如我们在电商购买购买书籍,购买某种商品的时候,实时出现的推荐都属于这种模式为主。系统会告诉你购买了某本书的用户一般还同时购买了哪些书籍。我们不用去探究用户本身是否相关,而是关心用户购买行为触发的物品相关性。
如果我们将头条和微博比较,虽然头条也可以关注某个头条号或用户,但是头条核心的推荐仍然是基于内容的推荐机制,即本身弱化用户之间的关系,这是头条和微博推送的一个关键区别。
我们浏览头条你可能也会发现一些文章推送特点。
比如我们可能会浏览到我们关注的某个头条号发送的文章或微头条,或者你最近正在关注三十而已,那么会收到大量关于该电视剧的内容推荐。或者说你最近浏览小孩教育方面的文章比较多,也会收到很多类似文章推荐。当然你也可能收到你居住城市的热门内容推荐。
因此头条整体的文章推荐机制可以理解:
核心内容推荐+关注推荐+区域推荐+热门事件推荐组成。
注:即使你每天都花费大量时间浏览头条文章或视频,但是你时间看到的内容可能连千万分之一都不到,仅仅是整个头条海量内容的冰山一角。而且你浏览的越多,头条越懂你,给你推荐你感兴趣的内容,但是同样也导致你的知识内容面越来越窄,这也是我们也经常说头条会让你变得越来越懒于思考的原因。
所以你可以看到整个头条文章的推荐核心就是用户本身的标签关键字和文字提取的标签关键字的一个匹配过程。只要匹配上那么就可以进行推送。
首次推送一般也叫冷启动,简单来说就是先很小范围给你推一波,看下点击率,阅读量,点赞和转发量,如果数据都不错,就进一步给你推送到一个更大的流量池,如果效果不好往往就降低推送量或停止推荐。
用户的关键词往往都是基于用户大量的历史文章浏览所形成和提取出来的,但是对于用户的关键标签关键词,本身应该形成不同的权重。
而权重需要考虑到用户的浏览频度和浏览时间两个方面
比如我们最近1,2天突然开始大量浏览围棋教学类视频,那么你可能看到头条会大量推荐围棋主题相关的视频和内容文章。
也正是这个原因,即使你在头条一个账号都没有关注,但是头条仍然会根据你的浏览习惯和内容进行精确的内容推荐。关注和粉丝推荐反而在头条浏览里面权重不高。
反之,如果你根本就没有大量内容浏览和阅读,那么头条无法根据你的动态浏览行为进行推荐,则只能根据当前主流事件热点,你关注的人,你的区域等静态信息进行推荐,直到你产生大量浏览和阅读行为后,再来分析你的内容关键字。
最近头条对每一篇文章都增加了文章详细统计数据分析功能,如上图。
即可以看到你单篇文章的展现量,阅读量,粉丝阅读量,点击率,用户平均阅读时长。评论,点赞,收藏,转发等用户互动行为分析,同时还可以看到这些统计数据按天进行的时间趋势图分析。这些数据可以更好的方便创造者对自己创造的内容进行分析评估,并对创作内容进行持续优化和改进。任何一个自媒体创造者也是一个运营者,需要有数据驱动思维,基于数据分析来驱动自己的内容运营。
在前面我们已经谈到,头条更多是以内容驱动的推荐引擎模式。文章,微头条,视频等都是内容的呈现形式。而对于文章我们首先要分析行为事件和统计计算间的关系。
影响文章统计计数的关键行为或事件
影响文章统计计数的关键行为或事件,主要包括了如下内容。
以上即是我们实际在文章详细统计数据里面看到的关键信息。同时我们看到,对应每篇文章还提供了统计数据的时间趋势图,可以详细查询一个周期里面的统计数据信息。
对于文章推送,前面已经谈到基于推荐引擎机制结合冷启动的数据进行持续迭代文章推送。这个推送本身可能呈现为两种方式。
另外对于数据统计,我们可以看到应该是存在一个最小刷新间隔,比如是30秒或者1分钟,那么在这个间隔里面你可以看到展现量或阅读量并不会变化。
其次,对于点击率和阅读时长,你可以看到是当天只能够看到前一天的数据,即你可以理解为是非实时计算,可能是在凌晨计算昨天一天的最终统计数据结果,即按天增量计算。
整体实现思路初步思考
有了前面的初步思考,我们可以看到每篇文章都有一组最终的统计数据,这些统计数据可以存放到类似redis缓存库里面。如果数据有最小刷新间隔概念,那么实时的数据计数应该在统计在内存里面,然后再更新到Redis缓存库中。
如果以一天为一个完整的统计周期,那么Redis库应该只记录当天的数据,当天数据统计完成后,当天的数据应该写入到类似influxdb的时序数据库中,这种数据库来实现文章的时间趋势线分析是最合适的。
基于以上分析,以最小刷新间隔1分钟,我们给出一个很粗的实现思路图。
基于当前统计数据可以看到:
点击率 = 阅读量/展现量
那么我们再看下对应平均阅读时长如何处理。
当监测到用户退出事件的时候,我们就可以计算出文章的阅读总时长。
那么在1分钟的间隔内,我们的内存计算应该计算 阅读总时长,这个时候不用去计算平均阅读时长。包括到了Redis库也是同样道理,我们只需要将阅读总时长进行累加计数即可。
当需要计算上一天的阅读平均时长的时候,我们再进行计算,即:
当天阅读平均时长 = 阅读总时长/阅读次数
但是注意这是当天的阅读平均时长。而我们将每天最终的统计数据写入到类似influxDB时序数据库的时候,我应该计算一下文章截至到当前的总计数信息。
比如一篇文章已经发布了10天,那么我们应该有一个总计算信息,即:
第1到10天:文章ID,总推荐量A,总阅读量A,总阅读时长A,平均时长A
而第11天的数据我们统计后可以得到当天的统计计算为
第11天:文章ID,总推荐量B,阅读量B,总阅读时长B,平均时长B
在这个时候,我们不会对平均时长再进行平均,而是应该重新计算平均时长,即:
平均时长新 = (总阅读时长A+总阅读时长B)/(总推荐量A+总推荐量B)
另外,对应截止到上一天的汇总统计数据,这个数据应该更新到一个单独的数据库统计表中,这个信息可以放到结构化数据库中共数据展现时候使用,而不是查询的时候再去实时计算。即作者查询文章详细数据的时候,详细统计数据 = 当天Redis计数+文章截至到上一天的历史累计计数。
是否使用消息中间件
前面看到对应文章推送,用户浏览,交互等各种行为事件都会影响到文章计数,那么这些事件是否都需要统一写入到消息中间件,通过消息中间件进行削峰处理。
我个人理解是如果使用消息中间件的话可以进一步实现异步解耦,消息中间件可以替代内存计算过程,这个时候消息中间件直接对接到Redis库即可。
当然实际上头条最终的计算远比我上面初步思考复杂,在这里也仅仅给出一个简单实现思路供参考。特别是这种思路是否还存在进一步的线程安全,线程锁定等问题。在这里就不再做进一步的分析。