目录
需求2:Top10热门品类中每个品类的Top10活跃Session统计
HotCategoryTop10SessionController


说明:上面的数据图是从数据文件中截取的一部分内容,表示为电商网站的用户行为数据,主要包含用户的4种行为:搜索,点击,下单,支付。数据规则如下:
详细字段说明:
样品类:
- //用户访问动作表
- 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
- 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

品类是指产品的分类,大型电商网站品类分多级,咱们的项目中品类只有一级,不同的公司可能对热门的定义不一样。我们按照每个品类的点击、下单、支付的量来统计热门品类。
鞋 点击数 下单数 支付数
衣服 点击数 下单数 支付数
电脑 点击数 下单数 支付数
例如,综合排名 = 点击数*20%+下单数*30%+支付数*50%
本项目需求优化为:先按照点击数排名,靠前的就排名高;如果点击数相同,再比较下单数;下单数再相同,就比较支付数。
分别统计每个品类点击的次数,下单的次数和支付的次数:
(品类,点击总数)(品类,下单总数)(品类,支付总数)
方式一:5+5-(3+3)=4.首先是将五升水的杯子之中的水倒入三升水空杯子之中,倒出三升水杯子之中的水,将五升水杯子之中剩余的两升水倒入三升水杯子之中。灌满五升水的杯子,将五升水的杯子倒入只有两升水的另一个杯子,剩下的就是四升水。
方式二:3+3+3-5=4,首先将三升水倒入空的五升水的水杯,在将三升水灌满水倒入五升水的水杯,此刻三升水的水杯剩下一升水,将五升水的水杯之中的水倒掉,三升水的水杯之中的水转移到五生水的水杯之中,再将三升水导入五升水的水杯之中,得到四升水.

- package req
-
- import org.apache.spark.rdd.RDD
- import org.apache.spark.{SparkConf, SparkContext}
-
- /**
- * @anthor Yang
- * @create 2022-07-21 22:47
- */
- object Spark01_Req_HotCategoryTop10 {
- def main(args: Array[String]): Unit = {
- val conf= new SparkConf().setMaster("local[*]").setAppName("HotCategoryTop10")
- val sc = new SparkContext(conf)
-
- //TODO 需求一:Top10热门品类
- //TODO 获取原始文件
- val fileDatas = sc.textFile("data/user_visit_action.txt")
-
- //TODO 统计分析之前需要将不需要的数据进行过滤-先保留所有的统计数据-对点击数据进行统计
- val clickDatas = fileDatas.filter(
- data => {
- val datas = data.split("_")
- val cid = datas(6)
- cid != "-1"
- }
- )
- //TODO 统计品类点击数量
- val clickCntDatas = clickDatas.map(
- data => {
- val datas = data.split("_")
- val cid = datas(6)
- (cid,1)
- }
- ).reduceByKey(_+_)
- //TODO 统计品类下单数量(方法是和上面差不多的)
- val orderDatas = fileDatas.filter(
- data => {
- val datas = data.split("_")
- val cid = datas(8)
- cid != "null"
- }
- )
- //(1,2,3,4)->(1,1) (2,1) (3.1) 整体变成个体是使用扁平化
- //TODO 整体变成部分就是进行一个扁平化的过程
- val orderCntDatas = orderDatas.flatMap(
- data => {
- val datas = data.split("_")
- val cid = datas(8)
- val cids = cid.split(",")
- cids.map((_,1))
- }
- ).reduceByKey(_+_)
-
- //TODO 统计品类支付数量
- val payDatas = fileDatas.filter(
- data => {
- val datas = data.split("_")
- val cid = datas(10)
- cid != "null"
- }
- )
- //(1,2,3,4)->(1,1) (2,1) (3.1) 整体变成个体是使用扁平化
- //TODO 整体变成部分就是进行一个扁平化的过程
- val payCntDatas = payDatas.flatMap(
- data => {
- val datas = data.split("_")
- val cid = datas(10)
- val cids = cid.split(",")
- cids.map((_,1))
- }
- ).reduceByKey(_+_)
-
- //TODO 对统计结果进行排序 =》 点击,下单,支付(三个指标全部进行排序完成之后进行选取 -> Tuple)
- //val clickSortedDatas = clickCntDatas.sortBy(_._2,false) //这里不是用sortByKey,sortByKet只是对于Key进行一个排序的过程,false表示是降序的过程
-
- //TODO 将点击-》Tuple(品类ID,点击)
- //TODO 下单-》Tuple(品类ID,下单)
- //TODO 支付-》Tuple(品类ID,支付)
- //TODO 将上述的数据变成(品类ID,(点击,下单,支付))
- //上述的方式是不能采用JOIN的方式,因为有的数据可能是空的,join要求两边都是有数据的,join的底层是cogroup
- //这里不使用下面这一条语句的原因是由于括号的原因
- // val value: RDD[(String, (Option[(Option[Int], Option[Int])], Option[Int]))] = clickCntDatas.fullOuterJoin(orderCntDatas).fullOuterJoin(payCntDatas)
- val value: RDD[(String, (Iterable[Int], Iterable[Int], Iterable[Int]))] = clickCntDatas.cogroup(orderCntDatas,payCntDatas)
-
- val mapDatas = value.map{
- case(cid,(clickIter,orderIter,payIter)) =>
- {
- var clickcnt =0
- var ordercnt =0
- var paycnt = 0
- val iterator1: Iterator[Int] = clickIter.iterator
- if(iterator1.hasNext)
- {
- clickcnt = iterator1.next()
- }
- val iterator2: Iterator[Int] = orderIter.iterator
- if(iterator2.hasNext)
- {
- ordercnt = iterator2.next()
- }
- val iterator3: Iterator[Int] = payIter.iterator
- if(iterator3.hasNext)
- {
- paycnt = iterator3.next()
- }
- (cid,(clickcnt,ordercnt,paycnt))
- }
- }
-
- val top10: Array[(String, (Int, Int, Int))] = mapDatas.sortBy(_._2,false).take(10)
-
- //TODO 将统计采集之后的结果打印到控制台上
- top10.foreach(println)
-
- sc.stop()
- }
-
- }
1.三个过滤,同一个RDD(过滤)的重复使用.RDD是不保留数据的,重复使用浪费性能.
2.cogroup可能存在笛卡尔乘积的问题,相同的时候出现OneToOneDependency,不相同的时候可能会出现ShuffleDependency的现象.------性能瓶颈(可能会出现问题),十几万数据如果要是出现笛卡尔乘积,算子性能会低下.
一次性统计每个品类点击的次数,下单的次数和支付的次数:
(品类,(点击总数,下单总数,支付总数))
采用reduceByKey的方式代替cogroup,避免出现笛卡尔乘积的问题.
- package req
-
- import org.apache.spark.rdd.RDD
- import org.apache.spark.{SparkConf, SparkContext}
-
- /**
- * @anthor Yang
- * @create 2022-07-21 22:47
- */
- object Spark02_Req_HotCategoryTop10 {
- def main(args: Array[String]): Unit = {
- val conf= new SparkConf().setMaster("local[*]").setAppName("HotCategoryTop10")
- val sc = new SparkContext(conf)
-
- //TODO 需求一:Top10热门品类
- //TODO 获取原始文件
- val fileDatas = sc.textFile("data/user_visit_action.txt")
-
- //TODO 统计分析之前需要将不需要的数据进行过滤-先保留所有的统计数据-对点击数据进行统计
- val clickDatas = fileDatas.filter(
- data => {
- val datas = data.split("_")
- val cid = datas(6)
- cid != "-1"
- }
- )
-
- //TODO 统计品类点击数量
- val clickCntDatas = clickDatas.map(
- data => {
- val datas = data.split("_")
- val cid = datas(6)
- (cid,1)
- }
- ).reduceByKey(_+_)
- //TODO 统计品类下单数量(方法是和上面差不多的)
- val orderDatas = fileDatas.filter(
- data => {
- val datas = data.split("_")
- val cid = datas(8)
- cid != "null"
- }
- )
- //(1,2,3,4)->(1,1) (2,1) (3.1) 整体变成个体是使用扁平化
- //TODO 整体变成部分就是进行一个扁平化的过程
- val orderCntDatas = orderDatas.flatMap(
- data => {
- val datas = data.split("_")
- val cid = datas(8)
- val cids = cid.split(",")
- cids.map((_,1))
- }
- ).reduceByKey(_+_)
-
- //TODO 统计品类支付数量
- val payDatas = fileDatas.filter(
- data => {
- val datas = data.split("_")
- val cid = datas(10)
- cid != "null"
- }
- )
- //(1,2,3,4)->(1,1) (2,1) (3.1) 整体变成个体是使用扁平化
- //TODO 整体变成部分就是进行一个扁平化的过程
- val payCntDatas = payDatas.flatMap(
- data => {
- val datas = data.split("_")
- val cid = datas(10)
- val cids = cid.split(",")
- cids.map((_,1))
- }
- ).reduceByKey(_+_)
-
- //TODO 对统计结果进行排序 =》 点击,下单,支付(三个指标全部进行排序完成之后进行选取 -> Tuple)
- //val clickSortedDatas = clickCntDatas.sortBy(_._2,false) //这里不是用sortByKey,sortByKet只是对于Key进行一个排序的过程,false表示是降序的过程
-
- //TODO 将点击-》Tuple(品类ID,点击) ---(品类ID,(点击,0,0))
- //TODO 下单-》Tuple(品类ID,下单) ---(品类ID,(0,下单,0))
- //TODO 支付-》Tuple(品类ID,支付) ---(品类ID,(0,0,支付))
- //TODO 将上述的数据变成(品类ID,(点击,下单,支付))
- //上述的方式是不能采用JOIN的方式,因为有的数据可能是空的,join要求两边都是有数据的,join的底层是cogroup
- //这里不使用下面这一条语句的原因是由于括号的原因
- // val value: RDD[(String, (Option[(Option[Int], Option[Int])], Option[Int]))] = clickCntDatas.fullOuterJoin(orderCntDatas).fullOuterJoin(payCntDatas)
- //TODO reduceByKey存在shuffle,但是不存在笛卡尔乘积----修改的地方
- val clickMapDatas = clickCntDatas.map{
- case( cid,clickCnt ) => {
- (cid , (clickCnt,0,0))
- }
- }
- val orderkMapDatas = orderCntDatas.map{
- case( cid,orderCnt ) => {
- (cid , (0,orderCnt,0))
- }
- }
- val payMapDatas = payCntDatas.map{
- case( cid,payCnt ) => {
- (cid , (0,0,payCnt))
- }
- }
- //将上述的三个数据进行合并,使用union
- val unionRDD: RDD[(String, (Int, Int, Int))] = clickMapDatas.union(orderkMapDatas).union(payMapDatas)
- //使用reduceByKey
- val reduceRDD = unionRDD.reduceByKey(
- (t1,t2) =>
- {
- (t1._1+t2._1 ,t1._2+t2._2 ,t1._3+t2._3)
- }
- )
- val top10 = reduceRDD.sortBy(_._2,false).take(10)
-
-
- //TODO 将统计采集之后的结果打印到控制台上
- top10.foreach(println)
-
- sc.stop()
- }
-
- }
reduceByKey存在shuffle的问题,这里我们使用了四个reduceByKey,使用的shuffle次数太多,那么落盘的次数就是很多,想要提高性能,就要想办法减少落盘的过程.
对于上述的问题再次改进
- package req
-
- import org.apache.spark.rdd.RDD
- import org.apache.spark.{SparkConf, SparkContext}
- import org.codehaus.jackson.annotate.JsonTypeInfo.Id
-
- /**
- * @anthor Yang
- * @create 2022-07-21 22:47
- */
- object Spark03_Req_HotCategoryTop10 {
- def main(args: Array[String]): Unit = {
- val conf= new SparkConf().setMaster("local[*]").setAppName("HotCategoryTop10")
- val sc = new SparkContext(conf)
-
- //TODO 需求一:Top10热门品类
- //TODO 获取原始文件
- val fileDatas = sc.textFile("data/user_visit_action.txt")
-
- val flatDatas = fileDatas.flatMap(//flatMap要求返回的是可以迭代的集合
- data => {
- var datas = data.split("_")
- if( datas(6) != "-1" )
- {//点击数据的场合
- List((datas(6),(1,0,0)))
- }
- else if(datas(8) != "null")
- {//下单的场合
- val id = datas(8)
- val ids = id.split(",")
- ids.map(//map返回就是迭代的集合
- id => {
- (id,(0,1,0))
- }
- )
-
- }
- else if(datas(10) != "null")
- {
- val id= datas(10)
- val ids = id.split(",")
- ids.map(
- id=> {
- (id,(0,0,1))
- }
- )
- }
- else
- {
- Nil //全量函数的集合
- }
- }
- )
- val top10 = flatDatas.reduceByKey(
- (t1 , t2) =>
- {
- (t1._1 + t2._1 ,t1._2+t2._2 ,t1._3+t2._3)
- }
- ).sortBy(_._2,false).take(10)
-
- //TODO 将统计采集之后的结果打印到控制台上
- top10.foreach(println)
-
- sc.stop()
- }
-
- }
使用累加器的过程
- package com.atguigu.bigdata.spark.req
-
- import org.apache.spark.util.AccumulatorV2
- import org.apache.spark.{SparkConf, SparkContext}
-
- import scala.collection.mutable
-
- object Spark01_Req_HotCategoryTop10_3 {
-
- def main(args: Array[String]): Unit = {
-
- val conf = new SparkConf().setMaster("local[*]").setAppName("HotCategoryTop10")
- val sc = new SparkContext(conf)
-
- val fileDatas = sc.textFile("data/user_visit_action.txt")
-
- // 创建累加器对象
- val acc = new HotCategoryAccumulator()
- // 注册累加器
- sc.register(acc, "HotCategory")
-
- fileDatas.foreach(
- data => {
- val datas = data.split("_")
- if ( datas(6) != "-1" ) {
- // 点击的场合
- acc.add( (datas(6), "click") )
- } else if ( datas(8) != "null" ) {
- // 下单的场合
- val id = datas(8)
- val ids = id.split(",")
- ids.foreach(
- id => {
- acc.add( (id, "order") )
- }
- )
- } else if ( datas(10) != "null" ) {
- // 支付的场合
- val id = datas(10)
- val ids = id.split(",")
- ids.foreach(
- id => {
- acc.add( (id, "pay") )
- }
- )
- }
- }
- )
-
- // TODO 获取累加器的结果
- val resultMap: mutable.Map[String, HotCategoryCount] = acc.value
- val top10 = resultMap.map(_._2).toList.sortWith(
- (left, right) => {
- if ( left.clickCnt > right.clickCnt ) {
- true
- } else if ( left.clickCnt == right.clickCnt ) {
- if ( left.orderCnt > right.orderCnt ) {
- true
- } else if ( left.orderCnt == right.orderCnt ) {
- left.payCnt > right.payCnt
- } else {
- false
- }
- } else {
- false
- }
- }
- ).take(10)
-
- top10.foreach(println)
-
- sc.stop()
-
- }
- case class HotCategoryCount( cid:String, var clickCnt : Int, var orderCnt : Int, var payCnt : Int )
- // TODO 自定义热门点击累加器
- // 1. 继承AccumulatorV2
- // 2. 定义泛型
- // IN : (品类ID,行为类型)
- // OUT : Map[品类ID, HotCategoryCount]
- // 3. 重写方法 (3 + 3)
- class HotCategoryAccumulator extends AccumulatorV2[(String, String), mutable.Map[String, HotCategoryCount]]{
-
- private val map = mutable.Map[String, HotCategoryCount]()
-
- override def isZero: Boolean = {
- map.isEmpty
- }
-
- override def copy(): AccumulatorV2[(String, String), mutable.Map[String, HotCategoryCount]] = {
- new HotCategoryAccumulator()
- }
-
- override def reset(): Unit = {
- map.clear()
- }
-
- override def add(v: (String, String)): Unit = {
- val (cid, actionType) = v
- val hcc: HotCategoryCount = map.getOrElse(cid, HotCategoryCount(cid, 0, 0, 0))
- actionType match {
- case "click" => hcc.clickCnt += 1
- case "order" => hcc.orderCnt += 1
- case "pay" => hcc.payCnt += 1
- }
- map.update(cid, hcc)
- }
-
- override def merge(other: AccumulatorV2[(String, String), mutable.Map[String, HotCategoryCount]]): Unit = {
- other.value.foreach {
- case ( cid, otherHCC ) => {
- val thisHCC: HotCategoryCount = map.getOrElse(cid, HotCategoryCount(cid, 0, 0, 0))
- thisHCC.clickCnt += otherHCC.clickCnt
- thisHCC.orderCnt += otherHCC.orderCnt
- thisHCC.payCnt += otherHCC.payCnt
- map.update(cid, thisHCC)
- }
- }
- }
-
- override def value: mutable.Map[String, HotCategoryCount] = {
- map
- }
- }
- }
在需求一的基础上,增加每个品类用户session的点击统计
JavaEE Web:浏览器(zhangsan) => 服务器(lisi) [lisi懵了] Session:会话(通信状态)

①对于Application部分的封装
- package com.atguigu.bigdata.spark.summer.common
-
- import com.atguigu.bigdata.spark.summer.util.EnvCache
- import org.apache.spark.{SparkConf, SparkContext}
-
-
- trait TApplication {
-
- //下面第一个括号之中代表的是参数,第二个代表的是进行执行的操作
- def execute(master:String = "local[*]", appName:String)( op: =>Unit ): Unit = {
-
- val conf: SparkConf = new SparkConf().setMaster(master).setAppName(appName)
- val sc = new SparkContext(conf)
- EnvCache.put(sc) //用来存放sc这个值,后面可以拿来用
- try {
- op
- } catch {
- case e: Exception => e.printStackTrace()
- }
-
- sc.stop()
- EnvCache.clear()
- }
- }
②对于Controller部分的封装
- package com.atguigu.bigdata.spark.summer.common
-
- trait TController {
-
- def dispatch(): Unit
- }
③对于Dao部分的封装
- package com.atguigu.bigdata.spark.summer.common
-
-
- import com.atguigu.bigdata.spark.summer.util.EnvCache
- import org.apache.spark.SparkContext
-
- import scala.io.{BufferedSource, Source}
-
- trait TDao {
- def readFile( path : String ) = {
- // e:/data/word.txt
- val source: BufferedSource = Source.fromFile(EnvCache.get() + path)
- val lines = source.getLines().toList
- source.close()
- lines
- }
- def readFileBySpark( path : String ) = {
- //对于前面使用的sc进行一个读取的过程
- EnvCache.get().asInstanceOf[SparkContext].textFile(path)
- }
- }
④Service部分的封装
- package com.atguigu.bigdata.spark.summer.common
-
- trait TService {
- def analysis() : Any = {
-
- }
- def analysis( data : Any ) : Any = {
-
- }
- }
- package com.atguigu.bigdata.spark.summer.application
-
- import com.atguigu.bigdata.spark.summer.common.TApplication
- import com.atguigu.bigdata.spark.summer.controller.{HotCategoryTop10Controller, WordCountController}
-
- //这里进行继承App便是可以之间执行的主函数,可以不用写出main函数
- object HotCategoryTop10Application extends TApplication with App{
-
- //这里的execute本来是两个参数,但是其中一个是已经定义好的默认参数
- execute(appName = "HotCategoryTop10"){
- //执行controller
- val controller = new HotCategoryTop10Controller
- controller.dispatch()
- }
- }
- package com.atguigu.bigdata.spark.summer.controller
-
- import com.atguigu.bigdata.spark.summer.common.TController
- import com.atguigu.bigdata.spark.summer.service.HotCategoryTop10Service
-
- class HotCategoryTop10Controller extends TController {
-
- private val hotCategoryTop10Service = new HotCategoryTop10Service
-
- override def dispatch(): Unit = {
- val result: Array[(String, (Int, Int, Int))] = hotCategoryTop10Service.analysis()
- result.foreach(println)
- }
- }
- package com.atguigu.bigdata.spark.summer.dao
-
- import com.atguigu.bigdata.spark.summer.common.TDao
-
- class HotCategoryTop10Dao extends TDao {
-
- }
- package com.atguigu.bigdata.spark.summer.service
-
- import com.atguigu.bigdata.spark.summer.common.TService
- import com.atguigu.bigdata.spark.summer.dao.HotCategoryTop10Dao
-
- class HotCategoryTop10Service extends TService {
-
- private val hotCategoryTop10Dao = new HotCategoryTop10Dao
-
- override def analysis() = {
- val fileDatas = hotCategoryTop10Dao.readFileBySpark("data/user_visit_action.txt")
- val flatDatas = fileDatas.flatMap(
- data => {
- var datas = data.split("_")
- if ( datas(6) != "-1" ) {
- // 点击数据的场合
- List((datas(6), (1, 0, 0)))
- } else if ( datas(8) != "null" ) {
- // 下单数据的场合
- val id = datas(8)
- val ids = id.split(",")
- ids.map(
- id => {
- (id, (0, 1, 0))
- }
- )
- } else if ( datas(10) != "null" ) {
- // 支付数据的场合
- val id = datas(10)
- val ids = id.split(",")
- ids.map(
- id => {
- (id, (0, 0, 1))
- }
- )
- } else {
- Nil
- }
- }
- )
-
- val top10 = flatDatas.reduceByKey(
- (t1, t2) => {
- ( t1._1 + t2._1, t1._2 + t2._2, t1._3 + t2._3 )
- }
- ).sortBy(_._2, false).take(10)
- top10
- }
- }
- package com.atguigu.bigdata.spark.summer.util
-
- object EnvCache {
-
- private val envCache : ThreadLocal[Object] = new ThreadLocal[Object]
-
- def put( data : Object ): Unit = {
- envCache.set(data)
- }
-
- def get() = {
- envCache.get()
- }
-
- def clear(): Unit = {
- envCache.remove()
- }
- }
这部分的代码是在上面的原有基础上进行一个增加的过程.
- package com.atguigu.bigdata.spark.summer.application
-
- import com.atguigu.bigdata.spark.summer.common.TApplication
- import com.atguigu.bigdata.spark.summer.controller.{HotCategoryTop10Controller, HotCategoryTop10SessionController}
-
-
- object HotCategoryTop10SessionApplication extends TApplication with App{
-
- execute(appName = "HotCategoryTop10Session"){
- val controller = new HotCategoryTop10SessionController
- controller.dispatch()
- }
- }
- package com.atguigu.bigdata.spark.summer.controller
-
- import com.atguigu.bigdata.spark.summer.common.TController
- import com.atguigu.bigdata.spark.summer.service.{HotCategoryTop10Service, HotCategoryTop10SessionService}
-
- class HotCategoryTop10SessionController extends TController {
-
- //由于这里是需要用到上面的逻辑,因此这里是存在两个代码
- private val hotCategoryTop10Service = new HotCategoryTop10Service
- private val hotCategoryTop10SessionService = new HotCategoryTop10SessionService
-
- override def dispatch(): Unit = {
- val top10: Array[(String, (Int, Int, Int))] = hotCategoryTop10Service.analysis()
- val result = hotCategoryTop10SessionService.analysis(top10.map(_._1))//只是存取Id
-
- result.foreach(println)
- }
- }
- package com.atguigu.bigdata.spark.summer.dao
-
- import com.atguigu.bigdata.spark.summer.common.TDao
-
- class HotCategoryTop10SessionDao extends TDao {
-
- }
- package com.atguigu.bigdata.spark.summer.service
-
- import com.atguigu.bigdata.spark.summer.bean.UserVisitAction
- import com.atguigu.bigdata.spark.summer.common.TService
- import com.atguigu.bigdata.spark.summer.dao.{HotCategoryTop10Dao, HotCategoryTop10SessionDao}
- import org.apache.spark.rdd.RDD
-
- class HotCategoryTop10SessionService extends TService {
-
- private val hotCategoryTop10SessionDao = new HotCategoryTop10SessionDao
-
- override def analysis( data : Any ) = {
- val topIds: Array[String] = data.asInstanceOf[Array[String]]
-
- val fileDatas = hotCategoryTop10SessionDao.readFileBySpark("data/user_visit_action.txt")
-
- val actionDatas = fileDatas.map(
- data => {
- val datas = data.split("_")
- //将字符串转换为long类型
- UserVisitAction(
- datas(0),
- datas(1).toLong,
- datas(2),
- datas(3).toLong,
- datas(4),
- datas(5),
- datas(6).toLong,
- datas(7).toLong,
- datas(8),
- datas(9),
- datas(10),
- datas(11),
- datas(12).toLong
- )
- }
- )
-
- val clickDatas = actionDatas.filter {
- data => {
- if ( data.click_category_id != -1 ) //点击不是-1
- {
- topIds.contains(data.click_category_id.toString)
- } else {
- false
- }
- }
- }
-
- val reduceDatas = clickDatas.map(
- data => {
- (( data.click_category_id, data.session_id ), 1)
- }
- ).reduceByKey(_+_)
-
- val groupDatas: RDD[(Long, Iterable[(String, Int)])] = reduceDatas.map {
- case ((cid, sid), cnt) => {
- (cid, (sid, cnt))
- }
- }.groupByKey()
-
- groupDatas.mapValues(
- iter => {
- iter.toList.sortBy(_._2)(Ordering.Int.reverse).take(10)
- }
- ).collect()
-
- }
- }
- package com.atguigu.bigdata.spark.summer.bean
-
- //用户访问动作表
- 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
- 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
- )
1)页面单跳转化率
计算页面单跳转化率,什么是页面单跳转换率,比如一个用户在一次 Session 过程中访问的页面路径 3,5,7,9,10,21,那么页面 3 跳到页面 5 叫一次单跳,7-9 也叫一次单跳,那么单跳转化率就是要统计页面点击的概率。
比如:计算 3-5 的单跳转化率,先获取符合条件的 Session 对于页面 3 的访问次数(PV)为 A,然后获取符合条件的 Session 中访问了页面 3 又紧接着访问了页面 5 的次数为 B,那么 B/A 就是 3-5 的页面单跳转化率。
为什么IE被淘汰了?因为IE不支持新的技术,而且不能够装入插件.不要说什么产品经理没用,你是可以替换的,但是产品经理是不可以替换的.
2)统计页面单跳转化率意义
产品经理和运营总监,可以根据这个指标,去尝试分析,整个网站,产品,各个页面的表现怎么样,是不是需要去优化产品的布局;吸引用户最终可以进入最后的支付页面。
数据分析师,可以此数据做更深一步的计算和分析。
企业管理层,可以看到整个公司的网站,各个页面的之间的跳转的表现如何,可以适当调整公司的经营战略或策略。
信息产生过剩:产生的大量的垃圾信息,在垃圾信息之中,找出有用的信息.

第一列是进行一个采集的过程,数据产生乱序的原因,如下流程图所示:
上述过程是需要进行计算一个分子与分母的过程,分子(首页 - 详情) 分母(首页)

- package com.atguigu.bigdata.spark.req
-
- import com.atguigu.bigdata.spark.summer.bean.UserVisitAction
-
- object Spark03_Req_PageFlow {
-
- def main(args: Array[String]): Unit = {
-
- val conf = new SparkConf().setMaster("local[*]").setAppName("Pageflow")
- val sc = new SparkContext(conf)
-
- val fileDatas = sc.textFile("data/user_visit_action.txt")
-
- //包装成为样例类
- val actionDatas = fileDatas.map(
- data => {
- val datas = data.split("_")
- UserVisitAction(
- datas(0),
- datas(1).toLong,
- datas(2),
- datas(3).toLong,
- datas(4),
- datas(5),
- datas(6).toLong,
- datas(7).toLong,
- datas(8),
- datas(9),
- datas(10),
- datas(11),
- datas(12).toLong
- )
- }
- )
- actionDatas.cache()//做一个缓存过程
-
- // 【1,2,3,4,5,6,7】
- val okIds = List(1,2,3,4,5,6,7)
- // 【(1,2),(2,3)】
- val okFlowIds = okIds.zip(okIds.tail)
-
- // TODO 分母的计算 Long是页面id,int是统计结果
- val result: Map[Long, Int] = actionDatas.filter(
- action => {
- okIds.init.contains(action.page_id.toInt)
- }
- ).map(
- action => {
- (action.page_id, 1)
- }
- ).reduceByKey(_ + _).collect().toMap
-
- // TODO 分子的计算
- // 1. 将数据按照session进行分组
- val groupRDD: RDD[(String, Iterable[UserVisitAction])] = actionDatas.groupBy(_.session_id)
-
- // 将分组后的数据进行组内排序
- val mapRDD = groupRDD.mapValues(
- iter => {
- //toList含义就是将上面的东西进行转换,进行可以迭代操作
- val actions: List[UserVisitAction] = iter.toList.sortBy(_.action_time)
- //【1,2,3,4,5,6,7】
- //【2,3,4,5,6,7】
- // 滑窗
- //【1-2,2-3,3-4,4-5,5-6,6-7】
- val ids: List[Int] = actions.map(_.page_id.toInt)
-
- // val iterator: Iterator[List[Long]] = ids.sliding(2)
- // while ( iterator.hasNext ) {
- // val longs: List[Long] = iterator.next()
- // (longs.head, longs.last)
- // }
-
- val flowIds: List[(Int, Int)] = ids.zip(ids.tail)
-
- flowIds.filter(
- ids => {
- okFlowIds.contains(ids)
- }
- )
- }
- )
- val mapRDD2 = mapRDD.map(_._2)
- val flatRDD = mapRDD2.flatMap(list => list)
-
- // 分子计算完毕
- val reduceRDD = flatRDD.map((_, 1)).reduceByKey(_ + _)
- // TODO 单挑转换率的统计
- reduceRDD.foreach {
- case ( (id1, id2), cnt ) => {
- println(s"页面【${id1}-${id2}】单挑转换率为 :" + ( cnt.toDouble / result.getOrElse(id1, 1) ))
- }
- }
-
-
- sc.stop()
-
- }
- }