博客推荐系统(适合初学者的javaweb+hadoop结课设计)

系列文章:
第一章 Hadoop集群搭建的准备
第二章 Hadoop集群搭建
第三章 Zookeeper分布式集群部署(2n+1台虚拟机)
第四章 Hadoop高可用集群搭建(HA)
待更新
第九章 spark独立模式部署(Standalone)
第十章 spark配置历史服务
第十一章 搭建Spark高可用(HA)
第十二章 spark配置Yarn模式(混合部署模式)
待更新


前言

  博客推荐系统是向用户推荐可能感兴趣的博客的系统。分为游客状态和登录状态(功能:展示最新的博客、推荐热度最高的博客、按分类推荐博客、登录注册、点赞收藏、修改个人资料、发表管理博客、管理收藏的博客以及针对用户喜好推荐博客。)。

效果预览

  • 游客状态访问时的状态
    在这里插入图片描述
    在这里插入图片描述
  • 游客状态可以注册账号并登录成为用户状态
    在这里插入图片描述
    在这里插入图片描述
  • 登录后是用户空间,用户可以修改自己的昵称、密码、联系方式等信息
    在这里插入图片描述
  • 发表博客
    在这里插入图片描述
  • 管理个人发表过的博客
    在这里插入图片描述
  • 管理收藏的博客
    在这里插入图片描述
  • 浏览系统针对用户喜好个性化推荐博客
    在这里插入图片描述
  • 用户状态的首页,右上角由之前的“登录”变为“我的”,点击“我的”即可进入用户空间
    在这里插入图片描述

一、设计环境

语言:Java、Shell、HTML、CSS、JavaScript、SQL
环境:jdk1.8.0、CentOS6.9、MySQL8.0.21、Apache Tomcat v8.5、Hadoop

二、设计思想

  先在Linux服务器上部署Apache Tomcat环境,并搭建Hadoop集群,然后上传JavaWeb项目以及MapReduce实现的推荐算法。最后编写推荐算法自动化脚本定时执行推荐算法,将结果上传到MySQL数据库中,整个项目的设计流程如图所示。请添加图片描述

三、推荐算法

1、推荐算法简述

  推荐算法使用了基于物品的协同过滤算法,该算法是基于物品与物品的关联程度以及用户对一些物品的评分来预测该用户对其它物品的评分,然后将预测评分较高的物品推荐给用户。
  课程设计中则是需要根据用户行为表中的点赞和收藏行为按照不同的权重计算用户对浏览过的博客的评分,然后将用户ID、博客ID以及用户对该博客的评分三种字段的数据采集到文本文件中,用作算法的原始输入数据。
  算法需要从原始数据中提取出博客的同现矩阵(即博客与博客之间的关联度,关联程度是同时对两个博客评过分数的总人数)以及每个用户的评分矩阵(即某用户对所有物品的评分),然后将两个矩阵相乘。
  算法借鉴了Thinkgamer的CSDN博客,通过他的博客我了解到了该算法如何通过MapReduce实现,并试着看懂代码后进行了复现。

2、MapReduse实现基于物品的协同过滤算法

  通过MapReduse实现该算法需要三部分,分别是生成同现矩阵C1、生成评分矩阵C2和C2_1以及矩阵计算C4和C4_1。
  第一步生成评分矩阵,输入是原始数据,输出为“<博客ID 用户ID:评分>”格式。

	/*
	 * <0             用户ID 博客ID 评分>
	 */
	//map阶段
	public static class  C1Mapper extends Mapper<LongWritable,Text,Text,Text>{
		static Text k1 = new Text(); //key
		static Text v1 = new Text();//value
		@Override
		protected void map(LongWritable key,Text value,Context context) 
				throws IOException, InterruptedException {
			String line=value.toString();
			String[] ones=line.split("\t");
			k1.set(ones[1]);
			v1.set(ones[0]+":"+ones[2]);
			context.write(k1,v1);
			System.out.println(k1+" "+v1);
		}
	}
	/*
	 * <博客ID           用户ID:评分>
	 */

在这里插入图片描述
  第二步生成博客的同现矩阵,该步骤由两组MapReduce程序实现,C2和C2_1。
  第一组的输入是原始数据,输出是“<用户ID 博客ID:博客ID;博客ID:博客ID;>”格式为初步数据处理格式。

	/*
	 * <0            用户ID 博客ID 评分>
	 */
	//map阶段
	public static class C2Mapper extends Mapper<LongWritable,Text,Text,Text>{
		static Text k1 = new Text();
		static Text v1 = new Text();
		protected void map(LongWritable key,Text value,Context context) throws IOException, InterruptedException {
			String line = value.toString();
			String[] list = line.split("\t");
			k1.set(list[0]);
			v1.set(list[1]);
			context.write(k1, v1);
			System.out.println(k1+":"+v1);
		}
	}
	/*
	 * <用户ID              博客ID>
	 */
	//reduce阶段,键相同的数据作为一个集合传入
	public static class C2Reducer extends Reducer<Text,Text,Text,Text>{
		static Text result = new Text();
		protected void reduce(Text key,Iterable<Text> values,Context context) throws IOException, InterruptedException {
			ArrayList<String> c2List = new ArrayList<String>();
			for (Text value:values) {
				String value1 = value.toString();
				c2List.add(value1);
			}
			String one = new String();
			while (true) {
				for (String c2one:c2List) {
					if (c2List.get(0)!=c2one)
						one+=c2List.get(0)+":"+c2one+";"+c2one+":"+c2List.get(0)+";";
					else
						one+=c2List.get(0)+":"+c2one+";";
				}
				if (c2List!=null)
					c2List.remove(0);
				
				if (c2List.size()==0)
					break;
			}
//			System.out.println(one);
			result.set(one);
			context.write(key, result);
			System.out.println(key+"\t"+result);
		}
	}
	/*
	 * <用户ID           博客ID:博客ID;博客ID:博客ID;博客ID:博客ID>
	 */

在这里插入图片描述
  第二组的输入是C2中同现矩阵初步处理结果,输出是“<博客ID:博客ID 同时对两个博客打过分的总人数>”格式,此为同现矩阵的最终处理结果。

	/*
	 * <0         用户ID 博客ID:博客ID;博客ID:博客ID;博客ID:博客ID>
	 */
	//map阶段
	public static class C2Mapper extends Mapper<LongWritable,Text,Text,Text>{
		static Text k1 = new Text();
		static Text v1 = new Text();
		protected void map(LongWritable key,Text value,Context context) throws IOException, InterruptedException {
			String line = value.toString();
			String[] list = line.split("\t");
//			System.out.println(list[0]+" "+list[1]);
			String[] list1 = list[1].split(";");
			for (String one:list1) {
				k1.set(one);
				v1.set("1");
//				System.out.println(one+"_"+v1);
				context.write(k1, v1);
				System.out.println(k1+" "+v1);
			}
		}
	}
	/*
	 * <博客ID:博客ID      1>
	 */
	//reduce阶段
	public static class C2Reducer extends Reducer<Text,Text,Text,IntWritable>{
		static Text result = new Text();
		protected void reduce(Text key,Iterable<Text> values,Context context) throws IOException, InterruptedException {
			int count = 0;
			for (@SuppressWarnings("unused") Text value:values) {
				count += 1;
			}
			context.write(key, new IntWritable(count));
			System.out.println(key+"\t"+new IntWritable(count));
		}
	}
	/*
	 * <博客ID:博客ID      同时对两个博客发生行为的总人数>
	 */

在这里插入图片描述

  第三步由两组MapReduce程序实现,C4和C4_1。
  第一组的输入是C1和C2_1的输出结果,输出是“<用户ID 推荐的博客ID,推荐分数>”格式为初步数据处理格式(C4的输出结果是<用户ID 推荐的博客ID,推荐分数>,这里的分数并不是最终的推荐分数,而是某一用户对各个博客的评分乘以被该用户评分的博客与被推荐博客的关联度。),需要做进一步的汇总。

	public static class C4Mapper extends Mapper< LongWritable, Text, Text, Text>{

		String filename;
		@Override
		protected void setup(Context context) throws IOException,InterruptedException {
			// TODO Auto-generated method stub
			InputSplit input = context.getInputSplit();
			filename = ((FileSplit) input).getPath().getParent().getName();
//			System.out.println("FileName:" +filename);
		}
		/*
		 * C2_1:<0       博客ID:博客ID 同时对两个博客发生行为的总人数>
		 * C1:<0       博客ID 用户ID:评分>
		 */
		@Override
		protected void map(LongWritable key,Text value,Context context) throws IOException, InterruptedException {
			if (filename.equals("c2_1output")) {
				String line1 = value.toString();
				String[] list1 = line1.split("\t");
				//list1[0] 101:101
				//list1[1] 5 
				String[] one1 = list1[0].split(":");
				//one1[0] 101
				//one1[1] 101
				Text key1 = new Text(one1[0]);
				Text value1 = new Text("A:" + one1[1] +"," +list1[1]);
				context.write(key1,value1);
				System.out.println(key1+" "+value1);
			}
			else {
				String line = value.toString();
				String[] list = line.split("\t");
				//list[0] 101
				//list[1] 1:5.0 
//				System.out.println(list[0]);
				String[] one = list[1].split(":");
				//one[0] 100850003
				//one[1] 5.0
				Text key2 = new Text(list[0]);
				Text value2 = new Text("B:" + one[0] + "," + one[1]);
				context.write(key2,value2);
				System.out.println(key2+" "+value2);
			}
		}	
	}
	/*
	 * <博客ID A:博客ID,同时对两个博客发生行为的总人数>
	 * <博客ID B:用户ID,评分>
	 */
	//reduce阶段,键相同的数据作为一个集合传入
	public static class C4Reducer extends Reducer<Text,Text,Text,Text>{
		static Text result = new Text();
		protected void reduce(Text key,Iterable<Text> values,Context context) throws IOException, InterruptedException {
			Map<String, String> mapA = new HashMap<String, String>();
			Map<String, String> mapB = new HashMap<String, String>();
			
			for(Text line : values){
				String val = line.toString();
				if(val.startsWith("A")){
					
					String[] kv = val.substring(2).split(",");
					mapA.put(kv[0], kv[1]); //ItemID, num
				}else if(val.startsWith("B")){
					
					String[] kv = val.substring(2).split(",");
					mapB.put(kv[0], kv[1]); //userID, score
				}
			}
			
			double result = 0;
//			Iterator是迭代器类
			Iterator<String> iterA = mapA.keySet().iterator();
			while(iterA.hasNext()){
				String mapkA = (String) iterA.next();  //itemID
				int num = Integer.parseInt((String) mapA.get(mapkA));     // num
				Iterator<String> iterB = mapB.keySet().iterator();
				while(iterB.hasNext()){
					String mapkB = (String)iterB.next(); //UserID
					double score = Double.parseDouble((String) mapB.get(mapkB));  //score
					result = num * score; //矩阵乘法结果
					
					Text key2 = new Text(mapkB);
					Text value2 = new Text(mapkA + "," +result);
					context.write(key2,value2); //userID \t  itemID,result
					System.out.println(key2+ "\t" + value2);
				}
			}
		}
	}
	/*
	 * <用户ID 推荐的博客ID,推荐分数>
	 */

在这里插入图片描述
  第二组是将C4中矩阵相乘的输出结果进行汇总,输出是“<用户ID 推荐的博客ID 推荐分数>”格式,此为推荐算法的最终处理结果。

	public static class C4_1Mapper extends Mapper<LongWritable, Text, Text, Text>{
		/*
		 * <0          用户ID 推荐的博客ID,推荐分数>
		 */
		@Override
		protected void map(LongWritable key, Text value, Context context)throws IOException, InterruptedException {
			// TODO Auto-generated method stub
			String line1 = value.toString();
			String[] list = line1.split("\t");
			//list[1] 101,25.0
			String[] list1 = list[1].split(",");
			Text key1 = new Text(list1[0]+ "," +list[0]);//userID
			Text value1 = new Text(list1[1]);
			context.write(key1, value1);    //itemID,result
			System.out.println(key1+"\t"+value1);
		}
	}
	/*
	 * <推荐的博客ID,用户ID        推荐分数>
	 */
	public static class C4_1Reducer extends Reducer< Text, Text, Text, Text>{
		@Override
		protected void reduce(Text key, Iterable<Text> values, Context context)throws IOException, InterruptedException {
			// TODO Auto-generated method stub
			float num = 0;
			for (Text value:values) {
				Double result = Double.parseDouble(value.toString());
				num += result;
//				System.out.println(key+":"+value);
			}
			String[] oneList = key.toString().split(",");
			//oneList[0] 101
			//oneList[1] 1
			Text k1 = new Text(oneList[1]);
			Text v1 = new Text(oneList[0]+"\t"+String.valueOf(num));
			context.write(k1, v1);
			System.out.println(k1+"\t"+v1);
		}
	}
	/*
	 * <用户ID       推荐的博客ID 推荐分数>
	 */

在这里插入图片描述

3、推荐算法自动化Shell脚本

  考虑到用户的行为以及数据库中的博客是实时变化的,因此对于针对用户所推荐的博客也应该是实时更新变化的。而实现这种功能,就需要编写执行协同过滤推荐算法的Shell脚本,并在Linux服务器中设置定时任务,定时执行Shell脚本。
  Shell脚本主要分为三部分,数据采集上传、执行推荐算法jar包下载推荐结果、创建推荐表并将推荐结果保存到推荐表。
  首先需要导入环境变量并在服务器与HDFS上创建所需的工作路径。

#!/bin/bash
#必须导入环境变量,否则Hadoop命令无法执行
source /etc/profile
#Hadoop大作业在HDFS中的工作路径
work_dir=/hadoopbigwork/
#打分表存放地址
score_dir=/hadoopwork/score/
score_file=scoredata.txt
#待上传文件HDFS路径
score_dfs_dir=/hadoopbigwork/score/
#mapreduce的jar包执行结果输出路径本地
maybe_text=/hadoopworkmybe/maybe/
#jar包保存路径
jarpath=/hadoopwork/jars/
#判断打分目录是否存在,不存在创建
if [ ! -d  $score_dir ]
then
        mkdir -p $score_dir
        echo -e "this is $score_dir success ! "
else
        echo -e "directory already exists !"
      #清空/export/score/下的打分文件
        rm -rf $score_dir
		mkdir -p $score_dir
fi

  从behavior表采集用户ID、博客ID、打分(点赞0.4+收藏0.6)的数据,并将数据上传到HDFS上作为算法的输入的原始数据。

# behavior表提取用户id、博客id、打分(点赞*0.4+收藏*0.6)
mysql -uroot -p123456 boke -N -e "select user_id,text_id,text_good*0.4+text_save*0.6 score from behavior;">$score_dir$score_file
#删除HDFS下的/score/目录,创建空白的/score/
#Hadoop离开安全状态
hdfs dfsadmin -safemode leave
hdfs dfs -rm -r -f $work_dir
hdfs dfs -mkdir -p $score_dfs_dir
#将打分表上传到HDFS下的/score/路径
hdfs dfs -put $score_dir$score_file $score_dfs_dir

  执行算法jar包,将推荐结果下载到服务器本地。

#执行协同过滤的jar包
hadoop jar $jarpath"ItemCF.jar" com.work.test.C1 $score_dfs_dir $work_dir"c1output"
hadoop jar $jarpath"ItemCF.jar" com.work.test.C2 $score_dfs_dir $work_dir"c2output"
hadoop jar $jarpath"ItemCF.jar" com.work.test.C2_1 $work_dir"c2output" $work_dir"c2_1output"
hadoop jar $jarpath"ItemCF.jar" com.work.test.C3 $work_dir"c1output" $work_dir"c3output"
hadoop jar $jarpath"ItemCF.jar" com.work.test.C4 $work_dir"c2_1output" $work_dir"c3output" $work_dir"c4output"
hadoop jar $jarpath"ItemCF.jar" com.work.test.C4_1 $work_dir"c4output" $work_dir"resultoutput"
#判断是否存在本地的/hadoopwork/maybe/目录,没有则创,有则清空目录下的文件
if [ ! -d $maybe_text ]
then
    mkdir -p $maybe_text
    echo -e "this is $maybe_text success ! "
else
    echo -e "directory already exists !"
    #清空/export/score/下的打分文件
    rm -rf $maybe_text
	mkdir -p $maybe_text
fi
#将结果文件从HDFS中导出到本地
hdfs dfs -get $work_dir"resultoutput/part-r-00000" $maybe_text

  创建博客推荐表,将算法推荐结果上传到博客推荐表中。

#如果表不存在,则创建
mysql -uroot -p123456 --local-infile boke -N -e "
CREATE TABLE if not exists item_cf (user_id varchar(20) NOT NULL COMMENT '用户ID',
text_id varchar(20) NOT NULL COMMENT '博文ID',
score float NOT NULL COMMENT '分数')
DEFAULT CHARSET=utf8;"
#清空推荐表
mysql -uroot -p123456 --local-infile boke -N -e "truncate table item_cf;"
#将结果汇总到MySQL中
mysql -uroot -p123456 --local-infile boke -N -e "set global local_infile=1;load data local infile'$maybe_text"part-r-00000"'
into table item_cf fields terminated by '\t' 
lines terminated by '\n';"

四、创建JavaWeb项目编写博客推荐系统的动态网页

  JavaWeb项目包含游客状态(即未登录状态)和用户状态(即登录状态)。当处于游客状态时,可以浏览首页中全网近期发布的博客,热榜中热度较高的博客,以及可以按照不同的类别查看热度较高的博客,还可以对喜欢的博客进行点赞,但是不可以收藏。游客状态可以通过注册账号登录网站变成用户状态,用户状态除了拥有游客状态的所有功能外还可以收藏喜欢的博客、发表管理自己的博客、更改自己的用户资料,此外系统还会根据用户在网站上的一些行为(目前为点赞、收藏)对用户推荐其可能感兴趣的博客。

五、将整个项目搭建到Linux服务器上

  第一步,先在CentOS系统的服务器上安装jdk1.8.0、MySQL8.0.21、Apache Tomcat v8.5、Hadoop然后配置相关的配置文件完成基础设置,保证项目环境处于正常状态。
  第二步,创建所需的数据库,并创建博客表、用户表、用户行为表,将采集到的数据分别上传到三张表中。
  第三步,将JavaWeb项目打包成war包,部署到/home/apache-tomcat-8.5.72/webapps目录下,然后执行/home/apache-tomcat-8.5.72/bin/startup.sh,开启apache-tomcat环境。
  第四步,将推荐算法打包成jar包上传到服务器中/hadoopwork/jars目录下,然后将推荐算法自动化Shell脚本上传到服务器的/hadoopwork/run_itemCF_jar目录下,最后在服务器终端上输入“crontab -e”命令打开定时任务配置文件,写入每三分钟执行一次推荐算法自动化Shell脚本的定时任务,并启动Hadoop集群和定时服务,定时任务如图所示。
在这里插入图片描述
  第五步,关闭服务器的防火墙,使客户端即本机与服务器能够相互访问,在客户端上打开浏览器输入http://192.168.121.134/Hadoopbigwork/recommend.html,检验是否能成功可访问部署的博客网站。

六、项目文件下载

项目文件下载:https://download.csdn.net/download/qq_49101550/75671450

  • 6
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
项目完整可用,配合压缩包内数据库可直接运行使用。 eclipse+mysql5.7+jdk1.8 功能:推荐引擎利用特殊的信息过滤(IF,Information Filtering)技术,将不同的内容(例如电影、音乐、书籍、新闻、图片、网页等)推荐给可能感兴趣的用户。通常情况下,推荐引擎的实现是通过将用户的个人喜好与特定的参考特征进行比较,并试图预测用户对一些未评分项目的喜好程度。参考特征的选取可能是从项目本身的信息中提取的,或是基于用户所在的社会或社团环境。 根据如何抽取参考特征,我们可以将推荐引擎分为以下四大类: • 基于内容的推荐引擎:它将计算得到并推荐给用户一些与该用户已选择过的项目相似的内容。例如,当你在网上购书时,你总是购买与历史相关的书籍,那么基于内容的推荐引擎就会给你推荐一些热门的历史方面的书籍。 • 基于协同过滤的推荐引擎:它将推荐给用户一些与该用户品味相似的其他用户喜欢的内容。例如,当你在网上买衣服时,基于协同过滤的推荐引擎会根据你的历史购买记录或是浏览记录,分析出你的穿衣品位,并找到与你品味相似的一些用户,将他们浏览和购买的衣服推荐给你。 • 基于关联规则的推荐引擎:它将推荐给用户一些采用关联规则发现算法计算出的内容。关联规则的发现算法有很多,如 Apriori、AprioriTid、DHP、FP-tree 等。 • 混合推荐引擎:合以上各种,得到一个更加全面的推荐效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

开朗小哥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值