Spark实战电影点评系统(一)
一、通过RDD实战电影点评系统
日常的数据来源有很多渠道,如网络爬虫、网页埋点、系统日志等。下面的案例中使用的是用户观看电影和点评电影的行为数据,数据来源于网络上的公开数据,共有3个数据文件:uers.dat、ratings.dat和movies.dat。
其中,uers.dat的格式如下: UserID::Gender::Age::Occupation::Zip-code ,这个文件里共有6040个用户的信息,每行中用“::”隔开的详细信息包括ID、性别(F、M分别表示女性、男性)、年龄(使用7个年龄段标记)、职业和邮编。

ratings.dat的格式如下: UserID::MovieID::Rating::Timestamp ,这个文件共有一百万多条记录,记录的是评分信息,即用户ID、电影ID、评分(满分是5分)和时间戳。

movies.dat的格式如下: MovieID::Title::Genres ,这个文件记录的是电影信息,即电影ID、电影名称和电影类型。

首先初始化Spark,以及读取文件。创建一个Scala的object类,在main方法中配置SparkConf和SparkContext,这里指定程序在本地运行,并且把程序名字设置为“RDD_Movie_Users_Analyzer”。
val conf = new SparkConf().setMaster("local[*]").setAppName("RDD_Movie_User_Analyzer")
/**
* Spark2.0引入SparkSession封装了SparkContext和SQLContext,并且会在builder的getOrCreate方法中判断是否
* 含有符合要求的SparkSession存在,有则使用,没有则进行创建
*/
val spark = SparkSession.builder.config(conf).getOrCreate()
// 获取SparkSession的SparkContext
val sc = spark.sparkContext
// 把Spark程序运行时的日志设置为warn级别,以方便查看运行结果
sc.setLogLevel("WARN")
// 把用到的数据加载进来转换为RDD,此时使用sc.textFile并不会读取文件,而是标记了有这个操作,遇到Action级算子时才回真正去读取文件
val usersRDD = sc.textFile("./src/test1/users.dat")
val moviesRDD = sc.textFile("./src/test1/movies.dat")
val ratingsRDD = sc.textFile("./src/test1/ratings.dat")
首先我们来写一个案例计算,并打印出所有电影中评分最高的前10个电影名和平均评分。
第一步:从ratingsRDD中取出MovieID和rating,从moviesRDD中取出MovieID和Name,如果后面的代码重复使用这些数据,则可以把它们缓存起来。首先把使用map算子上面的RDD中的每一个元素(即文件中的每一行)以“::”为分隔符进行拆分,然后再使用map算子从拆分后得到的数组中取出需要用到的元素,并把得到的RDD缓存起来
第二步:从ratings的数据中使用map算子获取到形如(movieID,(rating,1))格式的RDD,然后使用reduceByKey把每个电影的总评分以及点评人数算出来。此时得到的RDD格式为(movieID,Sum(ratings),Count(ratings)))。
第三步:把每个电影的Sum(ratings)和Count(ratings)相除,得到包含了电影ID和平均评分的RDD:
第四步:把avgRatings与movieInfo通过关键字(key)连接到一起,得到形如(movieID, (MovieName,AvgRating))的RDD,然后格式化为(AvgRating,MovieName),并按照key(也就是平均评分)降序排列,最终取出前10个并打印出来。
println("所有电影中平均得分最高(口碑最好)的电影:")
val movieInfo = moviesRDD.map(_.split("::")).map(x=>(x(0),x(1))).cache()
val ratings = ratingsRDD.map(_.split("::")).map(x=>(x(0),x(1),x(2))).cache()
val moviesAndRatings = ratings.map(x=>(x._2,(x._3.toDouble,1))).reduceByKey((x,y)=>(x._1+y._1,x._2+y._2))
val avgRatings = moviesAndRatings.map(x=>(x._1,x._2._1.toDouble/x._2._2))
avgRatings.join(movieInfo).map(item=>(item._2._1,item._2._2))
.sortByKey(false).take(10)
.foreach(record=>println(record._2+"评分为:"+record._1))

接下来我们来看另外一个功能的实现:分析最受男性喜爱的电影Top10和最受女性喜爱的电影Top10。
首先来分析一下:单从ratings中无法计算出最受男性或者女性喜爱的电影Top10,因为该RDD中没有Gender信息,如果需要使用Gender信息进行Gender的分类,此时一定需要聚合。当然,我们力求聚合使用的是mapjoin(分布式计算的一大痛点是数据倾斜,map端的join一定不会数据倾斜),这里是否可使用mapjoin?不可以,因为map端的join是使用broadcast把相对小得多的变量广播出去,这样可以减少一次shuffle,这里,用户的数据非常多,所以要使用正常的join。
使用join连接ratings和users之后,对分别过滤出男性和女性的记录进行处理:
println("========================================")
println("所有电影中最受男性喜爱的电影Top10:")
val usersGender = usersRDD.map(_.split("::")).map(x=>(x(0),x(1)))
val genderRatings = ratings.map(x=>(x._1,(x._1,x._2,x._3))).join(usersGender).cache()
// genderRatings.take(10).foreach(println)
val maleFilteredRatings = genderRatings.filter(x=>x._2._2.equals("M")).map(x=>x._2._1)
val femaleFilteredRatings = genderRatings.filter(x=>x._2._2.equals("F")).map(x=>x._2._1)
maleFilteredRatings.map(x=>(x._2,(x._3.toDouble,1))).reduceByKey((x,y)=>(x._1+y._1,x._2+y._2))
.map(x=>(x._1,x._2._1.toDouble/x._2._2))
.join(movieInfo)
.map(item=>(item._2._1,item._2._2))
.sortByKey(false)
.take(10)
.foreach(record=>println(record._2+"评分为:"+record._1))
println("========================================")
println("所有电影中最受女性喜爱的电影Top10:")
femaleFilteredRatings.map(x=>(x._2,(x._3.toDouble,1))).reduceByKey((x,y)=>(x._1+y._1,x._2+y._2))
.map(x=>(x._1,x._2._1.toDouble/x._2._2))
.join(movieInfo)
.map(item=>(item._2._1,item._2._2))
.sortByKey(false)
.take(10)
.foreach(record=>println(record._2+"评分为:"+record._1))

在现实业务场景中,二次排序非常重要,并且经常遇到。下面来模拟一下这些场景,实现对电影评分数据进行二次排序,以Timestamp和Rating两个维度降序排列,值得一提的是,Java版本的二次排序代码非常烦琐,而使用Scala实现就会很简捷,首先我们需要一个继承自Ordered和Serializable的类。
class SecondarySortKey(val first:Double,val second:Double) extends Ordered[SecondarySortKey] with Serializable{
// 在这个类中重写compare方法
override def compare(other:SecondarySortKey):Int={
// 既然是二次排序,那么首先要判断第一个排序字段是否相等,如果不相等,就直接排序
if(this.first-other.first!=0){
(this.first-other.first).toInt
}else {
// 如果第一个字段相等,则比较第二个字段,若想实现多次排序,也可以按照这个模式继续比较下去
if(this.second-other.second>0){
Math.ceil(this.second-other.second).toInt
}else if (this.second-other.second<0) {
Math.floor(this.second-other.second).toInt
}else {
(this.second-other.second).toInt
}
}
}
}
然后再把RDD的每条记录里想要排序的字段封装到上面定义的类中作为key,把该条记录整体作为value。
println("========================================")
println("对电影评分数据以Timestamp和Rating两个维度进行二次降序排列:")
val pairWithSortkey = ratingsRDD.map(line=>{
val spilted = line.split("::")
(new SecondarySortKey(spilted(3).toDouble,spilted(2).toDouble),line)
})
// 直接调用sortByKey,此时会按照之前实现的compare方法排序
val sorted = pairWithSortkey.sortByKey(false)
val sortedResult = sorted.map(sortedline => sortedline._2)
sortedResult.take(10).foreach(println)
取出排序后的RDD的value,此时这些记录已经是按照时间戳和评分排好序的,最终打印出的结果如图所示,从图中可以看到已经按照timestamp和评分降序排列了。

Spark实战电影点评系统(一)的更多相关文章
- Spark实战电影点评系统(二)
二.通过DataFrame实战电影点评系统 DataFrameAPI是从Spark 1.3开始就有的,它是一种以RDD为基础的分布式无类型数据集,它的出现大幅度降低了普通Spark用户的学习门槛. D ...
- 基于Spark的电影推荐系统(实战简介)
写在前面 一直不知道这个专栏该如何开始写,思来想去,还是暂时把自己对这个项目的一些想法 和大家分享 的形式来展现.有什么问题,欢迎大家一起留言讨论. 这个项目的源代码是在https://github. ...
- 编程实战——电影管理器之界面UI及动画切换
编程实战——电影管理器之界面UI及动画切换 在前文“编程实战——电影管理器之利用MediaInfo获取高清视频文件的相关信息”中提到电影管理器的目的是方便播放影片,在想看影片时不需要在茫茫的文件夹下找 ...
- 基于Spark的电影推荐系统(推荐系统~1)
第四部分-推荐系统-项目介绍 行业背景: 快速:Apache Spark以内存计算为核心 通用 :一站式解决各个问题,ADHOC SQL查询,流计算,数据挖掘,图计算 完整的生态圈 只要掌握Spark ...
- 【精编重制版】JavaWeb 入门级项目实战 -- 文章发布系统 (第二节)
说明 本教程是,原文章发布系统教程的精编重制版,会包含每一节的源码,以及修正之前的一些错误.因为之前的教程只做到了评论模块,很多地方还不完美,因此重制版会修复之前的一些谬误和阐述不清的地方,而且,后期 ...
- Spark实战1
1. RDD-(Resilient Distributed Dataset)弹性分布式数据集 Spark以RDD为核心概念开发的,它的运行也是以RDD为中心.有两种RDD:第一种是并行Col ...
- Spark GraphX宝刀出鞘,图文并茂研习图计算秘笈与熟练的掌握Scala语言【大数据Spark实战高手之路】
Spark GraphX宝刀出鞘,图文并茂研习图计算秘笈 大数据的概念与应用,正随着智能手机.平板电脑的快速流行而日渐普及,大数据中图的并行化处理一直是一个非常热门的话题.图计算正在被广泛地应用于社交 ...
- 基于Spark的电影推荐系统(电影网站)
第一部分-电影网站: 软件架构: SpringBoot+Mybatis+JSP 项目描述:主要实现电影网站的展现 和 用户的所有动作的地方 技术选型: 技术 名称 官网 Spring Boot 容器 ...
- 基于Spark的电影推荐系统(推荐系统~2)
第四部分-推荐系统-数据ETL 本模块完成数据清洗,并将清洗后的数据load到Hive数据表里面去 前置准备: spark +hive vim $SPARK_HOME/conf/hive-site.x ...
随机推荐
- ffmpeg结合SDL编写播放器(一)
ffmpeg 工具是一个高效快速的命令行工具,进行视音频不同格式之间的转换. ffmpeg命令行 ffmpeg可以读取任意数量的输入“文件”(可以是常规文件,管道,网络流,抓取设备等)读取,由 -i ...
- python 获取文件运行路径
import os print(os.getcwd()) print("/".join(os.path.dirname(os.path.abspath(__file__)).spl ...
- SQL Mode
SQL Mode简介 在MySQL中,SQL Mode常常用来解决以下问题: 1.通过设置SQL Mode,可以完成不同严格程度的数据校验,有效保证数据准确性. 2.通过设置SQL Mode为ANSI ...
- Golang 接口
1 接口是什么 Golang中没有像Python.Java拥有类和对象的概念,其封装对象或说明对象是通过接口来实现的.比如谁能够实现什么样的功能,便能够将其抽象化封装. 接口定义了一组方法(抽象方法集 ...
- MySQL之replace函数应用
replace函数,从字面上看其主要作用就是替换.实际它的作用确实是替换.那么替换有哪些应用场景呢?比如A表和B表有一个关联的字段就是id,但是在A中id是数字,在B中id也是数字,但是B中id多一个 ...
- 表格样式、表格css、
.mytab{ border-collapse: collapse;}.mytab tr,.mytab td,.mytab th{ text-align: center; border: 1px so ...
- vue.js动态绑定input的checked
不管<input type='radio checked='true''> 你的checked属性值是true或者false,他都会选中. 其实原理是这样的,复选框里只要有checked ...
- cloneable以及深拷贝和浅拷贝
Objec类有11个方法,有两个protected的方法,其中一个为clone方法(另一个为finalize). 该方法的签名是: protected native Object clone() th ...
- hinkphp项目部署到Linux服务器上报错“模板不存在”如何解决
检查了服务器上的文件,并没有缺少文件,再次上传文件到服务器,还是报错.莫名其妙,怀疑是代码问题. 仔细检查后,发现是模板的文件名问题: 用过TP的都知道:thinkphp会在$this->dis ...
- MLflow系列3:MLflow项目
英文链接:https://mlflow.org/docs/latest/projects.html 本文链接:https://www.cnblogs.com/CheeseZH/p/11945432.h ...