基于大数据的音乐推荐系统的设计与实现

基于大数据的音乐推荐系统是为了给听众推荐符合内心喜好的个性化系统。系统提供的功能有,音乐管理:管理员可以添加删除音乐,音乐查找:用户可以在系统中自行查找想要听的歌曲,音乐推荐:系统在收集了用户的行为数据之后为用户个性化推荐音乐,用户管理:管理员可以对用户进行删除,评论管理:管理员可以对评论进行删除,音乐下载:用户可以自行下载个人喜欢分歌曲。
本系统部署在开源大数据平台hadoop3.5之上,搭配的spark2.2也为开源免费。后端使用的关系型数据库为mysql。开发软件使用的是eclipse社区版、pycharm社区版和其他一些免费开源软件。前端采用spring和ssm,技术已经较为成熟。
目 录
摘 要 I
Abstract II
1 绪论 1
1.1 研究背景 1
1.2 研究现状 1
1.2.1 国内研究现状 1
1.2.2 国外研究现状 2
1.3 发展趋势 2
2 相关技术介绍 3
2.1 大数据环境 3
2.1.1 全球开源的Linux系统-Ubuntu 3
2.1.2 开源分大数据处理平台Hadoop 3
2.1.3 shell 3
2.1.4 kettle 4
2.1.5 Scala 4
2.2 数据获取与处理 4
2.2.1 Pymysql 4
2.2.2 Urllib.request 4
2.2.3 besutifulsoup4 4
2.3网页端 5
2.3.1 tomcat9 5
2.3.2 spring 5
3 数据采集 6
3.1 数据源确定 6
3.2 爬虫数据库设计 6
3.3 数据爬取 7
4 系统分析 15
4.1 业务分析 15
4.2 系统功能分析 16
4.3 系统可行性分析 16
4.3.1 技术可行性分析 16
4.3.2 经济可行性分析 17
4.3.3 操作性可行性分析 17
4.4 数据字典 17
4.4.1 数据项条目 17
4.4.2 数据流 19
4.4.3 数据处理条目 21
5 系统设计 23
5.1 系统总体模块结构设计 23
5.2 局部模块设计 24
5.2.1 登录注册模块 24
5.2.2 热门推荐模块 25
5.2.3 新歌上架模块 26
5.2.4 个性化推荐模块 26
5.2.5 用户中心模块 27
5.2.6 音乐管理模块 27
5.2.7 用户管理模块 28
5.2.8 评论管理模块 28
5.3 系统角色功能设计 29
5.4 推荐系统数据库设计 29
5.4.1 数据库概念结构设计 29
5.4.2 数据库物理结构设计 32
5.5 大数据平台搭建设计 35
5.5.1 hadoop的安装与配置 35
5.5.2 spark的安装 38
5.6 推荐算法设计 39
6 系统实现 46
6.1 用户界面 46
6.1.1 系统主页 46
6.1.2 用户登录 46
6.1.3 用户注册 47
6.1.4 用户登陆后界面 49
6.1.5 热门推荐界面 49
6.1.6 音乐播放详情界面 50
6.1.7 个性化音乐推荐详情界面 51
6.1.8 个人音乐记录页面 51
6.2 管理员界面 53
6.2.1 管理员登录 53
6.2.3 评论管理界面 54
6.2.4 用户管理界面 55
总结与展望 56
致 谢 57
参考文献 58
2.2 数据获取与处理
2.2.1 Pymysql
Mysql是如今最为普遍的数据库里,而python作为比较流行的语言之一,自然少不了与mysql做交互,其中pymysql就是使用最多的工具库了。Python导入pymysql之后,配置完数据库就可以直接操作数据库内各种表等。
2.2.2 Urllib.request
Urllib.request 模块定义了适用于在各种复杂情况下打开 URL(主要为 HTTP)的函数和类 — 例如基本认证、摘要认证、重定向、cookies 及其它。Requests 继承了urllib2的所有特性。Requests支持HTTP连接保持和连接池,支持使用cookie保持会话,支持文件上传,支持自动确定响应内容的编码,支持国际化的 URL 和 POST 数据自动编码。Requests的文档非常完备,中文文档也相当不错。Requests能完全满足当前网络的需求,支持Python 2.6—3.8,而且能在PyPy下完美运行。
2.2.3 besutifulsoup4
HTML 文档本身是结构化的文本,有一定的规则,通过它的结构可以简化信息提取。于是,就有了lxml、pyquery、BeautifulSoup等网页信息提取库。一般会用这些库来提取网页信息。其中,lxml 有很高的解析效率,支持 xPath 语法(一种可以在 HTML 中查找信息的规则语法);pyquery 得名于 jQuery(知名的前端 js 库),可以用类似 jQuery 的语法解析网页。
BeautifulSoup(下文简称 bs)翻译成中文就是“美丽的汤”,这个奇奇怪怪的名字取自于《爱丽丝梦游仙境》,bs 最大的特点就是简单易用,不需要像正则和 xPath 等工具必须牢记很多特定的语法。虽然效率更高更直接,但对大多数 python 使用者来说,好用会比高效更重要。
2.3网页端
2.3.1 tomcat9
Tomcat是Apache Software Foundation的Jakarta项目的核心组件,该项目是由Apache,Sun和一些公司和个人开发的轻量级Web应用程序服务器。是Servlet和JSP规范的开源实现。由于其技术非常先进,而且工作稳且性能高,它深受java爱好者的追捧,很多开源边界者都对他赞赏有加。目前呢已经成为最流行流行的Web应用程序服务器。
2.3.2 spring
Spring框架是Rod Johnson启动的开源J2EE应用程序框架,而Rod Johnson是用于bean生命周期管理的轻量级容器。 Spring解决了J2EE开发过程中开发人员遇到的较多常见问题,还提供了非常实用的功能,例如IOC,AOP,Web MVC等。 Spring甚至可以单独构建,也可以与Struts,Webwork和Tapstry等桌面应用程序结合以创建JEE,桌面和小型应用程序。
3 数据采集
3.1 数据源确定
确定了要做基于大数据的音乐推荐系统之后,首先要确定数据源。通过分析基于大数据的音乐推荐系统,即音乐推荐需要哪些数据,详细了解推荐机制,搞清楚这些数据需要被处理为什么格式。
首先定义爬取目标:
1.实现音乐的个性化推荐,帮助用户快速找到可能感兴趣的音乐。
2.实现音乐的热门推荐,帮助主流大众迅速找到流行的趋势。
3.实现音乐的新品推荐,帮助唱片公司带动市场,实现引流。
各大音乐网站多如牛毛,确定了数据爬取目标之后,接下来要确定去哪个平台去爬取泽泻数据。选择数据源要确定数据源数据是否可靠真实,要避免爬取音乐平台发布的虚伪的音乐数据,如不存在的歌唱家、专辑、音乐等。
于是,在比较多家音乐平台(QQ音乐、网易云音乐、酷狗音乐、酷音乐)后,综合分析后选择网易云音乐网页版作为爬虫目标。基于之前设定的爬取目标,选择爬取以下数据:
1.歌手信息:歌手id,歌手名字
2.歌手专辑信息:专辑id,专辑名称,专辑封面
3.音乐信息:音乐名

歌曲推荐算法

package top.wangruns.trackstacking.algorithm;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

public class CollaborativeFiltering {

	/**
	 * 基于最近邻用户产生协同过滤的推荐结果
	 * @param userIdList
	 * 用户Id列表
	 * @param userKNNMatrix
	 * 用户KNN矩阵
	 * @param user2songRatingMatrix
	 * 用户歌曲“评分”矩阵
	 * @param songIdList
	 * 歌曲Id列表
	 * @param n
	 * 推荐的前n首歌曲
	 * @return
	 * 用户歌曲推荐结果矩阵.userId,[recSongId1,recSongId2...recSongIdn]
	 */
	public static Map<Integer, Integer[]> userKNNBasedCF(List<Integer> userIdList,
			final Map<Integer, Integer[]> userKNNMatrix, final Map<Integer, float[]> user2songRatingMatrix,
			final List<Integer> songIdList, final int n) {
		// TODO Auto-generated method stub
		final Map<Integer,Integer[]> user2songRecMatrix=new HashMap<Integer, Integer[]>();
		userIdList.forEach(new Consumer<Integer>() {

			public void accept(Integer curUserId) {
				// TODO Auto-generated method stub
				Integer[] knnIdArray=userKNNMatrix.get(curUserId);
				/**
				 * 对于每一首当前用户没有听过的歌曲
				 * 协同得分为:
				 * 其k个最近邻用户对该歌曲的“评分”的聚合
				 */
				float[] curUserRatings=user2songRatingMatrix.get(curUserId);
				//为用户建立一个最小堆来存放最高的前n首歌曲
				MininumHeap mininumHeap=new MininumHeap(n);
				for(int i=0;i<curUserRatings.length;i++) {
					//对于没有听过的歌曲
					/**
					 * 这里需要注意的是,浮点数不能用==来比较...之前竟然犯了这个低级的错误...
					 * 故这里用 curUserRatings[i]<0.01f 来表示 curUserRatings[i]==0f
					 */
					if(curUserRatings[i]<0.01f) {
						for(int knnIndex=0;knnIndex<knnIdArray.length;knnIndex++) {
							int knnId=knnIdArray[knnIndex];
							float[] knnUserRatings=user2songRatingMatrix.get(knnId);
							curUserRatings[i]+=knnUserRatings[i];
						}
						//这里的聚合策略取均值
						curUserRatings[i]/=knnIdArray.length;
						int curSongId=songIdList.get(i);
						//放入堆中
						mininumHeap.addElement(new TreeNode(curSongId,curUserRatings[i]));
					}
				}
				/**
				 * 对该用户没有听过的歌曲,协同得分完成,选取n个得分最高的项目作为推荐
				 */
				int trueNumber=n;
				//如果推荐的歌曲少于计划推荐的n首(处理歌曲很少的情况)
				if(mininumHeap.getCurHeapSize()<n) {
					trueNumber=mininumHeap.getCurHeapSize();
				}
				Integer[] curUserRecSongId=new Integer[trueNumber];
				for(int i=0;i<trueNumber;i++) {
					int recSongId=mininumHeap.getArray()[i].id;
					curUserRecSongId[i]=recSongId;
				}
				user2songRecMatrix.put(curUserId, curUserRecSongId);
				
			}
			
		});
		return user2songRecMatrix;
	}

}

获取歌曲信息代码:

package top.wangruns.trackstacking.algorithm;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

import top.wangruns.trackstacking.model.Collection;
import top.wangruns.trackstacking.model.DownloadRecord;
import top.wangruns.trackstacking.model.PlayRecord;
import top.wangruns.trackstacking.model.User;

public class DataTranslate {
	private final static float PLAY_SCORE=1f;
	private final static float DOWNLOAD_SCORE=2f;
	private final static float COLLECTION_SCORE=5f;
	private final static float MAX_SCORE=10f;
	private final static int SONG_ID_SET_KEY=0;

	/**
	 * 构建用户频率矩阵来近似用户评分,对于某些系统而言,我们是不可能获取到用户对某些项目的评分的,但是我们可以利用用户的
	 * 行为习惯来反映用户的“评分”,比如一个用户常常收听某一首歌,那么我们可以推断该用户喜欢该歌曲的可能性很大.
	 * 总分10分,主动播放一次1分,下载2分,收藏5分,如果超过10分,按10分计算.
	 * @param userIdList 
	 * 用户Id列表
	 * @param songIdList 
	 * 歌曲Id列表
	 * @param downloadList
	 * 用户的下载记录列表
	 * @param playList
	 * 用户的播放记录列表
	 * @param collectionList
	 * 用户的收藏记录列表
	 * @return
	 * 用户Id-歌曲Id 频率矩阵
	 */
	public static Map<Integer, float[]> getFrequencyMatrix(List<Integer> userIdList, final List<Integer> songIdList,
			List<DownloadRecord> downloadList, List<PlayRecord> playList, List<Collection> collectionList) {
		// TODO Auto-generated method stub
		final Map<Integer,float[]> user2songRatingMatrix=new HashMap<Integer, float[]>();
		final int songLen=songIdList.size();
		//获取用户-歌曲 下载映射
		final Map<Integer,Map<Integer,Set<Integer>>> userId2songIdDownloadMap=getUserId2songIdRecordMap(downloadList,false);
		//获取用户-歌曲 收藏映射
		final Map<Integer, Map<Integer, Set<Integer>>> userId2songIdCollectionMap=getUserId2songIdRecordMap(collectionList,false);
		//获取用户-歌曲-次数 播放映射
		final Map<Integer, Map<Integer, Set<Integer>>> userId2songIdPlayMap=getUserId2songIdRecordMap(playList,true);
		
		userIdList.forEach(new Consumer<Integer>() {

			public void accept(Integer userId) {
				// TODO Auto-generated method stub
				float[] curUserRatingArray=new float[songLen];
				int songIndex=0;
				//处理每一首歌曲
				for(Integer songId:songIdList) {
					/**
					 * 处理下载,这里不考虑下载次数
					 */
					if(userId2songIdDownloadMap.get(userId)!=null && userId2songIdDownloadMap.get(userId).get(SONG_ID_SET_KEY).contains(songId)) {
						//当前用户下载过的歌曲
						curUserRatingArray[songIndex]+=DOWNLOAD_SCORE;
					}
					
					/**
					 * 处理收藏,这里没有次数
					 */
					if(userId2songIdCollectionMap.get(userId)!=null && userId2songIdCollectionMap.get(userId).get(SONG_ID_SET_KEY).contains(songId)) {
						//当前用户收藏的歌曲
						curUserRatingArray[songIndex]+=COLLECTION_SCORE;
					}
					
					/**
					 * 处理播放,考虑播放次数
					 */
					if(userId2songIdPlayMap.get(userId)!=null && userId2songIdPlayMap.get(userId).get(SONG_ID_SET_KEY).contains(songId)) {
						//当前用户播放过的歌曲
						int count=userId2songIdPlayMap.get(userId).get(songId).iterator().next();
						curUserRatingArray[songIndex]+=PLAY_SCORE + count;
					}
					
					/**
					 * 处理最大得分,超过最大得分,记为最大得分
					 */
					if(curUserRatingArray[songIndex]>MAX_SCORE) {
						curUserRatingArray[songIndex]=MAX_SCORE;
					}
					//处理下一首歌
					songIndex++;
				}
				//处理完一个用户
				user2songRatingMatrix.put(userId, curUserRatingArray);
			}
			
		});
		return user2songRatingMatrix;
	}

	/**
	 * 获取用户Id - 歌曲Id 的映射Map
	 * @param recordList
	 * 包含userId,songId的记录列表
	 * @param isCount
	 * 是否需要计数。如果true,则Integer[1]存放计数。
	 * @return
	 * 两层Map
	 * 第一层Map<Integer,Map> 每个userId拥有一个自己的Map:
	 * userId,userSetMap
	 * 
	 * 第二层Map<Integer,Set> 用户自己的Map里面存放两个东西:
	 * (1)为每首歌曲计数songId,CountSet;
	 * (2)存放出现过的歌曲songSetFlay,SongIdSet:
	 */
	private static <T> Map<Integer, Map<Integer, Set<Integer>>> getUserId2songIdRecordMap(final List<T> recordList,final boolean isCount) {
		// TODO Auto-generated method stub
		final Map<Integer, Map<Integer, Set<Integer>>> userId2songIdRecordMap=new HashMap<Integer, Map<Integer, Set<Integer>>>();
		
		recordList.forEach(new Consumer<T>() {

			public void accept(T t) {
				// TODO Auto-generated method stub
				try {
					//利用反射获和泛型获取不同类型表的相同属性
					Field userIdField=t.getClass().getDeclaredField("userId");
					Field songIdField=t.getClass().getDeclaredField("songId");
					userIdField.setAccessible(true);
					songIdField.setAccessible(true);
					int userId=userIdField.getInt(t);
					int songId=songIdField.getInt(t);
					//不需要计数
					if(!isCount) {
						//map外层的userId已经存在
						if(userId2songIdRecordMap.containsKey(userId)) {
							//获取当前用户的记录集合Map
							Map<Integer,Set<Integer>> curRecordSetMap=userId2songIdRecordMap.get(userId);
							//将当前歌曲添加到当前用户的记录集合中
							curRecordSetMap.get(SONG_ID_SET_KEY).add(songId);
						}else {
							Map<Integer,Set<Integer>> curRecordSetMap=new HashMap<Integer, Set<Integer>>();
							//创建记录歌曲Id的集合
							Set<Integer> curSongIdSet=new HashSet<Integer>();
							curSongIdSet.add(songId);
							curRecordSetMap.put(SONG_ID_SET_KEY, curSongIdSet);
							userId2songIdRecordMap.put(userId, curRecordSetMap);
						}
					}else {
						//map外层的userId已经存在
						if(userId2songIdRecordMap.containsKey(userId)) {
							//获取当前用户的记录集合Map
							Map<Integer,Set<Integer>> curRecordSetMap=userId2songIdRecordMap.get(userId);
							//将当前歌曲添加到当前用户的记录集合中
							curRecordSetMap.get(SONG_ID_SET_KEY).add(songId);
							
							//计数
							count(songId,curRecordSetMap);
							
						}else {
							Map<Integer,Set<Integer>> curRecordSetMap=new HashMap<Integer, Set<Integer>>();
							//创建记录歌曲Id的集合
							Set<Integer> curSongIdSet=new HashSet<Integer>();
							curSongIdSet.add(songId);
							curRecordSetMap.put(SONG_ID_SET_KEY, curSongIdSet);
							userId2songIdRecordMap.put(userId, curRecordSetMap);
							
							//计数
							count(songId,curRecordSetMap);
							
						}
					}
					
				}catch (NoSuchFieldException e) {
					e.printStackTrace();
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}

			private void count(int songId, Map<Integer, Set<Integer>> curRecordSetMap) {
				// TODO Auto-generated method stub
				/**
				 * 计数,如果Map<songId,count>已经存在,则直接计数+1
				 */
				if(curRecordSetMap.containsKey(songId)) {
					//获取当前用户歌曲的计数集合(只有一个元素)
					Set<Integer> curCountSet=curRecordSetMap.get(songId);
					int cnt=curCountSet.iterator().next()+1;
					curCountSet.clear();
					curCountSet.add(cnt);
				}else {
					Set<Integer> curCountSet=new HashSet<Integer>();
					curCountSet.add(1);
					curRecordSetMap.put(songId, curCountSet);
				}
			}
			
			
		});
		return userId2songIdRecordMap;
	}

//	private static Map<Integer, Integer>getUserId2songIdDownloadMap(List<DownloadRecord> downloadList) {
//		// TODO Auto-generated method stub
//		final Map<Integer,Integer> userId2songIdDownloadMap=new HashMap<Integer, Integer>();
//		downloadList.forEach(new Consumer<DownloadRecord>() {
//
//			public void accept(DownloadRecord t) {
//				// TODO Auto-generated method stub
//				if(!userId2songIdDownloadMap.containsKey(t.getUserId())) {
//					userId2songIdDownloadMap.put(t.getUserId(), t.getSongId());
//				}
//			}
//			
//		});
//		return userId2songIdDownloadMap;
//	}
	

}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 20
    点赞
  • 331
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值