创新实训(11)——推荐系统实现之基于流行度和新鲜度的推荐

后台可以记录每篇博客的阅读次数,直接简单粗暴的推荐比较流行的博客即可,可能热门的博客会被很多人所喜欢。
问题:可能产生长尾效应,阅读数多的博客可能就有限的几篇,而大量的博客无人问津。
解决方案:将阅读次数与新鲜度结合,找到一个比较适合的排名方式,一篇刚发布的博客,有较大的新鲜度,可以将其排到前面。

算法的设计

(1)在抽取博客时,会抽取到文章发布的时间,我们可以将这个发布时间转化为时间戳,这个时间戳就可以作为一篇文章基础的评分,时间戳是随着时间不断增加的,新发布的博客会有比较大的时间戳,这样就保证了新来的博客的分值比较大。
(2)在时间戳的基础上,我们可以引入点击量,作为博客评分的另一个部分,我们给每一个点击量增加一个权重K,当博客被点击一次后,评分会+K(后期可以引入收藏,多一个收藏,评分还可以继续增加)。这样可以保证热度比较高的博客的分数也比较高。
(3)为了提高系统的响应时间,我考虑使用了redis的zset来进行数据的缓存,zset是一个根据score进行排序的集合,我们正好可以根据文章的评分让zset对博客的顺序自动的进行排序,我们取数据时,是需要顺序读取即可。

具体zset的操作
在这里插入图片描述

当每篇文章进入系统时,根据时间戳算出他的基础分数,加入zset中,当用户点击文章时,修改这篇文章的分数,将分数加K,当删除一篇文章时,将这篇文章从zset中删除
(4)当需要获取博客列表时,只需要根据range顺序的从redis中取数据即可,zset提供了方法可以取到排名从start到end的博客数据,所以很简单的就可以实现博客的分页查询。

具体实现

  1. 配置redis环境,在springboot项目中实现对redis的存取。

配置文件:

spring:
  redis:
      host: r-bp1943b4a5673ef4pd.redis.rds.aliyuncs.com
      port: 6379
      password: xxxx

有关redis存取方法的实现:
spring data for redis 提供了RedisTemplate类,实现对redis数据库的快捷存取

    /**
     * redis zset添加元素  如果元素存在则会用新的score替换原来的
     * @param key
     * @param value
     * @param score
     * @return
     */
    public boolean zsetAdd(String key,String value,double score)
    {
        boolean result = false;
        try {
            redisTemplate.opsForZSet().add(key, value,score);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 删除元素 zset
     *
     * @param key
     * @param value
     */
    public void zsetRemove(String key, String value) {
        redisTemplate.opsForZSet().remove(key, value);
    }


    /**
     * 值增加score
     * @param key
     * @param value
     * @param score
     * @return
     */
    public Double zsetIncrScore(String key, String value, double score) {
        return redisTemplate.opsForZSet().incrementScore(key, value, score);
    }

    /**
     * 获取value对应的score
     * 这个需要注意的是,当value在集合中时,返回其score;如果不在,则返回null
     * @param key
     * @param value
     * @return
     */
    public Double score(String key, String value) {
        return redisTemplate.opsForZSet().score(key, value);
    }

    /**
     * 查询集合中指定顺序的值  zrevrange 返回有序的集合中,score大的在前面
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<String> zsetRevRange(String key, int start, int end) {
        return redisTemplate.opsForZSet().reverseRange(key, start, end);
    }

2.推荐算法的设计
具体的解释都在代码里完整的注释了出来

@Service
public class FreshPopularRecommended {

    @Resource
    private RedisUtils redisUtils;



    @Resource
    private ArticleDao articleDao;


    //一次点击率可以增加的得分
    private static int K = 2000;

    private static String ZSET_KEY = "article";


    /**
     * 根据流行度和新鲜度获取博客的列表   注意返回值为articleId组成的list
     * @param start  开始位置
     * @param end  结束位置  注意与数据库的limit offset不同
      * @return
     */
    public List<Integer> getArticleByFreshPopular(int start,int end)
    {
        Set<String> a = redisUtils.zsetRevRange(ZSET_KEY,start,end);

        List<Integer> result = new ArrayList<>();
        for(String str : a)
        {
            result.add(Integer.valueOf(str));
        }
        return result;
    }
    /**
     *   结合博客更新时间的时间戳和博客的点击量给 给每一个博客一个分数
     *   key为固定的 article  value为每个文章的id  score为文章的得分
     * @param article  新插入的博客
     * @throws ParseException
     */
    public void setScore(Article article) throws ParseException {

            String updateTime = article.getUpdateTime();
            int ratingCount  = article.getRatingCount();
            SimpleDateFormat formatter = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss");
            Date date = formatter.parse(updateTime);
            //updateTime的时间戳  时间戳是得分的基础
            double timeStamp = date.getTime();
            //给某个article设置一个得分
            redisUtils.zsetAdd(ZSET_KEY,String.valueOf(article.getId()),timeStamp + ratingCount *K);



    }
    /**
     *当一个博客被点击后  点击量会+1 得分会增加K
     * @param articleId
     */
    public void addScore(int articleId)
    {
        redisUtils.zsetIncrScore(ZSET_KEY,String.valueOf(articleId),K);
    }
    /**
     * 当一个文章 被删除时  需要在redis删除它对应的分数
     * @param articleId
     */
    public void deleteArticle(int articleId)
    {
        redisUtils.zsetRemove(ZSET_KEY,String.valueOf(articleId));
    }
}

getArticleByFreshPopular方法可以根据范围,出去博客的id,最终返回到界面上。

3.给负责后端的同学一个方便调用的接口
他是需要调用这个方法,输入参数,就可以返回对应的articleId的列表了

  /**
     * 根据流行度和新鲜度获取博客的列表  可以进行分页查询(注意与 数据库的limit和offset不同 需要转化一下)
     * @param start   开始位置   start = offset
     * @param end   结束位置     end = offset+limit
     * @return   返回值为articleId组成的list
     */
    public  List<Integer> recommendArticleByFreshPopular(int start,int end)
    {
        return freshPopularRecommended.getArticleByFreshPopular(start,end);
    }

总结

基于流行度和新鲜度的推荐是比较基础的显示方法,所有的用户看到的内容都是相同的,接下来可能会个性化推荐的内容

猜你喜欢

转载自blog.csdn.net/baidu_41871794/article/details/106723853