Spark Core 练习
1、数据准备
本次练习的数据是采集电商网站的用户行为数据,主要包含用户的4种行为:搜索、点击、下单和支付
数据格式

- 数据采用_分割字段
- 每一行表示用户的一个行为,所以每一行只能是四种行为中的一种。
- 如果搜索关键字是null,表示这次不是搜索
- 如果点击的品类id和产品id是-1表示这次不是点击
- 下单行为来说一次可以下单多个产品,所以品类id和产品id都是多个,id之间使用逗号,分割。如果本次不是下单行为,则他们相关数据用null来表示
- 支付行为和下单行为类似
数据详细字段说明
|
编号 |
字段名称 |
字段类型 |
字段含义 |
|
1 |
date |
String |
用户点击行为的日期 |
|
2 |
user_id |
Long |
用户的ID |
|
3 |
session_id |
String |
Session的ID |
|
4 |
page_id |
Long |
某个页面的ID |
|
5 |
action_time |
String |
动作的时间点 |
|
6 |
search_keyword |
String |
用户搜索的关键词 |
|
7 |
click_category_id |
Long |
某一个商品品类的ID |
|
8 |
click_product_id |
Long |
某一个商品的ID |
|
9 |
order_category_ids |
String |
一次订单中所有品类的ID集合 |
|
10 |
order_product_ids |
String |
一次订单中所有商品的ID集合 |
|
11 |
pay_category_ids |
String |
一次支付中所有品类的ID集合 |
|
12 |
pay_product_ids |
String |
一次支付中所有商品的ID集合 |
|
13 |
city_id |
Long |
城市 id |
2、需求1 求top10 热门商品品类
需求说明:品类是指产品的分类,大型电商网站品类分多级,咱们的项目中品类只有一级,不同的公司可能对热门的定义不一样。我们按照每个品类的点击、下单、支付的量来统计热门品类。
鞋 点击数 下单数 支付数
衣服 点击数 下单数 支付数
生活用品 点击数 下单数 支付数
例如,综合排名=点击数*20%+下单数*30%+支付数*50%
本项目需求优化为:先按照点击数排名,靠前的就排名高;如果点击数相同,再比较下单数;下单数再相同,就比较支付数。
2.1、需求1 分析

2.2、需求1 实现
object Spark_RDD_TEST_Req10 {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("SparkTest").setMaster("local[*]")
var sc: SparkContext = new SparkContext(conf)
//读取文件
val lineRdd: RDD[String] = sc.textFile("data/user_visit_action.txt")
//封装对象
val userVistActionRdd: RDD[UserVisitAction] = lineRdd.map {
line => {
//对每一行数据进行切分&封装成对象
val strings: Array[String] = line.split("_")
UserVisitAction(
strings(0),
strings(1).toLong,
strings(2),
strings(3).toLong,
strings(4),
strings(5),
strings(6).toLong,
strings(7).toLong,
strings(8),
strings(9),
strings(10),
strings(11),
strings(12).toLong
)
}
}
//对访问行为进行分析,转换为封装CategoryCountInfo的对象
val infoRDD: RDD[CategoryCountInfo] = userVistActionRdd.flatMap( //由于一个记录会涉及多个品类ID,所有使用flatMap, 如果都是一个品类 使用map 或者mapPartition
userAction => {
//判断是否是点击操作 click_category_id !=-1 表示点击行为
if (userAction.click_category_id != -1) {
var clickLists: ListBuffer[CategoryCountInfo] = new ListBuffer[CategoryCountInfo]
clickLists.append(CategoryCountInfo(userAction.click_category_id.toString, 1, 0, 0))
clickLists
} else if (userAction.order_category_ids != "null") {//下单
var orderLists: ListBuffer[CategoryCountInfo] = new ListBuffer[CategoryCountInfo]
//点击&支付的拼品类ID是一个集合,需要切分后保存
val ids: Array[String] = userAction.order_category_ids.split(",")
for (id <- ids) {
orderLists.append(CategoryCountInfo(id, 0, 1, 0))
}
orderLists
} else if (userAction.pay_category_ids != "null") {//支付
var payLists: ListBuffer[CategoryCountInfo] = new ListBuffer[CategoryCountInfo]
val ids: Array[String] = userAction.pay_category_ids.split(",")
for (id <- ids) {
payLists.append(CategoryCountInfo(id, 0, 0, 1))
}
payLists
} else {
Nil
}
}
)
//reduceRDD.take(300).foreach(println)
//将相同的跑品类放在一起
val groupRDD: RDD[(String, Iterable[CategoryCountInfo])] = infoRDD.groupBy(_.categoryId)
//groupRDD.take(300).foreach(println)
//分组之后的数据进行求和
val reduceRDD: RDD[(String, CategoryCountInfo)] = groupRDD.mapValues(
datas => {
datas.reduce((info1, info2) => {
info1.clickCount += info2.clickCount
info1.orderCount += info2.clickCount
info1.payCount += info2.payCount
info1
})
}
)
//对 reduceRDD 机构进行转换
val mapRDD: RDD[CategoryCountInfo] = reduceRDD.map(_._2)
//mapRDD.take(300).foreach(println)
//排序降序 取 top10
val res: Array[CategoryCountInfo] = mapRDD.sortBy(info => (info.clickCount, info.orderCount, info.payCount), false).take(10)
res.foreach(println)
sc.stop()
}
}
//用户访问动作表
case class UserVisitAction(date: String, //用户点击行为的日期
user_id: Long, //用户的ID
session_id: String, //Session的ID
page_id: Long, //某个页面的ID
action_time: String, //动作的时间点
search_keyword: String, //用户搜索的关键词
click_category_id: Long, //某一个商品品类的ID,!= -1 表示 点击行为
click_product_id: Long, //某一个商品的ID
order_category_ids: String, //一次订单中所有品类的ID集合
order_product_ids: String, //一次订单中所有商品的ID集合
pay_category_ids: String, //一次支付中所有品类的ID集合
pay_product_ids: String, //一次支付中所有商品的ID集合
city_id: Long) //城市 id
// 输出结果表
case class CategoryCountInfo(categoryId: String, //品类id
var clickCount: Long, //点击次数
var orderCount: Long, //订单次数
var payCount: Long) //支付次数
3、需求2 Top10热门品类中每个品类的Top10活跃Session统计
3.1、需求分析
- 通过需求1,获取TopN热门品类的id
- 将原始数据进行过滤(1.保留热门品类 2.只保留点击操作)
- 对session的点击数进行转换 (category-session,1)
- 对session的点击数进行统计 (category-session,sum)
- 将统计聚合的结果进行转换 (category,(session,sum))
- 将转换后的结构按照品类进行分组 (category,Iterator[(session,sum)])
- 对分组后的数据降序 取前10
3.2、需求实现
object Spark_RDD_TEST_Req2_Session_Top10 {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("SparkTest").setMaster("local[*]")
var sc: SparkContext = new SparkContext(conf)
//读取文件
val lineRdd: RDD[String] = sc.textFile("data/user_visit_action.txt")
//封装对象
val userVistActionRdd: RDD[UserVisitAction] = lineRdd.map {
line => {
//对一行数据进行切分&封装
val strings: Array[String] = line.split("_")
UserVisitAction(
strings(0),
strings(1).toLong,
strings(2),
strings(3).toLong,
strings(4),
strings(5),
strings(6).toLong,
strings(7).toLong,
strings(8),
strings(9),
strings(10),
strings(11),
strings(12).toLong
)
}
}
//对访问行为进行分析,转换为封装CategoryCountInfo的对象
val infoRDD: RDD[CategoryCountInfo] = userVistActionRdd.flatMap( //由于一个记录会涉及多个品类ID,所有使用flatMap, 如果都是一个品类 使用map 或者mapPartition
userAction => {
//判断是否是点击操作
if (userAction.click_category_id != -1) {
var clickLists: ListBuffer[CategoryCountInfo] = new ListBuffer[CategoryCountInfo]
clickLists.append(CategoryCountInfo(userAction.click_category_id.toString, 1, 0, 0))
clickLists
} else if (userAction.order_category_ids != "null") {
var orderLists: ListBuffer[CategoryCountInfo] = new ListBuffer[CategoryCountInfo]
val ids: Array[String] = userAction.order_category_ids.split(",")
for (id <- ids) {
orderLists.append(CategoryCountInfo(id, 0, 1, 0))
}
orderLists
} else if (userAction.pay_category_ids != "null") {
var payLists: ListBuffer[CategoryCountInfo] = new ListBuffer[CategoryCountInfo]
val ids: Array[String] = userAction.pay_category_ids.split(",")
for (id <- ids) {
payLists.append(CategoryCountInfo(id, 0, 0, 1))
}
payLists
} else {
Nil
}
}
)
//reduceRDD.take(300).foreach(println)
//将相同的跑品类放在一起
val groupRDD: RDD[(String, Iterable[CategoryCountInfo])] = infoRDD.groupBy(_.categoryId)
//groupRDD.take(300).foreach(println)
//分组之后的数据进行求和
val reduceRDD: RDD[(String, CategoryCountInfo)] = groupRDD.mapValues(
datas => {
datas.reduce((info1, info2) => {
info1.clickCount += info2.clickCount
info1.orderCount += info2.clickCount
info1.payCount += info2.payCount
info1
})
}
)
//对 reduceRDD 机构进行转换
val mapRDD: RDD[CategoryCountInfo] = reduceRDD.map(_._2)
//mapRDD.take(300).foreach(println)
//排序降序 取 top10
val res: Array[CategoryCountInfo] = mapRDD.sortBy(info => (info.clickCount, info.orderCount, info.payCount), false).take(10)
//res.foreach(println)
// 通过需求1,获取TopN热门品类的id
val categoryIds: Array[String] = res.map(_.categoryId)
// 将原始数据进行过滤(1.保留热门品类 2.只保留点击操作)
val filterRDD: RDD[UserVisitAction] = userVistActionRdd.filter(
userActions => {
//只保留点击行为,并且点击行为的 品类ID在 top 热门品类中
userActions.click_category_id != -1 && categoryIds.contains(userActions.click_category_id.toString)
})
// 对session的点击数进行转换 (category-session,1)
val mapRDD1: RDD[(String, Int)] = filterRDD.map(
userAction => (userAction.click_category_id + "_" + userAction.session_id, 1)
)
// 对session的点击数进行统计 (category-session,sum)
val reduceRDD1: RDD[(String, Int)] = mapRDD1.reduceByKey(_ + _)
// 将统计聚合的结果进行转换 (category,(session,sum))
val mapRDD2: RDD[(String, (String, Int))] = reduceRDD1.map {
case (cateAndSession, count) => {
(cateAndSession.split("_")(0), (cateAndSession.split("_")(1), count))
}
}
// 将转换后的结构按照品类进行分组 (category,Iterator[(session,sum)])
val groupByKeyRDD: RDD[(String, Iterable[(String, Int)])] = mapRDD2.groupByKey()
// 对分组后的数据降序 取前10
val res2: RDD[(String, List[(String, Int)])] = groupByKeyRDD.mapValues(
datas => {
datas.toList.sortBy(-_._2).take(10)
}
)
res2.collect().foreach(println)
sc.stop()
}
}
4、需求3:页面单跳转化率统计
4.1、需求分析
- 读取原始数据
- 将原始数据映射为样例类
- 将原始数据根据session进行分组
- 将分组后的数据根据时间进行排序(升序)
- 将排序后的数据进行结构的转换(pageId,1)
- 计算分母-将相同的页面id进行聚合统计(pageId,sum)
- 计算分子-将页面id进行拉链,形成连续的拉链效果,转换结构(pageId-pageId2,1)
- 将转换结构后的数据进行聚合统计(pageId-pageId2,sum)
- 计算页面单跳转换率
4.2、需求实现
object Spark_RDD_TEST_Req3_Trans_rate10$ {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("SparkTest").setMaster("local[*]")
var sc: SparkContext = new SparkContext(conf)
//读取文件
val lineRdd: RDD[String] = sc.textFile("data/user_visit_action.txt")
//封装对象
val userVistActionRdd: RDD[UserVisitAction] = lineRdd.map {
line => {
//对一行数据进行切分&封装
val strings: Array[String] = line.split("_")
UserVisitAction(
strings(0),
strings(1).toLong,
strings(2),
strings(3).toLong,
strings(4),
strings(5),
strings(6).toLong,
strings(7).toLong,
strings(8),
strings(9),
strings(10),
strings(11),
strings(12).toLong
)
}
}
//计算分母
val fmMap: collection.Map[Long, Int] = userVistActionRdd.map(userAction => (userAction.page_id, 1)).reduceByKey(_ + _).collectAsMap()
//计算分子
//按照sessionID 对访问数据进行分组
val groupRDD: RDD[(String, Iterable[UserVisitAction])] = userVistActionRdd.groupBy(_.session_id)
//对数据进行排序
val mapValueRDD: RDD[(String, List[(String, Int)])] = groupRDD.mapValues(
datas => {
val sortedList: List[UserVisitAction] = datas.toList.sortBy(_.action_time)
//排序结果保留pageID
val userVisitList: List[Long] = sortedList.map(_.page_id)
//拉链形成单跳效果
val pageFlowList: List[(Long, Long)] = userVisitList.zip(userVisitList.tail)
//对拉链集合进行转换
pageFlowList.map {
case (page1, pag2) => {
(page1 + "_" + pag2, 1)
}
}
}
)
//对mapValue进行结构转换,只保留页面跳转&计数
val pageFlowRDD: RDD[(String, Int)] = mapValueRDD.map(_._2).flatMap(list => list)
val reduceBykeyRDD: RDD[(String, Int)] = pageFlowRDD.reduceByKey(_ + _)
//计算单跳转换率
reduceBykeyRDD.foreach {
case (pageAToPageB, fz) => {
//获取pageAid
val pageAID: Long = pageAToPageB.split("_")(0).toLong
//pageAID 获取pageA 分母的总访问量
val fm: Int = fmMap.getOrElse(pageAID, 1)
println("pageAToPageB --->" + fz.toDouble / fm)
}
}
sc.stop()
}
}

浙公网安备 33010602011771号