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 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | object Spark _ RDD _ TEST _ Req 10 { 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((info 1 , info 2 ) = > { info 1 .clickCount + = info 2 .clickCount info 1 .orderCount + = info 2 .clickCount info 1 .payCount + = info 2 .payCount info 1 }) } ) //对 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、需求实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | object Spark _ RDD _ TEST _ Req 2 _ Session _ Top 10 { 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((info 1 , info 2 ) = > { info 1 .clickCount + = info 2 .clickCount info 1 .orderCount + = info 2 .clickCount info 1 .payCount + = info 2 .payCount info 1 }) } ) //对 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 mapRDD 1 : RDD[(String, Int)] = filterRDD.map( userAction = > (userAction.click _ category _ id + "_" + userAction.session _ id, 1 ) ) // 对session的点击数进行统计 (category-session,sum) val reduceRDD 1 : RDD[(String, Int)] = mapRDD 1 .reduceByKey( _ + _ ) // 将统计聚合的结果进行转换 (category,(session,sum)) val mapRDD 2 : RDD[(String, (String, Int))] = reduceRDD 1 .map { case (cateAndSession, count) = > { (cateAndSession.split( "_" )( 0 ), (cateAndSession.split( "_" )( 1 ), count)) } } // 将转换后的结构按照品类进行分组 (category,Iterator[(session,sum)]) val groupByKeyRDD : RDD[(String, Iterable[(String, Int)])] = mapRDD 2 .groupByKey() // 对分组后的数据降序 取前10 val res 2 : RDD[(String, List[(String, Int)])] = groupByKeyRDD.mapValues( datas = > { datas.toList.sortBy(- _ . _ 2 ).take( 10 ) } ) res 2 .collect().foreach(println) sc.stop() } } |
4、需求3:页面单跳转化率统计
4.1、需求分析
- 读取原始数据
- 将原始数据映射为样例类
- 将原始数据根据session进行分组
- 将分组后的数据根据时间进行排序(升序)
- 将排序后的数据进行结构的转换(pageId,1)
- 计算分母-将相同的页面id进行聚合统计(pageId,sum)
- 计算分子-将页面id进行拉链,形成连续的拉链效果,转换结构(pageId-pageId2,1)
- 将转换结构后的数据进行聚合统计(pageId-pageId2,sum)
- 计算页面单跳转换率
4.2、需求实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | object Spark _ RDD _ TEST _ Req 3 _ Trans _ rate 10 $ { 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 (page 1 , pag 2 ) = > { (page 1 + "_" + pag 2 , 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() } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
2021-04-13 Centos7开机之后连不上网 ens33:<BROADCAST,ULTICAST>mtu 1588 qdisc noop state DOWl group default qlen 1888