写博客的人都熟悉这个场景:文章底部放两篇"相关阅读",一开始手动挑两篇,后来文章多了就懒得维护,最后要么链到不相关的内容,要么指向404页面。一位开发者用纯PHP解决了这个问题——没有数据库,只有一个JSON文件。

他的博客所有内容都描述在一个posts.json文件里。每篇文章的slug、分类、标签都在里面。这些信息足够让程序自动算出哪些文章最相关。

打开网易新闻 查看精彩图片

硬编码链接的问题很典型。早期版本是给blog_footer()函数传一个数组,里面塞两个URL和标题。十篇文章还能管,五十篇就失控了。更麻烦的是,新发了文章也不会自动出现在相关推荐里,哪怕它明明更匹配。

posts.json的结构很简单。每篇文章一条记录,包含slug、标题、发布日期、分类、标签数组。这个文件本来就用在博客列表和站点地图生成上,顺手拿来做推荐也不费事。

算法设计直截了当。对每篇候选文章(排除当前这篇),按两个维度打分:共享标签每个加2分,标签是最精确的信号;同分类加1分,这是更宽泛的上下文线索。按分数降序排,同分取最新的,最后留前三。零分的直接过滤掉。

具体实现用了几十行PHP。先读JSON找到当前文章,提取它的标签和分类。然后遍历所有其他文章,array_intersect算标签交集个数乘2,分类相同再加1。结果存进数组,usort排序,分数高的在前,分数一样比日期。最后map一下输出URL和标题。

blog_footer()函数原本就接收$slug参数用来加载评论。现在加个逻辑:如果没传显式的相关文章列表,就自动调用上面的算法计算。老文章不用改,新文章自动生成推荐。

这个方案的限制很明显。标签质量决定推荐质量,如果作者打标签很随意,结果就乱。没有语义分析,"机器学习"和"深度学习"会被当成完全不同的东西,尽管人看来很相关。但对于个人博客,标签通常是认真打的,效果足够好。

更深层的问题是,为什么非得摆脱数据库?对一个小博客,MySQL是重量级的依赖。备份要管,迁移要管,版本升级要管。JSON文件直接用Git版本控制,部署就是一行scp。这种"足够好"的极简主义,在特定场景下比工业级方案更实用。

技术选型常陷入一种陷阱:用解决大厂问题的工具解决自己的问题。这位开发者的做法相反——先定义问题边界,再找最小可行的解法。相关文章推荐不需要协同过滤,不需要向量相似度,甚至不需要数据库。标签匹配加时间衰减,对个人博客已经够用。

代码里有个细节值得注意:排序时用了太空船运算符<=>,这是PHP 7引入的。按分数降序是$b <=> $a,同分比日期字符串用strcmp,也是降序。这种简洁的比较逻辑在旧版本PHP里要写好几行。

最终效果是什么?作者发新文章时,相关推荐自动更新。旧文章不用动,系统会找到新文章里最相关的那几篇。维护成本降到零,同时避免了硬编码的僵化。

这个案例的启示不在技术本身,而在问题定义。很多人看到"相关文章推荐"就想到机器学习、想到Elasticsearch、想到复杂的流水线。但回到本质,推荐的核心是"找到相似的东西"。如果标签体系足够干净,简单的加权匹配就能达到可用水平。复杂度和收益的平衡,是每个技术决策都要面对的。