博客网站的文章推荐思路及其算法实现

该文章描述了一个博客网站如何利用用户的行为(如浏览、点赞、收藏、评论)来构建推荐系统。系统通过分析用户在不同时间段的行为权重,结合文章的标签,动态生成用户喜好权值表,从而实现个性化的文章推荐。推荐算法考虑了用户的行为频率和时间衰减因素,以提供更精准的推荐内容。
摘要由CSDN通过智能技术生成

        做了一个博客网站,基本功能就是实现用户查找文章,查看文章内容。后面想到增加实现能够根据用户的喜好程度不同来推荐不同的文章,希望看完能对你有所帮助。

        简述一下本网站实现推荐的过程是如何进行的。

        首先定义好推荐数据的依据,对文章数据库中设置keyWord字段,用于存储文章的话题或者标签,每个文章存储的话题有多个由发布文章时由作者设定,以下为数据库存储文章标签的字段。

f16385fae6be4c63b22c699eeffe4d2f.png

        其次设计一个喜好的程度的方案,用户对文章在不同时期对文章做的不同操作表现出用户对该类型的文章喜好程序不一样,例如用户对文章的浏览、点赞、收藏、评论的行为加分权值为0.1、0.3、0.5、0.3,评论的加分权值可根据用户的内容来评判这里不作为详细处理统计评论就是加0.3分。由于用户存在喜新厌旧的情况,因此在收集用户的行为记录时需要按时间收集并且时间越长的行为权值越低,比如一个月以内的点赞权值为0.3*1,一个月后的点赞权值为0.3*0.6.

        建立用户喜好分析模型UserAnalysisModel,该模型用于分析用户的某种(点赞评论等)行为的喜好偏好表,key为喜好标签名,value为权重值

6b9245813ed2414a9b242714403bb362.png


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.yiqingxuan_article.content.bean.Article;//自定义实体类
import com.example.yiqingxuan_article.content.dao.ArticleMapper;//自定义DAO类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Author: 曾敏
 * @Description: 用户喜好分析模型
 * @DateTime: 2023/5/26 13:36
 **/
@Service
public class UserAnalysisModel {
    //Mapper层,用于从数据库中提取文章内容
    @Autowired
    private ArticleMapper articleMapper;
    //redis
    @Autowired
    RedisTemplate redisTemplate;

    public Map<String,Double> WeightTable;
    private List<Integer> listone=new ArrayList<>();//用户第一个月的行为记录列表
    private List<Integer> listtow=new ArrayList<>();//用户第二个月的行为记录列表
    private List<Integer> listtree=new ArrayList<>();//用户第三个月及其以后的行为记录列表
    List<Integer> listsum;//三个时间节点的文章id总和
    List<Article> listarticles;//存放用户的浏览记录文章
    Map<Integer,List<String>> id_keymap;//存放文章号代表的关键字对照表

    private Integer userid;
    private double Weight;//当前分享的行为权值表
    private double ONE_MONTH=1;
    private double TOW_MONTH=0.6;
    private double TREE_MONTH=0.4;


    public Map<String,Double> execute(Integer uid,String type){
        if (type.equals("点赞")){
            Weight=0.5;
        }else if (type.equals("收藏")){
            Weight=0.3;
        }else if (type.equals("浏览")){
            Weight=0.1;
        }else if (type.equals("评论")){
            Weight=0.3;
        }else {
            return null;
        }
        userid=uid;
        //开始业务分析
        System.out.println("当前行为分析:"+type);
        GetMonthLists();
        GetArticleAndKeyTable();
        createWeightTable();
        Calculate();
        return WeightTable;
    }
    /**
     * 将三时期各种行为记录的set集合存入列表
     */
    private void GetMonthLists(){
        String key;
        if (Weight==0.5){
            key="user_like_";
        }else if (Weight==0.4){
            key="user_store_";
        }else if (Weight==0.3){
            key="user_comment_";
        }else if (Weight==0.1){
            key="user_read_";
        }else {
            return;
        }
        //一个月内
        Set<String> membersone= (Set<String>) redisTemplate.opsForZSet().rangeByScore(key+userid, GetCurrentTimestamp(1), GetCurrentTimestamp(0)).stream().map(Object::toString).collect(Collectors.toSet());
        //一月到二月间
        Set<String> memberstwo= (Set<String>) redisTemplate.opsForZSet().rangeByScore(key+userid, GetCurrentTimestamp(2), GetCurrentTimestamp(1)).stream().map(Object::toString).collect(Collectors.toSet());
        //二月后
        Set<String> memberstree= (Set<String>) redisTemplate.opsForZSet().rangeByScore(key+userid, Double.MIN_VALUE, GetCurrentTimestamp(2)).stream().map(Object::toString).collect(Collectors.toSet());
        System.out.println("第一时期的"+Weight+"记录:"+membersone);
        if (membersone.size()>0||membersone!=null){
            for (String s:membersone){
                if (Weight==0.1||Weight==0.3){
                    String [] parms=s.split("_");
                    String result=parms[0];
                    listone.add(Integer.valueOf(result));
                }else {
                    listone.add(Integer.valueOf(s));
                }
            }
            while (listone.size() > 500) {//将数据采集量限制大小
                Random random = new Random();
                int indexToRemove = random.nextInt(listone.size());
                listone.remove(indexToRemove);
            }
        }//将set集合存入列表中
        if (membersone.size()>0||membersone!=null){
            for (String s:memberstwo){
                if (Weight==0.1||Weight==0.3){
                    String [] parms=s.split("_");
                    String result=parms[0];
                    listone.add(Integer.valueOf(result));
                }else {
                    listone.add(Integer.valueOf(s));
                }
            }
            while (listtow.size() > 400) {
                Random random = new Random();
                int indexToRemove = random.nextInt(listtow.size());
                listtow.remove(indexToRemove);
            }
        }
        if (membersone.size()>0||membersone!=null){
            for (String s:memberstree){
                if (Weight==0.1||Weight==0.3){
                    String [] parms=s.split("_");
                    String result=parms[0];
                    listone.add(Integer.valueOf(result));
                }else {
                    listone.add(Integer.valueOf(s));
                }
            }
            while (listtree.size() > 300) {
                Random random = new Random();
                int indexToRemove = random.nextInt(listtree.size());
                listtree.remove(indexToRemove);
            }
        }
        // 合并列表,只合并非空列表
        System.out.println("三个时期的表记录值:"+listone+"==="+listtow+"==="+listtree);
        listsum = Stream.of(listone, listtow, listtree)
                .filter(list -> !list.isEmpty())
                .flatMap(list -> list.stream())
                .collect(Collectors.toList());

    }

    /**
     * 得到文章号与其关键字的对照表
     */
    private void GetArticleAndKeyTable(){
        System.out.println(listsum);
        QueryWrapper<Article> wrapper = new QueryWrapper<>();
        wrapper.select("id", "keyWords").in("id", listsum);
        listarticles=articleMapper.selectList(wrapper);//获取结果集合
        System.out.println("用户看过的文章记录表:"+listarticles);
        Map<Integer,List<String>> map1=new HashMap<>();
        for (Article article:listarticles){
            String keyWord=article.getKeywords();
            String [] keyWords=keyWord.split("#");
            int n=keyWords.length;
            String first = keyWords[0];
            for (int i=1;i<n;i++) {
                keyWords[i-1]=keyWords[i];
            }
            keyWords[n-1]=first;
            // 移除最后一个元素
            String[] newArray=new String[n-1];
            for (int i=0;i<n-1;i++) {
                newArray[i]=keyWords[i];
            }
            keyWords = newArray; // 更新原数组
            List<String> list = Arrays.asList(keyWords);
            map1.put(article.getId(),list);
        }//得到文章号与其关键字的对照表
        System.out.println(map1.size());
        id_keymap=map1;
    }

    /**
     * 初始化用户喜好权值表,获取用户浏览过的标签生成map
     */
    private void createWeightTable(){
        List<String> listkeysum=new ArrayList<>();//当前用户的所有喜好集合
        Set<Integer> set=id_keymap.keySet();
        for (Integer element : set) {
            List<String> list=id_keymap.get(element);//得到当前文章号的标签集合
            Set<String> tempset=new HashSet<String>();
            tempset.addAll(listkeysum);
            tempset.addAll(list);
            listkeysum=new ArrayList<>(tempset);
        }
        Map<String,Double> map1=new HashMap<>();
        for (String s:listkeysum){
            map1.put(s,0.0);
        }
        WeightTable=map1;
    }

    /**
     * 为用户权值表计算
     */
    private  void Calculate(){
        if (listone.size()>0){
            System.out.println("用户浏览过的文章关键字合集:"+id_keymap);
            for (Integer i:listone){//一个月内的计算
                List<String> listKeys=id_keymap.get(i);//得到一个关键字集合
                for (String s:listKeys){//
                    Double initial=WeightTable.get(s);
                    Double newvalue=initial+(Weight*ONE_MONTH);
                    WeightTable.put(s,newvalue);
                }
            }
        }
        if (listtow.size()>0){
            for (Integer i:listtow){//一个月内的计算
                List<String> listKeys=id_keymap.get(i);//得到一个关键字集合
                for (String s:listKeys){//
                    Double initial=WeightTable.get(s);
                    Double newvalue=initial+(Weight*TOW_MONTH);
                    WeightTable.put(s,newvalue);
                }
            }
        }
        if (listtree.size()>0){
            for (Integer i:listtree){//一个月内的计算
                List<String> listKeys=id_keymap.get(i);//得到一个关键字集合
                for (String s:listKeys){//
                    Double initial=WeightTable.get(s);
                    Double newvalue=initial+(Weight*TREE_MONTH);
                    WeightTable.put(s,newvalue);
                }
            }
        }
        System.out.println("权值表:"+WeightTable);
    }
    /**
     * 输入获取的月份返回时间戳
     * @param i
     * @return
     */
    public static long GetCurrentTimestamp(int i){
        // 获取当前时间戳
        long currentTimestamp = System.currentTimeMillis();
        // 获取一个月前的时间戳
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date(currentTimestamp));
        calendar.add(Calendar.MONTH, -1); // 减去一个月
        long oneMonthAgoTimestamp = calendar.getTimeInMillis();
        // 获取两个月前的时间戳
        calendar.add(Calendar.MONTH, -1); // 再减去一个月
        long twoMonthsAgoTimestamp = calendar.getTimeInMillis();
        if (i==0){
            return currentTimestamp;
        }else if (i==1){
            return oneMonthAgoTimestamp;
        }else if (i==2){
            return twoMonthsAgoTimestamp;
        }else {
            return 0;
        }
    }

}

 以上代码用于获取用户某个行为喜好权重表,具体推荐由以下代码进行

package com.example.yiqingxuan_article.content.service;

import com.example.yiqingxuan_article.content.bean.Article;
import com.example.yiqingxuan_article.content.dao.ArticleMapper;
import com.example.yiqingxuan_article.content.dao.CategoryMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

import static jdk.nashorn.internal.objects.Global.Infinity;

/**
 * @Author: 曾敏
 * @Description: TODO
 * @DateTime: 2023/5/26 1:21
 **/
@Service
public class Recommend {
    @Autowired
    private ArticleMapper articleMapper;
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    private CategoryMapper categoryMapper;

    @Autowired
    private UserAnalysisModel UserLike1;
    @Autowired
    private UserAnalysisModel UserLike2;
    @Autowired
    private UserAnalysisModel UserLike3;
    @Autowired
    private UserAnalysisModel UserLike4;

    /**
     * 获取用户喜好列表内容
     * @param userid
     * @return
     */
    public List<String> GetUserLike(Integer userid){
        Map<String,Double> readmap=UserLike1.execute(userid,"浏览");
        Map<String,Double> givemap=UserLike2.execute(userid,"点赞");
        Map<String,Double> collecttmap=UserLike3.execute(userid,"收藏");
        Map<String,Double> commentmap=UserLike4.execute(userid,"评论");
        List<Map<String, Double>> maps=new ArrayList<>();
        maps.add(readmap);
        maps.add(givemap);
        maps.add(collecttmap);
        maps.add(commentmap);
        Map<String, Double> mergeMaps=mergeMaps(maps);
        List<Map.Entry<String, Double>> entries = new ArrayList<>(mergeMaps.entrySet());
        // 按Map中的值排序
        Collections.sort(entries, new Comparator<Map.Entry<String, Double>>() {
            public int compare(Map.Entry<String, Double> o1, Map.Entry<String, Double> o2) {
                return o2.getValue().compareTo(o1.getValue());
            }
        });
        List<String> listKeyWords=new ArrayList<>();
        // 打印四个最大值的键
        for (int i = 0; i < entries.size(); i++) {
            if (i==4){
                break;
            }
            listKeyWords.add(entries.get(i).getKey());
        }
        return listKeyWords;
    }

    /**
     * 根据用户偏好标签查询文章,并通过协同过滤返回推荐文章的id号
     * @param list
     * @param userid
     * @return
     */
    public List<Integer> itemFilter(List<String> list,Integer userid){
        //根据list中的关键字数据查询到文章集合
        System.out.println("根据list中的关键字数据查询到文章集合:"+articleMapper.selectByKeywords(list));
        List<Article> articleList=articleMapper.selectByKeywords(list);
        while (articleList.size() > 100) {//将数据采集量限制大小
            Random random = new Random();
            int indexToRemove = random.nextInt(articleList.size());
            articleList.remove(indexToRemove);
        }
        Set<String> memberstree= (Set<String>) redisTemplate.opsForZSet().rangeByScore("user_read_"+userid, Double.MIN_VALUE, System.currentTimeMillis()).stream().map(Object::toString).collect(Collectors.toSet());
        List<String> readlists=new ArrayList<>();//用户抽取的部分浏览记录,含文章号浏览的id
        if (memberstree.size()>0||memberstree!=null){
            List<String> templist = new ArrayList<>(memberstree);
            templist=parmeruni(templist);
            System.out.println(templist);
            while (templist.size() > 100) {//将数据采集量限制大小
                Random random = new Random();
                int indexToRemove = random.nextInt(templist.size());
                templist.remove(indexToRemove);
            }
            readlists=templist;
        }//将set集合存入列表中
        Double [][] matrix = new Double[readlists.size()][articleList.size()];
        for (int i=0;i<readlists.size();i++){
            for (int j=0;j<articleList.size();j++){
                matrix[i][j]=0.0;
            }
        }
        int i=0,j=0;
        for (String s:readlists){
            i=0;
            //小表中的某个文章
            Set<String> s2= (Set<String>) redisTemplate.opsForZSet().rangeByScore("article_read_"+s, Double.MIN_VALUE,System.currentTimeMillis()).stream().map(Object::toString).collect(Collectors.toSet());
            List<String> l2=new ArrayList<>(s2);
            l2=parmeruni(l2);//得到浏览过该文章的用户id列表,用户浏览记录
            for (Article article:articleList){
                //大表中的某个文章
                Set<String> s1= (Set<String>) redisTemplate.opsForZSet().rangeByScore("article_read_"+article.getId(), Double.MIN_VALUE,System.currentTimeMillis()).stream().map(Object::toString).collect(Collectors.toSet());//
                List<String> l1=new ArrayList<>(s1);
                l1=parmeruni(l1);//得到浏览过该文章的用户id列表,大表记录
                // 计算交集大小
                Set<String> intersection = new HashSet<>(l1);
                intersection.retainAll(l2);
                Double intersectionSize = Double.valueOf(intersection.size());
                // 计算并集大小
                Set<String> union = new HashSet<>(l1);
                union.addAll(l2);
                Double unionSize = Double.valueOf(union.size());
                Double weight;
                if (unionSize==0){
                    weight=0.0;
                }else {
                    weight=unionSize/intersectionSize;
                }
                if (weight==Infinity){
                    weight=0.01;
                }
                matrix[j][i]=weight;
                i++;
            }
            j++;
        }
        //显示矩阵
        for (int q = 0; q < matrix.length; q++) {
            for (int w = 0; w < matrix[q].length; w++) {
                System.out.print(matrix[q][w] + " ");
            }
            System.out.println(); // 换行
        }
        //矩阵计算前十的坐标
        List<Integer> coords=findTopTenCoordinates(matrix);
        List<Integer> listid=new ArrayList<>();
        for (Integer integer:coords){
            Integer artid=articleList.get(integer).getId();
            listid.add(artid);
        }
        //list1列表中存储着推荐的文章号id
        System.out.println("推荐的文章号id集合:"+listid);
        return listid;
    }

    /**
     * 传入用户id返回推荐文章列表
     * @param userid
     * @return
     */
    public List<Article> SelectRecommend(Integer userid){
        List<String> list=GetUserLike(userid);
        if (list==null||list.size()==0){
            return null;
        }
        List<Integer> ArticleidList=itemFilter(list,userid);
        List<Article> articleList=articleMapper.selectByArticleIds(ArticleidList);
        for (Article article:articleList){
            Integer commenttotal=getArticleComments(article.getId());
            if (commenttotal==null){
                article.setCommentTotal(0);
            }else {
                article.setCommentTotal(commenttotal);
            }
            article.setContent(null);
            article.setName(categoryMapper.selectById(article.getCategoryid()).getName());
            article.setReadcnt((int) GetLookArticle(article.getId()));
            article.setAgreecnt((int) GetGiveArticle(article.getId()));
        }
        return articleList;//返回推荐结果
    }

    /**
     * 将含有后缀的列表规整,并元素唯一
     * @param list
     * @return
     */
    public List<String> parmeruni(List<String> list ){
        int i=0;
        for (String s:list){
            String [] parms=s.split("_");
            String result=parms[0];
            list.set(i,result);
            i++;
        }
        Set<String> set=new HashSet<>(list);
        List<String> temp = new ArrayList<>(set);
        return temp;
    }

    public static Map<String, Double> mergeMaps(List<Map<String, Double>> maps) {
        Map<String, Double> result = new HashMap<>();
        for (Map<String, Double> map : maps) {
            for (Map.Entry<String, Double> entry : map.entrySet()) {
                String key = entry.getKey();
                Double value = entry.getValue();
                result.merge(key, value, Double::sum);
            }
        }
        return result;
    }



    /**
     * 传入一个二维矩阵,获得纵轴坐标集
     * @param matrix
     * @return
     */
    public static List<Integer> findTopTenCoordinates(Double[][] matrix) {
        List<Integer> coordinates = new ArrayList<>();
        TreeSet<Double> uniqueValues = new TreeSet<>(Collections.reverseOrder());

        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                uniqueValues.add(matrix[i][j]);
            }
        }

        int count = 0;
        for (double value : uniqueValues) {
            for (int i = 0; i < matrix.length; i++) {
                for (int j = 0; j < matrix[i].length; j++) {
                    if (matrix[i][j] == value) {
                        if (!coordinates.contains(j)) {
                            coordinates.add(j);
                            coordinates.add(i);
                            count++;
                        }
                    }
                    if (count == 10) {
                        Set<Integer> set=new HashSet<>(coordinates);
                        List<Integer> temp = new ArrayList<>(set);
                        while (temp.size() > 10) {//将数据采集量限制大小
                            Random random = new Random();
                            int indexToRemove = random.nextInt(temp.size());
                            temp.remove(indexToRemove);
                        }
                        return temp;
                    }
                }
            }
        }
        Set<Integer> set=new HashSet<>(coordinates);
        List<Integer> temp = new ArrayList<>(set);
        while (temp.size() > 10) {//将数据采集量限制大小
            Random random = new Random();
            int indexToRemove = random.nextInt(temp.size());
            temp.remove(indexToRemove);
        }
        return temp;
    }
    /**
     * 获取某个文章的评论量
     */
    public Integer getArticleComments(Integer id){
        return articleMapper.selectRanking(id);
    }
    public long GetLookArticle(Integer artid) {
        Long zcard= Long.valueOf(redisTemplate.opsForSet().members("article_show_"+artid).size());
        return zcard;
    }

    /**
     * 获取到文章的点赞量
     * @param artid
     * @return
     */
    public long GetGiveArticle(Integer artid) {
        Long zcard= Long.valueOf(redisTemplate.opsForSet().members("article_give_"+artid).size());
        return zcard;
    }
}

在控制层使用样式

@RestController
@RequestMapping("/selectarticle")
public class ArticleSelectControll {    
     @Autowired
    private Recommend recommend;


    /**
     * 传入用户id,返回推荐文章列表
     * @param userid
     * @return
     */
    @GetMapping("/recommend")
    public jsonModel Recommend(@RequestParam Integer userid){
        jsonModel jsonModel=new jsonModel();
        System.out.println("推荐查询:"+userid);
        List<Article> list=recommend.SelectRecommend(userid);
        System.out.println(list);
        if (list.size()>0&&list!=null){
            jsonModel.setData(list);
            jsonModel.setCode(1);
        }else {
            jsonModel.setMsg("推荐失败");
            jsonModel.setCode(0);
        }
        return jsonModel;
    }
}

使用推荐算法时,后台算法细节

c0e05129073b45b0aebd6aa9165b335e.png

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邻家小妹妹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值