SparkCore项目实战|Top10热门品类|Top10热门品类中每个品类的Top10活跃Session统计|页面单跳转化率统计

数据分析

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

SparkCore项目实战|Top10热门品类|Top10热门品类中每个品类的Top10活跃Session统计|页面单跳转化率统计_第1张图片

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

SparkCore项目实战|Top10热门品类|Top10热门品类中每个品类的Top10活跃Session统计|页面单跳转化率统计_第2张图片

字段说明

编号 字段名称 字段类型 字段含义
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

需求一:Top10热门品类

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

需求分析

SparkCore项目实战|Top10热门品类|Top10热门品类中每个品类的Top10活跃Session统计|页面单跳转化率统计_第3张图片

代码实现

package com.atguigu.req

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.mutable.ListBuffer

object TopN {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    // 读取数据
    val rdd: RDD[String] = sc.textFile("D:\\MyWork\\WorkSpaceIDEA\\scalaDemo\\spark\\input\\user_visit_action.txt")

    // 对读取的数据进行转换,封装为UserVisitAction,放到RDD中
    val actionRDD: RDD[UserVisitAction] = rdd.map {
      line => {
        // 对读取的每一行数据进行切分
        val fields: Array[String] = line.split("_")
        // 封装程UserVisitAction对象
        UserVisitAction(
          fields(0),
          fields(1).toLong,
          fields(2),
          fields(3).toLong,
          fields(4),
          fields(5),
          fields(6).toLong,
          fields(7).toLong,
          fields(8),
          fields(9),
          fields(10),
          fields(11),
          fields(12).toLong
        )
      }
    }

    // 对数据进行处理:对每一条用户行为进行处理,转换为一个CategoryCountInfo对象
    val infoRDD: RDD[CategoryCountInfo] = actionRDD.flatMap {
      userAction => {
        // 判断是否为点击
        if (userAction.click_category_id != -1) {
          List(CategoryCountInfo(userAction.click_category_id.toString, 1, 0, 0))
        } else if (userAction.order_category_ids != "null") {
          // 判断是否为下单
          // 对下单的品类ID就行切分
          val ids: Array[String] = userAction.order_category_ids.split(",")
          val list_order: ListBuffer[CategoryCountInfo] = ListBuffer[CategoryCountInfo]()
          for (elem <- ids) {
            list_order.append(CategoryCountInfo(elem, 0, 1, 0))
          }
          list_order
        } else if (userAction.pay_category_ids != "null") {
          // 判断是否为支付
          // 对下单的品类ID就行切分
          val ids: Array[String] = userAction.pay_category_ids.split(",")
          val list_pay: ListBuffer[CategoryCountInfo] = ListBuffer[CategoryCountInfo]()
          for (elem <- ids) {
            list_pay.append(CategoryCountInfo(elem, 0, 1, 0))
          }
          list_pay

        } else {
          // 其它
          Nil
        }
      }
    }

    // 对数据进行处理 ==> CategoryCountInfo(保健平,1,1,1)
    // 将相同的品类放到一组
    val groupRDD: RDD[(String, Iterable[CategoryCountInfo])] = infoRDD.groupBy(info => info.categoryId)

    // 对分组之后的数据进行聚合
    val reduceRDD: RDD[(String, CategoryCountInfo)] = groupRDD.mapValues {
      datas => {
        datas.reduce {
          (info1, info2) => {
            info1.clickCount = info1.clickCount + info2.clickCount
            info1.orderCount = info1.orderCount + info2.orderCount
            info1.payCount = info1.payCount + info2.payCount
            info1
          }
        }
      }
    }

    // 获取聚合后的品类
    val mapRDD: RDD[CategoryCountInfo] = reduceRDD.map(_._2)

    // 排序取前10 利用元组的特性,先比较点击数,再比较下单数,最后比较支付数
    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
                           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)//支付次数

结果

CategoryCountInfo(15,6120,2931,0)
CategoryCountInfo(2,6119,2963,0)
CategoryCountInfo(20,6098,3020,0)
CategoryCountInfo(12,6095,2958,0)
CategoryCountInfo(11,6093,2983,0)
CategoryCountInfo(17,6079,2983,0)
CategoryCountInfo(7,6074,3048,0)
CategoryCountInfo(9,6045,2966,0)
CategoryCountInfo(19,6044,2880,0)
CategoryCountInfo(13,6036,2942,0)

可使用reduceByKey、groupByKey、累加器等 方法

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

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

代码实现

 通过需求1,获取TopN热门品类的id
 将原始数据进行过滤(1.保留热门品类 2.只保留点击操作)
 对session的点击数进行转换 (category-session,1)
 对session的点击数进行统计 (category-session,sum)
 将统计聚合的结果进行转换 (category,(session,sum))
 将转换后的结构按照品类进行分组 (category,Iterator[(session,sum)])
 对分组后的数据降序 取前10

package com.atguigu.req

import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.mutable.ListBuffer

object TopN_2 {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    // 读取数据
    val rdd: RDD[String] = sc.textFile("D:\\MyWork\\WorkSpaceIDEA\\scalaDemo\\spark\\input\\user_visit_action.txt")

    // 对读取的数据进行转换,封装为UserVisitAction,放到RDD中
    val actionRDD: RDD[UserVisitAction] = rdd.map {
      line => {
        // 对读取的每一行数据进行切分
        val fields: Array[String] = line.split("_")
        // 封装程UserVisitAction对象
        UserVisitAction(
          fields(0),
          fields(1).toLong,
          fields(2),
          fields(3).toLong,
          fields(4),
          fields(5),
          fields(6).toLong,
          fields(7).toLong,
          fields(8),
          fields(9),
          fields(10),
          fields(11),
          fields(12).toLong
        )
      }
    }

    // 对数据进行处理:对每一条用户行为进行处理,转换为一个CategoryCountInfo对象
    val infoRDD: RDD[CategoryCountInfo] = actionRDD.flatMap {
      userAction => {
        // 判断是否为点击
        if (userAction.click_category_id != -1) {
          List(CategoryCountInfo(userAction.click_category_id.toString, 1, 0, 0))
        } else if (userAction.order_category_ids != "null") {
          // 判断是否为下单
          // 对下单的品类ID就行切分
          val ids: Array[String] = userAction.order_category_ids.split(",")
          val list_order: ListBuffer[CategoryCountInfo] = ListBuffer[CategoryCountInfo]()
          for (elem <- ids) {
            list_order.append(CategoryCountInfo(elem, 0, 1, 0))
          }
          list_order
        } else if (userAction.pay_category_ids != "null") {
          // 判断是否为支付
          // 对下单的品类ID就行切分
          val ids: Array[String] = userAction.pay_category_ids.split(",")
          val list_pay: ListBuffer[CategoryCountInfo] = ListBuffer[CategoryCountInfo]()
          for (elem <- ids) {
            list_pay.append(CategoryCountInfo(elem, 0, 0, 1))
          }
          list_pay

        } else {
          // 其它
          Nil
        }
      }
    }

    // 对数据进行处理 ==> CategoryCountInfo(保健平,1,1,1)
    // 将相同的品类放到一组
    val groupRDD: RDD[(String, Iterable[CategoryCountInfo])] = infoRDD.groupBy(info => info.categoryId)

    // 对分组之后的数据进行聚合
    val reduceRDD: RDD[(String, CategoryCountInfo)] = groupRDD.mapValues {
      datas => {
        datas.reduce {
          (info1, info2) => {
            info1.clickCount = info1.clickCount + info2.clickCount
            info1.orderCount = info1.orderCount + info2.orderCount
            info1.payCount = info1.payCount + info2.payCount
            info1
          }
        }
      }
    }

    // 获取聚合后的品类
    val mapRDD: RDD[CategoryCountInfo] = reduceRDD.map(_._2)

    // 排序取前10 利用元组的特性,先比较点击数,再比较下单数,最后比较支付数
    val top10Arr: Array[CategoryCountInfo] = mapRDD.sortBy(info => (info.clickCount, info.orderCount, info.payCount), false)
      .take(10)


    // 需求2 Top10热门品类中每个品类的Top10活跃Session统计
    // 获取Top10品类ID
    val ids: Array[String] = top10Arr.map(_.categoryId)

    // 优化,将ids声明为广播变量
    val broadIds: Broadcast[Array[String]] = sc.broadcast(ids)

    // 对原始数据进行过滤,只保留人们品类记录以及点击操作
    val filterRDD: RDD[UserVisitAction] = actionRDD.filter {
      action => {
        // 判断是否为点击操作
        if (action.click_category_id != -1) {
          // 判断是否为热门品类,  集合为String  id为Long 需要转换
          broadIds.value.contains(action.click_category_id.toString)
        } else {
          false
        }
      }
    }

    // 对RDD中的数据结构进行转换 (品类_SessionId,1)
    val catSessionRDD: RDD[(String, Int)] = filterRDD.map {
      action => {
        (action.click_category_id + "_" + action.session_id, 1)
      }
    }

    // 对当前品类下用户的点击次数进行汇总 (品类_SessionId,100)
    val catSessionSumRDD: RDD[(String, Int)] = catSessionRDD.reduceByKey(_+_)

    // 再次对结构进行转换  (品类,(SessionId,1))
    val catMapRDD: RDD[(String, (String, Int))] = catSessionSumRDD.map {
      case (catSession, sum) => {
        val catAndSession: Array[String] = catSession.split("_")
        (catAndSession(0), (catAndSession(1), sum))
      }
    }

    // 按照品类进行分组
    val groupByRDD: RDD[(String, Iterable[(String, Int)])] = catMapRDD.groupByKey()

    // 对分组后的数据按照点击次数进行降序排序
    val resRDD: RDD[(String, List[(String, Int)])] = groupByRDD.mapValues {
      datas => {
        datas.toList.sortWith {
          case (left, right) => {
            left._2 > right._2
          }
        }.take(10)
      }
    }

    resRDD.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
                           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)//支付次数
*/

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

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

在这里插入图片描述

代码实现

 读取原始数据
 将原始数据映射为样例类
 将原始数据根据session进行分组
 将分组后的数据根据时间进行排序(升序)
 将排序后的数据进行结构的转换(pageId,1)
 计算分母-将相同的页面id进行聚合统计(pageId,sum)
 计算分子-将页面id进行拉链,形成连续的拉链效果,转换结构(pageId-pageId2,1)
 将转换结构后的数据进行聚合统计(pageId-pageId2,sum)
 计算页面单跳转换率

package com.atguigu.req

import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.mutable.ListBuffer

object TopN_3 {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    // 读取数据
    val rdd: RDD[String] = sc.textFile("D:\\MyWork\\WorkSpaceIDEA\\scalaDemo\\spark\\input\\user_visit_action.txt")

    // 对读取的数据进行转换,封装为UserVisitAction,放到RDD中
    val actionRDD: RDD[UserVisitAction] = rdd.map {
      line => {
        // 对读取的每一行数据进行切分
        val fields: Array[String] = line.split("_")
        // 封装程UserVisitAction对象
        UserVisitAction(
          fields(0),
          fields(1).toLong,
          fields(2),
          fields(3).toLong,
          fields(4),
          fields(5),
          fields(6).toLong,
          fields(7).toLong,
          fields(8),
          fields(9),
          fields(10),
          fields(11),
          fields(12).toLong
        )
      }
    }

    // 需求三
    // 1. 计算每个页面被访问的次数,作为分母
    val pageRDD: RDD[(Long, Long)] = actionRDD.map {
      action => {
        (action.page_id, 1L)
      }
    }

    // Map[页面id,页面出现的次数]
    val fmIdsMap: Map[Long, Long] = pageRDD.reduceByKey(_+_).collect().toMap

    // 2. 计算分子
    // 将原始数据按照Session_id进行分组 (用户sessionId,当前session下页面访问情况)
    val sessionRDD: RDD[(String, Iterable[UserVisitAction])] = actionRDD.groupBy(_.session_id)

    // 对分组之后的页面,按照时间进行排序,得到的就是当前session下,用户的页面跳转顺序
    // (用户SessionId,List[(页面1-页面2,1)])
    val pageFlowRDD: RDD[(String, List[(String, Int)])] = sessionRDD.mapValues {
      datas => {
        val userActions: List[UserVisitAction] = datas.toList.sortWith {
          (left, right) => {
            left.action_time < right.action_time
          }
        }
        // 将用户对页面访问按照时间排序之后,可以将用户信息去掉了,只保留页面即可 -> 首页-详情-下单-支付-成功
        val pageIdsList: List[Long] = userActions.map(action => action.page_id)
        // 使用拉链对用户跳转情况进行合并
        // 首页-详情-下单-支付-成功
        // 详情-下单-支付-成功 =====> (首页,详情) (详情,下单)
        val pageFlow: List[(Long, Long)] = pageIdsList.zip(pageIdsList.tail)
        // 记录页面的跳转次数
        pageFlow.map {
          case (pageId1, pageId2) => {
            (pageId1 + "-" + pageId2, 1)
          }
        }
      }
    }
    // 3. 对RDD进行结构的转换,只保留页面跳转以及计数(首页-详情,1)
    val pageFlowMapRDD: RDD[(String, Int)] = pageFlowRDD.map(_._2).flatMap(list=>list)

    // 4. 计算页面跳转的总次数
    val pageToPage2SumRDD: RDD[(String, Int)] = pageFlowMapRDD.reduceByKey(_ + _)

    // 5. 页面单跳转换率计算(首页-详情,20)
    pageToPage2SumRDD.foreach{
      case (pageFlow,sum) => {
        // 分母
        // 将分母页面id获取到
        val pageIds: Array[String] = pageFlow.split("-")
        val fm: Long = fmIdsMap.getOrElse(pageIds(0).toLong,1L)
        println(pageFlow + "-------> " + sum.toDouble / fm)
      }
    }

    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
                           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)//支付次数
*/

你可能感兴趣的:(零)