Dev.to Feed 算法 🤖
我以前开发过应用程序。现在还在做,但我以前也开发过。
从头开始
掌控
给我看看故事
好的,首先给我看精选故事
现在给我展示剩下的故事吗?
这些不是您要找的 div
没人预料到西班牙宗教裁判所
新的故事不是旧的故事
其余的呢?
TL;DR
结束语
能够回溯帖子 #3455
threadkiller 谢谢!
我以前开发过应用程序。现在还在做,但我以前也开发过。
早在2007/2008年,我学习了Ruby on Rails,并开发了两个原型网站,但最终未能投入生产。从那时起,我大量开发了非Ruby、非Rails的服务器应用程序,并学习了足够的Android和iOS应用程序知识,足以让我在目前的职位上管理移动应用程序的开发。
我再也没有接触过 Ruby on Rails...直到@anshbansal问了一个我之前问过自己几次的问题。
以下是我深入研究 dev.to 代码库以回答这个问题的过程。可能存在一些错误,请在评论中指出,以便我进行修改。谢谢。
从头开始
而且它不会比根路由早很多root "stories#index"
掌控
Rails 遵循模型视图控制器 (MVC) 架构。当你请求 dev.to 显示根页面时,它会请求 stories 控制器运行 index 操作。
我们看到的是它设置了一堆状态,然后渲染了文章/索引模板render template: "articles/index"
给我看看故事
如果你检查一下 dev.to 的主屏幕,你会发现所有文章/故事都列在一个articles-list
div 中。你可以在文章/索引视图中找到它。
从这里我们开始了解 feed 是如何填充的。
好的,首先给我看精选故事
文章列表中的第一个故事是特色故事。
获取已登录用户的精选故事的算法来自stories 控制器和articles/index视图。我通过替换一些变量和重新组织一些语句来简化它。
@stories = Article.published.limited_column_select.page(1).per(35)
@stories = @stories.
where("score > ? OR featured = ?", 9, true).
order("hotness_score DESC")
offset = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 2, 2, 2, 3, 3, 4,
5, 6, 7, 8, 9, 10, 11].sample # random offset, weighted more towards zero
@stories = @stories.offset(offset)
@featured_story = @stories.where.not(main_image: nil).first&.decorate || Article.new
用英语讲:
- 获取评分高于 9 分或精选的故事集
- 点餐时,先从“最辣”的开始
- 随机跳过前 0 到 11 个故事,权重更大,偏向于 0
- 特色故事是第一个有主图的故事
如何确定分数、特色和热度留给读者练习
请注意,特色文章与您关注的人物、组织或标签无关。
现在给我展示剩下的故事吗?
渲染特色故事后,文章/索引视图会创建一个substories
div,然后渲染 stories/main_stories_feed 部分<%= render "stories/main_stories_feed" %>
这些不是您要找的 div
我在阅读_main_stories_feed 部分内容时感到很困惑
new-articles-object
它填充了一个div 和一个div的 data 属性home-articles-object
,然后又填充了一堆没有内容的 div。我在检查主屏幕时看到的 div 确实有这个single-article single-article-small-pic
类,但看起来和这个文件中的内容不一样。
像这样的远程邪恶行动只能意味着一件事:JavaScript
没人预料到西班牙宗教裁判所
在 repo 中搜索new-articles-object
和home-articles-object
,我们发现它们都在initializeFetchFollowed Articles中,该函数在页面初始化时很早就被调用。
这里有很多我没有想到的逻辑。
新的故事不是旧的故事
stories 控制器填充了@stories
用于专题报道的集合。它也用于填充 div 的 data 属性home-articles-object
。不过那是接下来的事情,不是现在。
相反,我们在专题文章之后看到的第一个故事是通过视图中的查询直接填充的。
@new_stories = Article.published.
where("published_at > ? AND score > ?", rand(2..6).hours.ago, -15).
limited_column_select.
order("published_at DESC").
limit(rand(15..80))
用英语讲:
- 获取过去 2 到 6 小时内发布且评分高于 -15 的故事集
- 按最近的顺序排列
- 返回前 15 到 80 个
然后 JavaScript 函数insertNewArticles
接管:
articlesJSON.forEach(function(article){
var articlePoints = 0
var containsUserID = findOne([article.user_id], user.followed_user_ids || [])
var containsOrganizationID = findOne([article.organization_id], user.followed_organization_ids || [])
var intersectedTags = intersect_arrays(user.followed_tag_names, article.cached_tag_list_array)
var followedPoints = 1
var experienceDifference = Math.abs(article['experience_level_rating'] - user.experience_level || 5)
var containsPreferredLanguage = findOne([article.language || 'en'], user.preferred_languages_array || ['en']);
JSON.parse(user.followed_tags).map(function(tag) {
if (intersectedTags.includes(tag.name)) {
followedPoints = followedPoints + tag.points
}
})
articlePoints = articlePoints + (followedPoints*2) + article.positive_reactions_count
if (containsUserID || article.user_id === user.id) {
articlePoints = articlePoints + 16
}
if (containsOrganizationID) {
articlePoints = articlePoints + 16
}
if (containsPreferredLanguage) {
articlePoints = articlePoints + 1
} else {
articlePoints = articlePoints - 10
}
var rand = Math.random();
if (rand < 0.3) {
articlePoints = articlePoints + 3
} else if (rand < 0.6) {
articlePoints = articlePoints + 6
}
articlePoints = articlePoints - (experienceDifference/2);
article['points'] = articlePoints
});
var sortedArticles = articlesJSON.sort(function(a, b) {
return b.points - a.points;
});
sortedArticles.forEach(function(article){
var parent = insertPlace.parentNode;
if ( article.points > 12 && !document.getElementById("article-link-"+article.id) ) {
insertArticle(article,parent,insertPlace);
}
});
用英语讲:
- 给每篇文章 0 分
- 将用户关注的每个标签(也可以是负数)的权重相加,并将该权重乘以 2
- 现在再加上这篇文章目前收到的正面反应数量
- 如果用户关注文章作者,或者自己就是文章作者,则加 16 分
- 如果用户遵循文章的组织结构,则加 16 分
- 如果文章是用用户的语言写的,则加 1 分,否则减 10 分
- 随机(机会均等)给文章额外加 0、3 或 6 分。
- 减去本文经验水平与用户体验的差异的一半
- 按得分最高排序文章
- 如果文章内容超过 12 点,则向用户显示
其余的呢?
下一批初始化的文章与我们获取特色文章的批次相同,并由 中的新(但熟悉)算法处理insertTopArticles
。
当你浏览到列表底部时,文章会从有序文章的 algoliasearch 索引中填充出来。该索引的定义可以在 Article 模型中找到。
最后,您可以在initScrolling.js.erb中找到滚动功能,并从 algoliasearch 索引中填充更多文章。
将这些细节留给读者作为练习
TL;DR
对于列表中的第一篇文章:
- 获取评分高于 9 分或精选的故事集
- 点餐时,先从“最辣”的开始
- 随机跳过前 0 到 11 个故事,权重更大,偏向于 0
- 特色故事是第一个有主图的故事
对于下一批文章:
- 获取过去 2 到 6 小时内发布且评分高于 -15 的故事集
- 按最近的顺序排列
- 返回前 15 到 80 个
- 给每篇文章 0 分
- 将用户关注的每个标签(也可以是负数)的权重相加,并将该权重乘以 2
- 现在再加上这篇文章目前收到的正面反应数量
- 如果用户关注文章作者,或者自己就是文章作者,则加 16 分
- 如果用户遵循文章的组织结构,则加 16 分
- 如果文章是用用户的语言写的,则加 1 分,否则减 10 分
- 随机(机会均等)给文章额外加 0、3 或 6 分。
- 减去本文经验水平与用户体验的差异的一半
- 按得分最高排序文章
- 如果文章内容超过 12 点,则向用户显示
如果你已经浏览过所有这些内容,
- 使用与专题文章相同的集合
- 使用与上一批类似但不同的算法进行处理
最后
结束语
这种情况随时可能发生变化。例如,在 2019 年 9 月 19 日,@ben合并了一个 PR,旨在为主页提要添加更多变体。所有指向 GitHub 的链接都指向我master
在撰写本文时看到的那个提交,但当你读到这篇文章时,master
它可能已经移动了。