基于SparkSql的日志分析实战

本文详细介绍了如何使用SparkSql进行日志分析,包括日志数据内容解析、用户行为意义、离线处理流程、需求分析和数据清洗。通过SparkSql统计最受欢迎的视频、课程访问,并按地市和流量进行排名。数据清洗过程中利用开源项目处理IP,结果通过ECharts和Zeppelin进行图形化展示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

日志数据内容

用户行为日志分析的意义

离线数据处理流程

需求分析

数据清洗

解析访问日志

使用github上的开源项目

对日志进行统计分析 

统计最受欢迎的TOPN的视频访问次数

按照地市统计imooc主站最受欢迎的TOPN课程

按流量统计imooc主站最受欢迎的TOPN课程

调优点

EChart展示图形化界面

静态数据展示

动态数据展示


日志数据内容

  1. 访问系统属性,操作系统,浏览器
  2. 访问特征,点击的url,从那个url跳转过来的,页面的停留时间
  3. 访问信息,session_id,访问ip(访问城市)

用户行为日志分析的意义

  • 网站的眼睛
  • 网站的神经,在网站的布局是否合理,导航是否清晰,内容是否合理
  • 网站的大脑,分析的目标,要做哪些优化。

离线数据处理流程

  • 1)数据采集 flume来采集
  • 2)数据清洗,采集过来的数据有很多不符合日志规范的脏数据
  • 3)数据处理。 按照我们的需求进行相应业务的统计的分析
  • 4)处理结果入库  结果可以存放到RDBMS\noSQl
  • 5)数据可视化展示  ECharts\HUE\Zeppelin

需求分析

  1. 统计imooc主站最受欢迎的课程/手机的TOpN访问次数
  2. 按照地市统计imooc主站最受欢迎的TOPN课程
  3. 按流量统计imooc主站最受欢迎的TOPN课程

数据清洗

  1. 需要先将日志文件中的日期格式进行转换

simpleDataformat是线程不安全的。

package com.rachel

import java.text.SimpleDateFormat
import java.util.{Date, Locale}

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

object DateUtils {
    //输入文件日期格式
    val YYYYMMDDHHMM_TIME_FORMAT = FastDateFormat.getInstance("dd/MMM/yyyy:HH:mm:ss Z",Locale.ENGLISH)
    //目标格式
    val TARGET_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss")

    def parse(time:String)={
      TARGET_FORMAT.format(new Date(getTime(time)))
    }

    def getTime(time:String) ={
      try{
        YYYYMMDDHHMM_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]"))
  }
}
package com.rachel

import org.apache.spark.sql.SparkSession
import org.apache.spark.SparkContext

object SparkStatFormatJob {
  def main(args: Array[String]): Unit = {
    val spark =  SparkSession.builder().appName("SparkStatFormatJob").master("local[2]").getOrCreate()
    val access = spark.sparkContext.textFile("file:///f:/java/10000_access.log")
    //access.take(10).foreach(println)
    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)
     // (ip,DateUtils.parse(time),url)
      DateUtils.parse(time)+"\t"+url+"\t"+traffic+"\t"+ip+"\t"
    }).saveAsTextFile("file:///f:/java/output_10000_access.log")

    spark.stop()

  }
}

解析访问日志

  • 使用SparkSql解析访问日志
  • 解析出课程编号、类型    
  • 根据IP解析出城市信息 val city = IpUtils.getCity(ip)
  • 使用SparkSql将访问时间按天进行分区。 
package com.rachel

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("cmsType", StringType),
      StructField("cmsId", LongType),
      StructField("traffic", LongType),
      StructField("ip", StringType),
      StructField("city", StringType),
      StructField("time", StringType),
      StructField("day", StringType)
    )
  }

  /**
    * 根据输入的每一行信息转换成输出的样式
    * @param log  输入的每一行记录信息
    */
  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 cms = url.substring(url.indexOf(domain) + domain.length)
      val cmsTypeId = cms.split("/")

      var cmsType = ""
      var cmsId = 0l
      if(cmsTypeId.length > 1) {
        cmsType = cmsTypeId(0)
        cmsId = cmsTypeId(1).toLong
      }

      val city = IpUtils.getCity(ip)
      val time = splits(0)
      val day = time.substring(0,10).replaceAll("-","")
      //这个row里面的字段要和struct中的字段对应上
      Row(url, cmsType, cmsId, traffic, ip, city, time, day)
    } catch {
      case e:Exception => Row(0)
    }
  }
}
package com.rachel

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

object SparkStatCleanJob {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder().appName("SparkStatCleanJob")
      .config("spark.sql.parquet.compression.codec","gzip")
      .master("local[2]").getOrCreate()
    val accessRDD = spark.sparkContext.textFile("file:///f:/java/access.log")
    val accessDF = spark.createDataFrame(accessRDD.map(x => AccessConvertUtil.parseLog(x)),
      AccessConvertUtil.struct)

//  accessDF.printSchema()
//  accessDF.show(false)
    accessDF.coalesce(1).write.format("parquet").mode(SaveMode.Overwrite)
      .partitionBy("day").save("file:///f:/java/imooc/clean2")
  }
}

这一过程中使用了ipdatabase项目来解析ip地址。所以需要在自己的项目中使用别人的项目。

使用github上的开源项目

  1. git clone https://github.com/wzhe06/ipdatabase
  2. #编译下载的项目(需要切换到项目目录下)
    mvn clean package -DskipTests
  3. #将依赖的jar包导到自己的maven仓库
    mvn install:install-file -Dfile=F:/开发文档/ipdatabase/target/ipdatabase-1.0-SNAPSHOT.jar -DgroupId=com.ggstart -DartifactId=ipdatabase -Dversion=1.0 -Dpackaging=jar

对日志进行统计分析 

统计最受欢迎的TOPN的视频访问次数

package com.rachel

import org.apache.spark.sql.{DataFrame, SparkSession}
import org.apache.spark.sql.functions._
object TopNStatJob {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder()
      .config("spark.sql.sources.partitionColumnTypeInference.enabled","false")
      .appName("TopNStatJob")
      .master("local[2]").getOrCreate()
    val accessDF =  spark.read.format("parquet").load("file:///f:/java/imooc/clean2")
    
//  accessDF.printSchema()
//  accessDF.show(false)
    
    videoAccessTopNStat(spark,accessDF)
    spark.stop()
  }

  /**
    * 最受欢迎的TOPN课程,
    * 这其中包含了两种方法操作DF,一种是函数,一种是以SQL的方式对DF进行统计分析
    * @param spark
    * @param accessDF
    */
  def videoAccessTopNStat(spark:SparkSession,accessDF:DataFrame): Unit ={
    import  spark.implicits._
//  val videoAccessTopDF =  accessDF.filter($"day" === "20170511" && $"cmsType" === "video")
//  .groupBy("day","cmsId").agg(count("cmsId").as("times")).orderBy($"times".desc)
   
    accessDF.createOrReplaceTempView("access_logs")
    val videoAccessTopDF = spark.sql("select day, cmsId, count(1) as times from access_logs " +
      "where day = '20170511' and cmsType = 'video'" +
      "group by day ,cmsId order by times desc")
    
    videoAccessTopDF.show(false)
  }
}

统计结果展示如下:

+--------+-----+------+
|day     |cmsId|times |
+--------+-----+------+
|20170511|14540|111027|
|20170511|4000 |55734 |
|20170511|14704|55701 |
|20170511|14390|55683 |
|20170511|14623|55621 |
|20170511|4600 |55501 |
|20170511|4500 |55366 |
|20170511|14322|55102 |
+--------+-----+------+
  • 将统计结果写入Mysql中,这个过程是从底层往上层封装的

1;编写MySQL的连接工具类

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值