一、通过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实战电影点评系统(一)的更多相关文章

  1. Spark实战电影点评系统(二)

    二.通过DataFrame实战电影点评系统 DataFrameAPI是从Spark 1.3开始就有的,它是一种以RDD为基础的分布式无类型数据集,它的出现大幅度降低了普通Spark用户的学习门槛. D ...

  2. 基于Spark的电影推荐系统(实战简介)

    写在前面 一直不知道这个专栏该如何开始写,思来想去,还是暂时把自己对这个项目的一些想法 和大家分享 的形式来展现.有什么问题,欢迎大家一起留言讨论. 这个项目的源代码是在https://github. ...

  3. 编程实战——电影管理器之界面UI及动画切换

    编程实战——电影管理器之界面UI及动画切换 在前文“编程实战——电影管理器之利用MediaInfo获取高清视频文件的相关信息”中提到电影管理器的目的是方便播放影片,在想看影片时不需要在茫茫的文件夹下找 ...

  4. 基于Spark的电影推荐系统(推荐系统~1)

    第四部分-推荐系统-项目介绍 行业背景: 快速:Apache Spark以内存计算为核心 通用 :一站式解决各个问题,ADHOC SQL查询,流计算,数据挖掘,图计算 完整的生态圈 只要掌握Spark ...

  5. 【精编重制版】JavaWeb 入门级项目实战 -- 文章发布系统 (第二节)

    说明 本教程是,原文章发布系统教程的精编重制版,会包含每一节的源码,以及修正之前的一些错误.因为之前的教程只做到了评论模块,很多地方还不完美,因此重制版会修复之前的一些谬误和阐述不清的地方,而且,后期 ...

  6. Spark实战1

    1. RDD-(Resilient Distributed Dataset)弹性分布式数据集      Spark以RDD为核心概念开发的,它的运行也是以RDD为中心.有两种RDD:第一种是并行Col ...

  7. Spark GraphX宝刀出鞘,图文并茂研习图计算秘笈与熟练的掌握Scala语言【大数据Spark实战高手之路】

    Spark GraphX宝刀出鞘,图文并茂研习图计算秘笈 大数据的概念与应用,正随着智能手机.平板电脑的快速流行而日渐普及,大数据中图的并行化处理一直是一个非常热门的话题.图计算正在被广泛地应用于社交 ...

  8. 基于Spark的电影推荐系统(电影网站)

    第一部分-电影网站: 软件架构: SpringBoot+Mybatis+JSP 项目描述:主要实现电影网站的展现 和 用户的所有动作的地方 技术选型: 技术 名称 官网 Spring Boot 容器 ...

  9. 基于Spark的电影推荐系统(推荐系统~2)

    第四部分-推荐系统-数据ETL 本模块完成数据清洗,并将清洗后的数据load到Hive数据表里面去 前置准备: spark +hive vim $SPARK_HOME/conf/hive-site.x ...

随机推荐

  1. 【luoguP3000】 [USACO10DEC]牛的健美操Cow Calisthenics

    题目链接 二分答案,判断需要断几条边,用\(f[i]\)表示以\(i\)为根的子树断边后的最长路径,对于一个点\(u\),存在\(f[v]>mid\)时就删到\(v\)的边\(f[v1]+f[v ...

  2. APP用户隐私协议

    告知用户 重视每个用户的的隐私,郑重承诺如下: 一.我们所收集的信息以及如何使用: 我们可能通过您的IP地址,地理位置信息,收集一些非个人隐私的统计资料,使我们能够进一步改善APP的服务.例如,当您浏 ...

  3. zabbix 同步ldap帐号脚本

    1.界面配置ldap验证(略) 2.mysql导入ldap帐号信息 #!/usr/bin/env python# -*- coding:utf-8 -*- import pymysqlimport c ...

  4. java.lang.IllegalArgumentException: host parameter is null

    即 URL 应为 http://www.baidu.com  但是实际配置成了  www.baidu.com 所以出现此错误

  5. 一篇文章理解Redis集群【转】

    Redis作为一款性能优异的内存数据库,支撑着亿级数据量的社交平台,也成为很多互联网公司的标配.这里将以Redis Cluster 集群为核心,基于最新的Redis5版本,从原理到实战,玩儿转Redi ...

  6. openssl制作证书全过程 + 部分修改

    一:生成CA证书    目前不使用第三方权威机构的CA来认证,自己充当CA的角色.     先决条件:从openssl官网下载www.openssl.org                安装open ...

  7. 2019 GDD breaking world‘s record of π

    Day 2 1.breaking pi‘s world record with google cloud [concept] memory wall: Originally theorized in ...

  8. Linux 对音频万能处理的命令——SOX

    what's the SOX         SoX(即 Sound eXchange)是一个跨平台(Windows,Linux,MacOS 等)的命令行实用程序,可以将各种格式的音频文件转换为需要的 ...

  9. Classic BAdi and New BAdi

    Former Member Classic BAdi and New BAdi ... 2007年04月27日 04:43 | 1.5k Views Hi all, I have a question ...

  10. shell基础知识8-xargs命令

    简介 xargs 命令应该紧跟在管道操作符之后.它使用标准输入作为主要的数据源,将从 stdin 中 读取的数据作为指定命令的参数并执行该命令. 将多行输入转换成单行输出 [root@dns-node ...