SparkCore实战练习

1.数据准备

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

数据格式

在这里插入图片描述

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

数据集下载

链接:https://pan.baidu.com/s/1ZLhYdXz1Foi6MpeUBFGCnQ
提取码:12lt

需求1:Top10热门品类

需求说明

        品类是指产品的分类,大型电商网站品类分多级,咱们的项目中品类只有一级,不同的公司可能对热门的定义不一样。我们按照每个品类的点击、下单、支付的量来统计热门品类。
        本项目需求优化为:先按照点击数排名,靠前的就排名高;如果点击数相同,再比较下单数;下单数再相同,就比较支付数

需求分析

  • 判断操作是 “点击”、“下单”、“支付”三类中的哪一类。
  • 将品类和操作类别组合为(类别,(点击数,下单数,支付数)),可以将元组分装为对象。
  • 按照key类别聚合三种操作的总次数。
  • 排序取前10。

代码实现:

创建样例类,用于封装数据信息

// 样例类可以自动生成apply方法和unapply()方法
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
// 定义样例类存储商品品类及对应的操作次数
// 样例类的属性默认使用val修饰,不可重新赋值
case class CategoryCountInfo(categoryId: String, //品类id
                             var clickCount: Long, //点击次数
                             var orderCount: Long, //订单次数
                             var payCount: Long) //支付次数

需求1代码

    val conf: SparkConf = new SparkConf().setAppName(this.getClass.getName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val dataRDD: RDD[String] = sc.textFile("D:\\学习资料\\spark\\spark\\2.资料\\spark-core数据\\user_visit_action.txt")
    // 封装对象
    val userActionRDD: RDD[UserVisitAction] = dataRDD.map {
    
    
      line => {
    
    
        val lineSplit: Array[String] = line.split("_")
        // 封装对象
        UserVisitAction(
          lineSplit(0),
          lineSplit(1).toLong,
          lineSplit(2),
          lineSplit(3).toLong,
          lineSplit(4),
          lineSplit(5),
          lineSplit(6).toLong,
          lineSplit(7).toLong,
          lineSplit(8),
          lineSplit(9),
          lineSplit(10),
          lineSplit(11),
          lineSplit(12).toLong
        )
      }
    }
    // 转换数据格式
    val cateInfoRDD: RDD[(String, CategoryCountInfo)] = userActionRDD.flatMap {
    
    
      userAction => {
    
    
        // 判断操作类型
        if (userAction.click_category_id != -1) {
    
    
          // 转换数据格式 (品类, CategoryCountInfo对象)
          List((userAction.click_category_id.toString,
            CategoryCountInfo(userAction.click_category_id.toString, 1, 0, 0)))
        } else if (userAction.order_category_ids != "null") {
    
    
          // 处理order_category_ids
          val cateIds: Array[String] = userAction.order_category_ids.split(",")
          // ListBuffer存储多个对象
          val list = new mutable.ListBuffer[(String, CategoryCountInfo)]()
          for (cateId <- cateIds) {
    
    
            list.append((cateId, CategoryCountInfo(cateId, 0, 1, 0)))
          }
          list
        } else if (userAction.pay_category_ids != "null") {
    
    
          val cateIds: Array[String] = userAction.pay_category_ids.split(",")
          // ListBuffer存储多个对象
          val list = new mutable.ListBuffer[(String, CategoryCountInfo)]()
          for (cateId <- cateIds) {
    
    
            list.append((cateId, CategoryCountInfo(cateId, 0, 0, 1)))
          }
          list
        } else {
    
    
          // 返回空集合
          Nil
        }
      }
    }
    // 根据key聚合数据
    val reduceRDD: RDD[(String, CategoryCountInfo)] = cateInfoRDD.reduceByKey(
      (cate1, cate2) => {
    
    
        cate1.clickCount = cate1.clickCount + cate2.clickCount
        cate1.orderCount = cate1.orderCount + cate2.orderCount
        cate1.payCount = cate1.payCount + cate2.payCount
        cate1
      }
    )
    // 去掉多余的key,转换格式
    val cateCountRDD: RDD[CategoryCountInfo] = reduceRDD.map {
    
    
      _._2
    }
    // 排序取前10
    val resArr: Array[CategoryCountInfo] = cateCountRDD.sortBy(
      // 元组可以按照元素的顺序来依次排序
      cate =>{
    
    (cate.clickCount,cate.orderCount,cate.payCount)},
      // 倒序
      false
    ).take(10)
    sc.stop()

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

需求说明

        对于排名前10的品类,分别获取每个品类点击次数排名前10的sessionId。(注意: 这里我们只关注点击次数,不关心下单和支付次数)
        对于top10的品类,每一个都要获取对它点击次数排名前10的sessionId。这个功能,可以让我们看到,对某个用户群体最感兴趣的品类,各个品类最感兴趣最典型的用户的session的行为。

需求分析

  • 从需求1获取top10热门品类中的id。
  • 过滤数据,值保留top10热门品类id和对应的点击日志。
  • 转换数据格式 (品类id_session,1)。
  • 对如上数据做聚合,统计每个品类的session点击次数。
  • 转换数据格式 (品类id,(session,count))。
  • 按照品类分组。
  • 倒序排序,每组取前10。

代码实现

  • 需求2需要借助需求1的结果。
	// =======================================上面是需求1=============================================
    // 取出top10的商品id
    val top10Ids: Array[String] = resArr.map(_.categoryId)
    // top10Ids要发送到每个task,可以做广播变量优化
    val broadcast: Broadcast[Array[String]] = sc.broadcast(top10Ids)
    // 过滤数据,只保留top10 id对应的点击数据
    val filterRDD: RDD[UserVisitAction] = userActionRDD.filter(
      // 注意click_category_id的类型是Long类型,需要转换为String类型
      datas => {
    
    
        if (datas.click_category_id != -1) {
    
    
          broadcast.value.contains(datas.click_category_id.toString)
        } else {
    
    
          false
        }
      }
    )
    // 转换格式 (品类id_session, 1)
    val cateIdAndSession1: RDD[(String, Int)] = filterRDD.map(
      datas => {
    
    
        (datas.click_category_id + "_" + datas.session_id, 1)
      }
    )
    // 按照相同的key聚合 (品类id_session, count)
    val cateIdAndSessionCount: RDD[(String, Int)] = cateIdAndSession1.reduceByKey(_ + _)
    // 转换数据格式 (品类id, (session, count))
    val cateIdAndSessionCount2: RDD[(String, (String, Int))] = cateIdAndSessionCount.map {
    
    
      case (idAndSession, count) => {
    
    
        val split: Array[String] = idAndSession.split("_")
        (split(0), (split(1), count))
      }
    }
    // 按照品类分组
    val cateGroupRDD: RDD[(String, Iterable[(String, Int)])] = cateIdAndSessionCount2.groupByKey()
    // 倒序排序,取前10
    val res2RDD: RDD[(String, List[(String, Int)])] = cateGroupRDD.mapValues(
      datas => {
    
    
        val list: List[(String, Int)] = datas.toList
        // 排序
        list.sortWith(_._2 > _._2)
      }.take(10)
    )
    res2RDD.foreach(println)

    sc.stop()

需求3:页面单跳转化率统计

需求说明

        计算页面单跳转化率,什么是页面单跳转换率,比如一个用户在一次 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 的页面单跳转化率。

需求分析

  • 可以先统计每个页面的访问次数
        -->先转换数据格式:(页面id,1)
        -->再根据页面id做聚合,求出每个页面的总访问量 (页面id,count)
        -->再统计页面A->页面B->页面C->页面D…的单跳次数
  • 根据sessionId和页面id,对用户访问时间做排序 (sessionid-页面id, 访问时间)
  • 转换数据格式,按照sessionid分组
        -->session1:页面A->页面B->页面C
        -->session2:->页面A->页面B->页面C
  • 每个组中返回访问顺序的集合 List(每个用户的页面访问顺序)
        -->页面访问顺序 : A -> B -> C -> D -> E
        -->与集合tail做拉链: B -> C -> D -> E
        -->(A,B) (B,C) (C,D) (D,E)
  • 求出拉链后组成的集合中每个元素出现的次数,即为每个页面的单跳次数 ((A,B),count)
  • 最后用每个页面 单跳次数 / 每个页面的总次数 = 页面单跳转化率
	val conf: SparkConf = new SparkConf().setAppName(this.getClass.getName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val dataRDD: RDD[String] = sc.textFile("D:\\学习资料\\spark\\spark\\2.资料\\spark-core数据\\user_visit_action.txt")
    // 封装对象
    val userVisitActionRDD: RDD[UserVisitAction] = dataRDD.map {
    
    
      line => {
    
    
        val lineSplit: Array[String] = line.split("_")
        // 封装对象
        UserVisitAction(
          lineSplit(0),
          lineSplit(1).toLong,
          lineSplit(2),
          lineSplit(3).toLong,
          lineSplit(4),
          lineSplit(5),
          lineSplit(6).toLong,
          lineSplit(7).toLong,
          lineSplit(8),
          lineSplit(9),
          lineSplit(10),
          lineSplit(11),
          lineSplit(12).toLong
        )
      }
    }

    // 先统计每个页面的访问次数
    // (页面id,1) -> (页面id,count)
    val pageAnd1: RDD[(Long, Long)] = userVisitActionRDD.map(
      action => {
    
    
        (action.page_id, 1L)
      }
    )
    // 按照pageId聚合,每个页面的访问次数, 转为map字典,方便使用
    val pageAndCount: Map[Long, Long] = pageAnd1.reduceByKey(_ + _).collect().toMap

    // 根据sessionid和pageid,按照时间排序
    val sessionAndPage: RDD[(String, String)] = userVisitActionRDD.map(
      action => {
    
    
        // 转换格式 (sessionid_pageid, 时间)
        (action.session_id + "_" + action.page_id, action.action_time)
      }
    )
    // 按照sessionid分组,按照时间正序排序
    // 转换格式 (sessionid, (pageid,时间))
    val sessionPageTime: RDD[(String, (String, String))] = sessionAndPage.map {
    
    
      case (session_page, time) => {
    
    
        val split: Array[String] = session_page.split("_")
        (split(0), (split(1), time))
      }
    }
    // 按照sessionid分组,每个用户的访问页面和时间
    val sessionGroup: RDD[(String, Iterable[(String, String)])] = sessionPageTime.groupByKey()
    // 转换格式,舍弃sessionid Iterable(pageid,时间)
    val pageTime: RDD[Iterable[(String, String)]] = sessionGroup.map(_._2)
    // 按照时间正序排序 Iterable(pageid,时间)
    val pageSortRDD: RDD[Iterable[(String, String)]] = pageTime.map(
      data => {
    
    
        data.toList.sortWith(_._2 < _._2)

      }
    )
    //转换格式,舍弃时间 Iterable(pageid)
    val pageRDD: RDD[List[String]] = pageSortRDD.map(
      datas => {
    
    
        datas.toList.map(_._1)
      }
    )

    // 拉链操作,组成((A,B),1) 的形式,方便计算从A到B的跳转数
    val zipRDD: RDD[((String, String), Long)] = pageRDD.flatMap(
      pageList => {
    
    
        pageList.zip(pageList.tail).map((_, 1L))
      }
    )
    val breakCount: RDD[((String, String), Long)] = zipRDD.reduceByKey(_ + _)

    // breakCount.take(3).foreach(println)
    // 计算A到B的跳转数
    // 跳转数 / 页面访问数
    val resRDD: RDD[String] = breakCount.map {
    
    
      case (breakPage, count) => {
    
    
        val sum: Long = pageAndCount.getOrElse(breakPage._1.toLong, 1)
        val res: Double = count * 1.0 / sum
        breakPage + ":" + res
      }
    }
    resRDD.foreach(println)

    //breakRDD.take(3).foreach(println)

    sc.stop()

猜你喜欢

转载自blog.csdn.net/FlatTiger/article/details/115205775