SegmentFault 热门内容优化

47

之前 SegmentFault 的热门内容算法没有考虑到时间因素,导致热门内容更新较慢,新的热门内容排不上位,所以最近对这块做了一些改动,主要参考了阮一峰老师的关于网站热门内容排序的一系列博客:

这几篇文章对于网站热门内容的计算研究有很好的指导作用,非常值得拜读。

言归正传,来说说 SegmentFault 的热门算法。

热门文章

对于热门文章,使用了如下公式:

$$ \frac{lg(views) * 4 + recommendScore + collectScore + ln(articleComments)}{(age/2 + update/2 + 1) ^ i}
$$

其中

  • views:浏览量,对浏览量做了一次去对数处理,主要是为了防止某些浏览量较大的文章异军突起,待在榜单迟迟不动。

  • recommendScore / collectScore:文章的推荐数和收藏数,直接加和到分子中,作为文章热门程度的考虑因素。

  • articleComments:文章评论数,这个也作为一个影响文章热度的因素,不过为了降低其影响,对其作了一次取对数操作,主要是考虑到评论数量的影响力并没有上面两个的高。

  • (age/2 + update/2 + 1) ^ i:分母是对时间因子的考虑,宏观上来看,就是文章热度和创建时间成反比。细节上,做成了个指数函数,可以通过对 i 变量的调控来改变时间因子在对热度的影响。

    • age:内容发布时间

    • update:内容最后更新时间(所有时间值单位均为 h/3600)

    • i:重力因子,取值的大小会直接决定热门排序(后面将介绍这点)

热门问答

对于热门问答,使用了如下公式:

$$
\frac{lg(views) * 4 + sum(Qanswers * anwserScores + Qscore) / 5 + ln(commentSocres)}{(age/2 + update/2 + 1) ^ i}
$$

热门问答的计算参考了 Stack Overflow 对于回答数量和问题得票数的处理。同时,结合我们的实际,将评论的得票数也做为一个因素加入计算。

  • Qanswers / Qscore:分别是问题的答案数量和问题的得票数

  • anwserScores / commentSocres:分别是该问题下所有答案的总得票数和所有评论的总得票数

  • update:该问题下答案的最新更新时间

其余的变量含义和文章算法相同。

日/周/月热门如何区别?

首先要明确各类不同热门内容的目的。

  • 日热门的主要目的就是突出最近一天内的热门内容,更方便于内容被大家看到,文章快速地形成讨论、受关注的问题尽快得到解决;

  • 周热门的主要目的很明确,就是突出过去一周内的热门内容,同时,给新产生的优秀内容机会,让其有机会进入热门列表;

  • 月热门同周热门目的一样,但更需要给新内容进入列表的机会,以让内容经常更新。

所以,该怎么做呢?

对于同一内容,上面的计算公式均可化简为

$$ \frac{s}{t^i} (s 是总得分指标,t 为时间量,i 是变量重力因子) $$

可以看出,其热度和创建时间成反比,那么这个反比的值最终就由重力因子 i 来影响。

日热门为了突出新热内容、过滤时间过久的热门内容,需要增大重力因子,尽可能排除 24 小时之外的热门内容;周热门和月热门则需要按时间要求依次逐渐降小 i 值。

关于指数 i 值的选定,采取了估算:绘制出一定范围内时间和文章热度的指数函数的图,然后根据需求挑选满足自己条件的指数值。如下图

图片描述

多次估值测试,最终分别将日、周、月的 i 值选取为 1.0、0.5、0.3。

总结

开始修改热门算法的时候,并没有收到计算周热门、月热门的需求,所以直接很暴力的将时间因子加入进去,这样的确能够满足需求,可以保证热门的内容随着时间的推移保持更新,而不会出现之前的某几篇文章的热度一直保持恒定,稳居榜首。

后来需要计算区间内的热门内容时,仍然使用开始的方案,因为时间因子的关系,会导致区间内的热门内容受时间的影响太大,出现的问题就是周热门、月热门榜首的内容依然是最新产生的内容占比重较多,所以才考虑对分母的时间因子做了动态调节的方案,也就是将分母的指数根据计算不同的区间,给出不同的值(但未找到更合适的取值方法,只能多次估值)。

我们目前的算法仍有需要完善之处,重力因子 i 的取值依旧是难点,大家如果有更好的方法,欢迎讨论。


如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

20 条评论
杨佰 · 2016年01月06日

又是阮一峰。。。

+3 回复

Noodles 作者 · 2016年01月11日

这个公式不难,结合产品实际情况,把所有影响因素都考虑进去即可。重点在于倍数的设计,比如 lg(view) * 4 中为什么数值是 4、Qanswers ∗ Qscore / 5 中的 5,以及 i 的取值,这些数值实际是与网站整体的内容活跃数据相关联的,理论上来说,应该是随着网站活跃情况不断变动的。

+2 回复

Ferguson · 2016年01月07日

厉害!

+1 回复

rainbow702 · 2016年01月11日

不管是Stack Overflow,还是SF,我好奇的是如何设计出这个公式来的?有什么数学理论作为指导么?

+1 回复

leveychen · 2016年01月07日

不明所以,

回复

返照回林 · 2016年01月07日

不明觉厉!

回复

有明 · 2016年01月07日

喜欢搞学问不蛮干的程序猿儿

回复

kumfo · 2016年01月12日

话说你写这个你看得懂吗?

回复

rainbow702 · 2016年01月12日

其实,直接看那个公式是看不懂的,但是结合下面的注释看的话,还是能够理解

回复

kumfo · 2016年01月12日

看阮一峰的那个代码就能看得懂,我在大二的时候做过类似的算法,但是那个算法太复杂了,一万条数据运算起来需要三个小时,太扯淡了。

回复

Tairy · 2016年01月14日

没这么慢吧,也不是很复杂的计算,我觉得耗时还是在数据库的查询上

回复

Tairy · 2016年01月14日

开始我也是瞎搞的,后来看了牛顿冷却定律那一篇之后,觉得这玩意还是有点道理的,世间万物都能用物理定律解释,只是有些我们还没发现。

回复

rrttyyui · 2016年02月03日

这个用户交互是怎么实现的呢,是定时处理热门列表么,然后写到缓存里,用户查看的时候通过缓存的文章id,在从数据库拿文章么?

回复

Tairy · 2016年02月03日

嗯 定时脚本计算,把文章/问题 id 写入redis, 然后应用代码读取redis,再读取数据库(优先读取缓存中的内容)。

回复

rrttyyui · 2016年02月03日

@Tairy 那么你们一般多长时间执行一次脚本呢

回复

Tairy · 2016年02月03日

现在设置的是半个小时

回复

mikale · 2016年03月11日

重力因子,我感觉可以根据用户参与频率来决定。3/((post/2+answer/2)*t),post是过去一小时的发帖量,answer是过去一小时的回答量(或者评论量),t代表日、周、月,是三个常量。

回复

Luff · 2016年04月12日

求问,热门文章和热门回答,怎么转化为php的代码?

回复

dream · 2016年12月23日

好复杂

回复

yuistack · 2018年01月03日

(所有时间值单位均为 h/3600)

所以到底 update 的单位是 h(小时),还是 h / 3600 呢?

回复

载入中...