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() } }