【Spark SQL实战】日志分析(一)介绍、数据清洗

笔记目录:

【Spark SQL实战】日志分析(一)介绍、数据清洗

【Spark SQL实战】日志分析(二)实现项目需求

【Spark SQL实战】日志分析(三)结果可视化

目录

 

一、用户行为日志

1.日志介绍

2.日志数据内容

二、数据处理流程

1、数据采集

2、数据清洗

3、数据处理

4、处理结果入库

5、数据可视化

三、项目需求

四、数据清洗

五、数据清洗代码实现

1、第一次清洗:格式化原始日志数据

2、二次清洗:日志解析


一、用户行为日志

1.日志介绍

用户每次访问网站时所有行为数据(访问、浏览、点击、搜索....)用户行为轨迹、流量日志

2.日志数据内容

1)访问的系统属性:操作系统、浏览器等等

2)访问特征:点击url、从哪个url跳转过来的(referer)、页面上停留的时间等....

3)访问信息:session_id、访问ip(访问城市)等

二、数据处理流程

1、数据采集

  • Flume:web日志写入到HDFS

2、数据清洗

  • 脏数据处理
  • 可以使用Spark、Hive、MapReduce
  • 清洗完之后数据可以放在HDFS

3、数据处理

  • 按照需求进行业务统计和分析
  • 使用Spark、Hive、MapReduce或者其他分布式计算框架

4、处理结果入库

  • 结果存放在RDBMS、NoSQL

5、数据可视化

  • 通过图形化展示的方式展现出来:饼图、折线图等
  • 使用ECharts、HUE等

【离线数据处理架构流程图】 

三、项目需求

  • 需求一:统计慕课网最受欢迎的课程/手记的Top N访问次数
  • 需求二:按地市统计慕课网最受欢迎的的Top N课程
    • 根据ip地址提取城市信息
    • 窗口函数在Spark SQL的使用
  • 需求三:按流量统计慕课网最受欢迎的的Top N课程

四、数据清洗

  • 使用Spark SQL解析日志
  • 解析出课程编号、类型
  • 根据ip解析出城市信息
  • 使用Spark SQL将访问时间按天进行分区输出
  • 一般处的日志处理方式,我们需要分区的
    • 安装日志中的访问时间进行相应的的分区,比如:d(天),h(小时),m5(每五分钟一个分区)
  • 输入:访问时间、访问URL、耗费的流量、访问ip地址信息
  • 输出:URL、cmsType(video/article)、cmsId(编号)、流量、ip、城市信息、访问时间、天

五、数据清洗代码实现

原始日志:

183.162.52.7 - - [10/Nov/2016:00:01:02 +0800] "POST /api3/getadv HTTP/1.1" 200 813 "www.imooc.com" "-" cid=0&timestamp=1478707261865&uid=2871142&marking=androidbanner&secrect=a6e8e14701ffe9f6063934780d9e2e6d&token=f51e97d1cb1a9caac669ea8acc162b96 "mukewang/5.0.0 (Android 5.1.1; Xiaomi Redmi 3 Build/LMY47V),Network 2G/3G" "-" 10.100.134.244:80 200 0.027 0.027
10.100.0.1 - - [10/Nov/2016:00:01:02 +0800] "HEAD / HTTP/1.1" 301 0 "117.121.101.40" "-" - "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.16.2.3 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-" - - - 0.000
117.35.88.11 - - [10/Nov/2016:00:01:02 +0800] "GET /article/ajaxcourserecommends?id=124 HTTP/1.1" 200 2345 "www.imooc.com" "http://www.imooc.com/code/1852" - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36" "-" 10.100.136.65:80 200 0.616 0.616
182.106.215.93 - - [10/Nov/2016:00:01:02 +0800] "POST /socket.io/1/ HTTP/1.1" 200 94 "chat.mukewang.com" "-" - "android-websockets-2.0" "-" 10.100.15.239:80 200 0.004 0.004
10.100.0.1 - - [10/Nov/2016:00:01:02 +0800] "HEAD / HTTP/1.1" 301 0 "117.121.101.40" "-" - "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.16.2.3 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-" - - - 0.000
183.162.52.7 - - [10/Nov/2016:00:01:02 +0800] "POST /api3/userdynamic HTTP/1.1" 200 19501 "www.imooc.com" "-" cid=0&timestamp=1478707261847&uid=2871142&touid=2871142&page=1&secrect=a6e8e14701ffe9f6063934780d9e2e6d&token=3837a5bf27ea718fe18bda6c53fbbc14 "mukewang/5.0.0 (Android 5.1.1; Xiaomi Redmi 3 Build/LMY47V),Network 2G/3G" "-" 10.100.136.65:80 200 0.195 0.195
10.100.0.1 - - [10/Nov/2016:00:01:02 +0800] "HEAD / HTTP/1.1" 301 0 "117.121.101.40" "-" - "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.16.2.3 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-" - - - 0.000
114.248.161.26 - - [10/Nov/2016:00:01:02 +0800] "POST /api3/getcourseintro HTTP/1.1" 200 2510 "www.imooc.com" "-" cid=283&secrect=86b720f312c2b25da3b20e59e7c89780&timestamp=1478707261951&token=4c144b3f4314178b9527d1e91ecc0fac&uid=3372975 "mukewang/5.0.2 (iPhone; iOS 8.4.1; Scale/2.00)" "-" 10.100.136.65:80 200 0.007 0.008

1、第一次清洗:格式化原始日志数据

SparkStatFormatJob.scala

package log

import org.apache.spark.sql.SparkSession

/**
  *  第一步数据清洗:抽取需要的指定列的数据
  */
object SparkStatFormatJob {

  def main(args: Array[String]): Unit = {

    val spark=SparkSession.builder().appName("SparkStatFormatJob").master("local[2]").getOrCreate()

    // 获取文件
    val access=spark.sparkContext.textFile("/Users/Mac/testdata/10000_access.log")

    access.map(line=>{
      val splits=line.split(" ")
      val ip=splits(0)

      /**
        * 获取日志中完整的访问时间
        * 并转换日期格式
        */
      val time=splits(3)+" "+splits(4)
      val url=splits(11).replaceAll("\"","")
      val traffic=splits(9)

      DateUtils.parse(time)+"\t"+url+"\t"+traffic+"\t"+ip
    }).saveAsTextFile("/Users/Mac/testdata/spark-output/")

    spark.stop()
  }

}

读取文件的debug效果图:

DateUtils.scala

package log

import java.util.{Date, Locale}

import org.apache.commons.lang3.time.FastDateFormat

/**
  * 日期时间解析工具类
  */
object DateUtils {

  // 输入文件日期时间格式
  val SOURCE_TIME_FORMAT = FastDateFormat.getInstance("dd/MMM/yyyy:HH:mm:ss Z", Locale.ENGLISH)

  // 目标日期格式
  val TARGET_TIME_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss")

  /**
    * 获取时间
    */
  def parse(time: String) = {
    TARGET_TIME_FORMAT.format(new Date(getTime(time)))
  }

  /**
    * 获取输入日志时间
    */

  def getTime(time: String) = {
    try {
      SOURCE_TIME_FORMAT.parse(time.substring(time.indexOf("[") + 1, time.lastIndexOf("]"))).getTime
    } catch {
      case e: Exception => {
        0l
      }
    }

  }

  def main(args: Array[String]): Unit = {
    println(parse("[10/Nov/2016:00:01:02 +0800]"))
  }

}

清洗完的部分数据: 

è¿éåå¾çæè¿°

2、二次清洗:日志解析

输入:访问时间、访问URL、耗费的流量、访问的IP地址信息 
输出:URL,课程类型,课程编号,流量,IP,城市信息,访问时间,天

需要使用第三方包:ipdatabase

安装ipdatabase介绍:IP地址解析之github中ipdatabase项目的使用

IpUtils.scala

package log

import com.ggstar.util.ip.IpHelper

/**
  * ip解析工具类
  */
object IpUtils {

  def getCity(ip:String)={
    IpHelper.findRegionByIp(ip)
  }

  def main(args: Array[String]): Unit = {
    println(getCity("218.75.35.226"))
  }
}

AccessConvertUtil.scala

package log

import org.apache.spark.sql.Row
import org.apache.spark.sql.types.{LongType, StringType, StructField, StructType}

/**
  * 日志数据转换(输入==>输出)工具类
  */
object AccessConvertUtil {

  // 定义输出字段
  val struct = StructType(
    Array(
      StructField("url", StringType),
      StructField("classType", StringType),
      StructField("classId", LongType),
      StructField("traffic", LongType),
      StructField("ip", StringType),
      StructField("city", StringType),
      StructField("time", StringType),
      StructField("day", StringType)
    )
  )

  /**
    * 根据输入的每一行信息转换成输出的格式
    */
  def parseLog(log: String) = {

    try {
      val splits = log.split("\t")

      val url = splits(1)
      val traffic = splits(2).toLong
      val ip = splits(3)

      val domain = "http://www.imooc.com/"

      val className = url.substring(url.indexOf(domain) + domain.length)

      val classTypeId = className.split("/")

      var classType = ""
      var classId = 0l

      if (classTypeId.length > 1) {
        classType = classTypeId(0)
        if (classId.isInstanceOf[Long]){
          classId = classTypeId(1).toLong
        }else{
          classId=0l
        }

      }

      val city = IpUtils.getCity(ip)
      val time = splits(0)
      val day = time.substring(0, 10).replaceAll("-", "")

      // row要和struct对应上
      Row(url, classType, classId, traffic, ip, city, time, day)

    } catch {
      case e: Exception => Row("","",0l,0l,"","","","")
    }
  }

}

SparkStatCleanJob.scala

package log


import org.apache.spark.sql.{SaveMode, SparkSession}


/**
  * 使用spark完成数据清洗
  */
object SparkStatCleanJob {

  def main(args: Array[String]): Unit = {

    val spark = SparkSession.builder().appName("SparkStatCleanJob").master("local[2]").getOrCreate()

    val accessRDD = spark.sparkContext.textFile("/Users/Mac/testdata/access.log")

    val accessDF = spark.createDataFrame(accessRDD.map(x => AccessConvertUtil.parseLog(x)),
      AccessConvertUtil.struct)

//    accessDF.printSchema()
//    accessDF.show(false)

    // coalesce:设置输出文件的个数,默认3个
    accessDF.coalesce(1).write.format("parquet").mode(SaveMode.Overwrite)
      .partitionBy("day").save("/Users/Mac/testdata/log_clean/")
  }
}

 
这里写图片描述

发布了30 篇原创文章 · 获赞 20 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/YiWangJiuShiXingFu/article/details/101367578