Spark Core 练习

1、数据准备

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

数据格式

  1. 数据采用_分割字段
  2. 每一行表示用户的一个行为,所以每一行只能是四种行为中的一种。
  3. 如果搜索关键字是null,表示这次不是搜索
  4. 如果点击的品类id和产品id是-1表示这次不是点击
  5. 下单行为来说一次可以下单多个产品,所以品类id和产品id都是多个,id之间使用逗号,分割。如果本次不是下单行为,则他们相关数据用null来表示
  6. 支付行为和下单行为类似

数据详细字段说明

编号

字段名称

字段类型

字段含义

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()
  }
}
posted @ 2022-04-13 20:31  晓枫的春天  阅读(116)  评论(0编辑  收藏  举报