数仓demo-点击流 笔记

文章目录

  • 数仓demo-点击流分析
    • 日志数据格式
    • 数据仓库-ETL处理
    • 点击流模型pageviews
    • 点击流模型visit
  • 数据入库
    • 1.1. 原始日志数据表
    • 1.2.点击流模型
    • 1.3. 点击流visit模型表
  • 点击流需求分析
    • 代码实现

数仓demo-点击流分析

用户行为日志

日志数据格式

日志数据内容样例(批量生成的假数据,用来做demo用的)

f5dd685d-6b83-4e7d-8c37-df8797812075 222.68.172.190 - - 2018-11-01 14:34:57 "GET /images/my.jpg HTTP/1.1" 200 19939 "http://www.angularjs.cn/A00n" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36" 

字段解析:
1 、用户id信息-uid: f5dd685d-6b83-4e7d-8c37-df8797812075
2、访客ip地址: 222.68.172.190
3、访客用户信息: - -
4、请求时间:2018-11-01 14:34:57
5、请求方式:GET
6、请求的url:/images/my.jpg
7、请求所用协议:HTTP/1.1
8、响应码:200
9、返回的数据流量:19939
10、访客的来源url:http://www.angularjs.cn/A00n
11、访客所用浏览器:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36
注意:分隔符为空格

数据仓库-ETL处理

点击流概念

**点击流(Click Stream)**是指用户在网站上持续访问的轨迹。

注重用户浏览网站的整个流程。

用户对网站的每次访问包含了一系列的点击动作行为,这些点击行为数据就构成了点击流数据(Click Stream Data),它代表了用户浏览网站的整个流程。

点击流和网站日志是两个不同的概念,点击流是从用户的角度出发,注重用户浏览网站的整个流程;

而网站日志是面向整个站点,它包含了用户行为数据、服务器响应数据等众多日志信息,我们通过对网站日志的分析可以获得用户的点击流数据。

点击流模型完全是业务模型,相关概念由业务指定而来。

由于大量的指标统计从点击流模型中更容易得出,所以在预处理阶段,可以使用spark程序来生成点击流模型的数据。

在点击流模型中,存在着两种模型数据:PageViewsVisits

点击流模型pageviews

Pageviews模型数据专注于用户每次会话(session)的识别,以及每次session内访问了几步和每一步的停留时间。
在日志数据分析中,通常把前后两条访问记录时间差在30分钟以内算成一次会话。如果超过30分钟,则把下次访问算成新的会话开始。
大致步骤如下:
在所有访问日志中找出该用户的所有访问记录
把该用户所有访问记录按照时间正序排序
计算前后两条记录时间差是否为30分钟
如果小于30分钟,则是同一会话session的延续
如果大于30分钟,则是下一会话session的开始
用前后两条记录时间差算出上一步停留时间
最后一步和只有一步的 业务默认指定页面停留时间60s

点击流模型visit

Visit模型专注于每次会话session内起始、结束的访问情况信息。

比如用户在某一个会话session内,进入会话的起始页面和起始时间,会话结束是从哪个页面离开的,离开时间,本次session总共访问了几个页面等信息。

大致步骤如下:

  1. 在pageviews模型上进行梳理
  2. 在每一次回收session内所有访问记录按照时间正序排序
  3. 第一天的时间页面就是起始时间页面
  4. 业务指定最后一条记录的时间页面作为离开时间和离开页面

数据入库

1. 创建ODS层数据表

1.1. 原始日志数据表

drop table if exists itcast_ods.ods_weblog_origin;
create table itcast_ods.ods_weblog_origin(
  valid Boolean,  
  remote_addr string,  
  remote_user string,
  time_local string,
  request string,
  status string,
  body_bytes_sent string,
  http_referer string,
  http_user_agent string,
  guid string)
  partitioned by (dt string) 
  STORED AS PARQUET;

valid Boolean, --判断数据是否合法
remote_addr string, --记录客户端的ip地址
remote_user string, --记录客户端用户名称,忽略属性"-"
time_local string, --记录访问时间与时区
request string, --记录请求的url与http协议
status string, --记录请求状态;成功是200
body_bytes_sent string, --记录发送给客户端文件主体内容大小
http_referer string, --用来记录从那个页面链接访问过来的
http_user_agent string, --记录客户浏览器的相关
guid string) --用户id信息

注意事项:
parquet中字段数据类型要与hive表字段类型保持一致!!

1.2.点击流模型

drop table if exists itcast_ods.ods_click_pageviews;
create table itcast_ods.ods_click_pageviews(
session string,
remote_addr string,
time_local string,
request string,
visit_step int,
page_staylong string,
http_referer string,
http_user_agent string,
body_bytes_sent string,
status string)
partitioned by (dt string)
STORED AS PARQUET;

session string, //session
remote_addr string, //ip地址
time_local string, //访问时间
request string, //请求路径
visit_step int, //访问第几个页面
page_staylong string, //停留时长
http_referer string, //用来记录从那个页面链接访问过来的
http_user_agent string, //记录客户浏览器的相关
body_bytes_sent string, //记录发送给客户端文件主体内容大小
status string //状态//用户ID

1.3. 点击流visit模型表

drop table if exist itcast_ods.ods_click_stream_visit;
create table itcast_ods.ods_click_stream_visit(
session   string,
remote_addr string,
inTime   string,
outTime   string,
inPage   string,
outPage   string,
referal   string,
pageVisits  int)
partitioned by (dt string)
STORED AS PARQUET;

session string, //session
remote_addr string, //IP地址
inTime string, //进入时间
outTime string, //离开时间
inPage string, //进入的页面
outPage string, //离开的页面
referal string, //用来记录从那个页面链接访问过来的
pageVisits int) //访问页面数量

点击流需求分析

数仓demo-点击流 笔记_第1张图片
数仓demo-点击流 笔记_第2张图片

代码实现

package clickLog

import java.util.UUID

import clickLog.bean.{PageViewsBeanCase, VisitBeanCase, WebLogBean, WeblogBeanCase}
import clickLog.util.DateUtil
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, SparkSession}
import scala.collection.mutable
import scala.collection.mutable.ListBuffer

/*
  点击流项目的实现
    1、对数据进行预处理,过滤掉无效的数据,将有效数据写入hive表
    2  计算用户点击流程,将有效数据写入hive表
    3   最终计算的visit数据写入HIVE表
 */
object ClicklogApp {
  //程序入口
  def main(args: Array[String]): Unit = {
    /*
     1、对数据进行预处理,过滤掉无效的数据,将有效数据写入hive表     itcast_ods.ods_weblog_origin
     */
    //准备静态数据
    // 用来存储网站url分类数据
    val pages = new mutable.HashSet[String]()

    //初始化静态资源路径集合
    def initlizePages(): Unit = {
      pages.add("/about")
      pages.add("/black-ip-list/")
      pages.add("/cassandra-clustor/")
      pages.add("/finance-rhive-repurchase/")
      pages.add("/hadoop-family-roadmap/")
      pages.add("/hadoop-hive-intro/")
      pages.add("/hadoop-zookeeper-intro/")
      pages.add("/hadoop-mahout-roadmap/")
    }


    //1 初始化 spark session
    val spark: SparkSession = SparkSession.builder().appName("Clicklog").master("local[*]").getOrCreate()
    //获取SparkContext
    val sc: SparkContext = spark.sparkContext
    //设置日志级别
    sc.setLogLevel("WARN")

    //2 读取数据
    val logDatas: RDD[String] = sc.textFile("E:\\\\access.log.20181101.dat_new.bak")

    //3 将每一条数据穿换成WebLogBean{使用WeblogBean操作数据更方便}
    val webLogBeanRDD: RDD[WebLogBean] = logDatas.map(WebLogBean(_))

    //4 对数据进行过滤(删掉无效数据)
    val activeWebLogBeans = webLogBeanRDD.filter(webLogBean => {
      //若bean不为空,并且数据有效  那么保留该数据,反之删除数据
      if (webLogBean != null && webLogBean.valid) {
        true
      } else {
        false
      }
    })

    //初始化静态数据
    initlizePages()

    //5 过滤掉静态数据
    val noStaticActiveWebLogBeans: RDD[WebLogBean] = activeWebLogBeans.filter(activeWebLogBean => {
      //静态数据中包含用户访问的路径,表示这个数据是无效的
      if (pages.contains(activeWebLogBean.request)) {
        false
      } else {
        //表示数据有效
        true
      }
    })

    import spark.implicits._
    //noStaticActiveWebLogBeans.toDF()

    //6 WebLogBean转换成WeblogBeanCase-->因为WeblogBeanCase的结构与最终存储数据的表的结构相同

    val weblogBeanCaseRDD: RDD[WeblogBeanCase] = noStaticActiveWebLogBeans.map(webLogBean => {
      WeblogBeanCase(webLogBean.valid, webLogBean.remote_addr, webLogBean.remote_user, webLogBean.time_local,
        webLogBean.request, webLogBean.status, webLogBean.body_bytes_sent, webLogBean.http_referer, webLogBean.http_user_agent
        , webLogBean.guid)
    })

/*
    //7 将数据写入到hive表
    val weblogBeanCaseDF: DataFrame = weblogBeanCaseRDD.toDF()
    //将数据写入到HDFS
    weblogBeanCaseDF.write.mode("overwrite").parquet("hdfs://node01:8020/user/hive/warehouse/itcast_ods.db/ods_weblog_origin/dt=20191212/")
*/

////////////////////////////////////////////////////////////////////////////////////

  //实现第二个需求     计算用户点击流程,将有效数据写入hive表
    //weblogBeanCaseRDD  包含所有用户的数据

    //1 根据用户ID 对数据进行分组。
    val  userWebLogList: RDD[(String, Iterable[WeblogBeanCase])] = weblogBeanCaseRDD.groupBy(weblogBeanCase=>weblogBeanCase.guid)

    //2 得到的数据是一个用户的数据都在一起,  按照浏览的时间排序。  某一个用户浏览的顺序数据
    val pageViewsBeanCaseRDD: RDD[PageViewsBeanCase] = userWebLogList.flatMap(oneUserWebLog => {
      //一个用户的数据
      //获取用户的userId
      val userId: String = oneUserWebLog._1
      // 获取用户的浏览记录(按照访问时间排序)
      // _  就是WeblogBeanCase
      val webLogList: List[WeblogBeanCase] = oneUserWebLog._2.toList.sortBy(_.time_local)

      //初始化session
      var session = UUID.randomUUID().toString

      //初始化用户访问的第几步
      var setp = 1

      //初始化存储PageViewsBeanCase的list
      var pageViewsBeanCaseList = ListBuffer[PageViewsBeanCase]()

      import scala.util.control.Breaks._

      breakable {
        //3 遍历每个用户的数据,
        for (num <- 0 until (webLogList.size - 1)) {
          //获取当前浏览记录
          var cruurentWebLog = webLogList(num)

          //3.1 数据量可能为一条,浏览时间默认60s
          if (webLogList.size == 1) {
            //封装PageViewsBeanCase
            val pageViewsBeanCase: PageViewsBeanCase = PageViewsBeanCase(session, cruurentWebLog.remote_addr, cruurentWebLog.time_local, cruurentWebLog.request,
              setp, 60 + "", cruurentWebLog.http_referer, cruurentWebLog.http_user_agent, cruurentWebLog.body_bytes_sent,
              cruurentWebLog.status, cruurentWebLog.guid
            )
            //将刚刚计算的PageViewsBeanCase  保存到PageViewsBeanCaseList中【最终写入HDFS】
            pageViewsBeanCaseList += pageViewsBeanCase
            //重新生成新的uuid
            session = UUID.randomUUID().toString
            break()
          }

          //若没有进入上面的if  表示有多条数据
          //3.2 数据量有可能是多条  1  2  3  4  5
          //若是第一条数据我们跳过第一天,进入第二个循环,得到第二天数据

          breakable {
            if (num == 0) {
              //num==0表示这是第一天的数据
              //跳过第一天
              break()
            }
            //先获取第一条数据时间{因为第一天已经跳过,所有cruurentWebLog 为第二天条的数据}
            val upDataTime: String = webLogList(num - 1).time_local

            //获取第二条数据的时间
            val nextDataTime: String = cruurentWebLog.time_local

            //求两个界面的时间差
            //第二条数据的时间 - 第一条数据的时间=第一个页面的停留时长
            val diffTime: Long = DateUtil.getTimeDiff(upDataTime, nextDataTime)

            //获取上一个数据的   WeblogBeanCase
            val upWeblogBean: WeblogBeanCase = webLogList(num - 1)

            if (diffTime < 30 * 60 * 1000) {
              //3.2.1 两个数据之间的间隔在30分钟之内
              //封装PageViewsBeanCase   这个PageViewsBeanCase 是第一天数据的PageViewsBeanCase
              val beanCase: PageViewsBeanCase = PageViewsBeanCase(session, upWeblogBean.remote_addr, upWeblogBean.time_local, upWeblogBean.request,
                setp, diffTime + "", upWeblogBean.http_referer, upWeblogBean.http_user_agent, upWeblogBean.body_bytes_sent,
                upWeblogBean.status, upWeblogBean.guid)
              //添加到结果集
              pageViewsBeanCaseList += beanCase
              //session不需要更新
              //setp+1
              setp += 1

            } else {
              //3.2.2 两个数据之间的间隔超过30分钟,换另外一个session会话
              //封装PageViewsBeanCase
              val beanCase: PageViewsBeanCase = PageViewsBeanCase(session, upWeblogBean.remote_addr, upWeblogBean.time_local, upWeblogBean.request,
                setp, 60 + "", upWeblogBean.http_referer, upWeblogBean.http_user_agent, upWeblogBean.body_bytes_sent,
                upWeblogBean.status, upWeblogBean.guid)
              //添加到结果集
              pageViewsBeanCaseList += beanCase


              //为下一个会话准备数据
              //session需要更新
              //重新生成session
              session = UUID.randomUUID().toString
              //setp 归1
              setp = 1
            }

            //最后一条数据
            if (webLogList.size - 1 == num) {
              //3.3最后一条数据  浏览时间默认60s
              //封装PageViewsBeanCase
              val lastViewsBeanCase: PageViewsBeanCase = PageViewsBeanCase(session, cruurentWebLog.remote_addr, cruurentWebLog.time_local, cruurentWebLog.request,
                setp, 60 + "", cruurentWebLog.http_referer, cruurentWebLog.http_user_agent, cruurentWebLog.body_bytes_sent,
                cruurentWebLog.status, cruurentWebLog.guid
              )
              //添加到结果集
              pageViewsBeanCaseList += lastViewsBeanCase
            }
          }
        }
      }
      pageViewsBeanCaseList
    }
    )
/*
    //每个用户的最终
    //写入Hive   itcast_ods.ods_click_pageviews
    pageViewsBeanCaseRDD.toDF().write.mode("overwrite")
    .parquet("hdfs://node01:8020/user/hive/warehouse/itcast_ods.db/ods_click_pageviews/dt=20191212/")
*/
////////////////////////////////////////////////////////////////////////////////////////////////

     //目标计算每一个session 用户id    session     第一次进入的页面    最后一次进入的页面   第一次进入的时间点    最后一次进入的时间点   总共多少次
    //4  将数据写入HIVE 的 itcast_ods.ods_click_pageviews

    //1、根据session   对数据进行分组
    val sessionGroupDatas: RDD[(String, Iterable[PageViewsBeanCase])] = pageViewsBeanCaseRDD.groupBy(bean=>bean.session)

    //2、获得到一个session内有哪些页面
    val visitBeanCaseRDD: RDD[VisitBeanCase] = sessionGroupDatas.map(pageViewsBeanCase => {
      val session: String = pageViewsBeanCase._1
      //3、对一个绘画内的数据进行排序    pageViewsBeanCases是所有的session
      val pageViewsBeanCases: List[PageViewsBeanCase] = pageViewsBeanCase._2.toList.sortBy(_.visit_step)

      //4、获取第一个访问页面数据
      val firstPageViewsBeanCase: PageViewsBeanCase = pageViewsBeanCases(0)

      //5、获取最后一个访问页面数据
      val lastPageViewsBeanCase: PageViewsBeanCase = pageViewsBeanCases(pageViewsBeanCases.size - 1)

      //6、封装VisitBeanCase
      VisitBeanCase(session, firstPageViewsBeanCase.remote_addr, firstPageViewsBeanCase.time_local, lastPageViewsBeanCase.time_local,
        firstPageViewsBeanCase.request, lastPageViewsBeanCase.request, firstPageViewsBeanCase.htp_referer, pageViewsBeanCases.size)
    })
    //将数据写入HIVE  itcast_ods.ods_click_stream_visit
    visitBeanCaseRDD.toDF().write.mode("overwrite")
      .parquet("hdfs://node01:8020/user/hive/warehouse/itcast_ods.db/ods_click_stream_visit/dt=20191212/")

  }
}

你可能感兴趣的:(数据仓库,数据仓库)