Spark数据清洗(日志信息)

模拟日志数据格式

//日志格式
2018-09-04T20:27:31+08:00	http://datacenter.bdqn.cn/logs/user?actionBegin=1536150451690&actionClient=Mozilla%2F5.0+%28Windows+NT+10.0%3B+WOW64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F63.0.3239.132+Safari%2F537.36&actionEnd=1536150451783&actionName=viewQuestionAnalysis&actionTest=0&actionType=3&actionValue=261533&clientType=001_bdqn&examType=001&ifEquipment=web&questionId=35406&skillIdCount=0&userSID=E02CD8FDB92BD9A3D777B7425D4FE8A0.kgc-tiku-node1.kgc-tiku-node1&userUID=261533&userUIP=115.200.118.135	GET	200	192.168.168.79	-	-	Apache-HttpClient/4.1.2 (java 1.5)

//日志可根据Table拆分为八个字段
event_time:	2018-09-04T20:27:31+08:00
	
url:http://datacenter.bdqn.cn/logs/useractionBegin=1536150451690&actionClient=Mozilla%2F5.0+%28Windows+NT+10.0%3B+WOW64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F63.0.3239.132+Safari%2F537.36&actionEnd=1536150451783&actionName=viewQuestionAnalysis&actionTest=0&actionType=3&actionValue=261533&clientType=001_bdqn&examType=001&ifEquipment=web&questionId=35406&skillIdCount=0&userSID=E02CD8FDB92BD9A3D777B7425D4FE8A0.kgc-tiku-node1.kgc-tiku-node1&userUID=261533&userUIP=115.200.118.135	

method:GET	

status:200	

sip:192.168.168.79	

user_uip:-	

action_prepend:-	

action_client:Apache-HttpClient/4.1.2 (java 1.5)

清洗任务

  • 读入日志文件并转化为RDD[Row]类型
    1. 按照Tab切割数据
    2. 过滤掉字段数量少于8个的
  • 对数据进行清洗
    1. 按照第一列和第二列对数据进行去重
    2. 过滤掉状态码非200
    3. 过滤掉event_time为空的数据
    4. 将url按照”&”以及”=”切割
  • 保存数据
    1. 将数据写入mysql表中

任务实现(清洗)

import java.util.Properties
import org.apache.commons.lang.StringUtils
import org.apache.spark.rdd.RDD
import org.apache.spark.sql._
import org.apache.spark.sql.types.{StringType, StructField, StructType}
import scala.collection.mutable

object DataClear {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder().appName("clearDemo").master("local[*]").getOrCreate()
    import spark.implicits._
    val sc = spark.sparkContext
    
    //加载日志文件
    val rdd: RDD[String] = sc.textFile("in/test.log")
   
    //将日志按Table分割
    val rdd2: RDD[Array[String]] = rdd.map(x=>x.split("\t"))
    
    //将切割后的rdd已Row对象格式生成新的Rdd(trim为去除空格)
    val rdd3: RDD[Row] = rdd2.filter(x=>x.length==8).map(x=>Row(x(0).trim,x(1).trim,x(2).trim,x(3).trim,x(4).trim,x(5).trim,x(6).trim,x(7).trim))
    
    //定义schema,等下用于创建DataFrame
    val schema = StructType(Array(
      StructField("event_time", StringType),
      StructField("url", StringType),
      StructField("method", StringType),
      StructField("status", StringType),
      StructField("sip", StringType),
      StructField("user_uip", StringType),
      StructField("action_prepend", StringType),
      StructField("action_client", StringType)
    ))

    //将存放row对象的Rdd加上Schema来创建DataFrame
    val df: DataFrame = spark.createDataFrame(rdd3,schema)
    println("-----------------开始清洗日志数据----------------------")

    //根据的第一列第二列进行去重
    val df1_1: Dataset[Row] = df.dropDuplicates("event_time","url")

    //过滤状态值不是200的数据
    val df1_2: Dataset[Row] = df1_1.filter(x=>x(3)=="200")

    //过滤event_time列中为空的数据
    //filter(x=>x(0).equals("")==false):这样写也没有问题
    val df1_3: Dataset[Row] = df1_2.filter(x=>StringUtils.isNotEmpty(x(0).toString))

    //对日志url字段进行拆分
    val frame: DataFrame = df1_3.map(row => {

      //读取row对象中url字段的数据:getAs[类型]("字段名")
      //根据“?”进行拆分(问号是特殊符合需要进行转义)
      val urlArr: Array[String] = row.getAs[String]("url").split("\\?")

      //创建一个空Map集合
      var map: Map[String, String] = Map("params" -> "null")

      //过滤url分割后长度不为2的情况
      if (urlArr.length == 2) {

        //将url拆分的第二段在根据“&”在拆分
        //在将拆分后的每个字段按"="拆分
        //过滤拆分后的字段不成对的日志数据
        //将拆分后的字段与值以元组的形式组成
        //最后载入预先创建的Map中
        map = urlArr(1).split("&").map(x => x.split("=")).filter(x => x.length == 2).map(x => (x(0), x(1))).toMap
      }
      (
        //(现在还处于Map中,注意括号)
        //将所有字段包括拆分的url中的字段同在新DF中展示出来
        //Tuple最大长度为22,这里超了,随便注掉了两个
        row.getAs[String]("event_time"),
        map.getOrElse("actionBegin", ""),
        map.getOrElse("actionClient", ""),
        map.getOrElse("actionEnd", ""),
        map.getOrElse("actionName", ""),
        map.getOrElse("actionTest", ""),
        map.getOrElse("actionType", ""),
        map.getOrElse("actionValue", ""),
        map.getOrElse("clientType", ""),
        map.getOrElse("ifEquipment", ""),
        //        map.getOrElse("skillIdCount", ""),
        //        map.getOrElse("skillLevel", ""),
        map.getOrElse("testType", ""),
        map.getOrElse("userSID", ""),
        map.getOrElse("userUID", ""),
        map.getOrElse("userUIP", ""),
        row.getAs[String]("method"),
        row.getAs[String]("status"),
        row.getAs[String]("sip"),
        row.getAs[String]("user_uip"),
        row.getAs[String]("action_prepend"),
        row.getAs[String]("action_client")
      )

    }).toDF()

    //现在frame是由RDD生成的,没有配Schema,现在解决一下
    //将frame重新转成rdd
    val rddFrame: RDD[Row] = frame.rdd

    //配置rddFrame中的字段信息
    val schemaDF = StructType{Array(
      StructField("event_time", StringType, false),
      StructField("actionBegin", StringType, false),
      StructField("actionClient", StringType, false),
      StructField("actionEnd", StringType, false),
      StructField("actionName", StringType, false),
      StructField("actionTest", StringType, false),
      StructField("actionType", StringType, false),
      StructField("actionValue", StringType, false),
      StructField("clientType", StringType, false),
      StructField("ifEquipment", StringType, false),
      StructField("testType", StringType, false),
      StructField("userSID", StringType, false),
      StructField("userUID", StringType, false),
      StructField("userUIP", StringType, false),
      StructField("method", StringType, false),
      StructField("status", StringType, false),
      StructField("sip", StringType, false),
      StructField("user_uip", StringType, false),
      StructField("action_prepend", StringType, false),
      StructField("action_client", StringType, false)

    )}

    //将rddFrame与Schema生成新的DF
    val orgDF: DataFrame = spark.createDataFrame(rddFrame,schemaDF)

    //现在日志已经是我们想要的样子了,下一步将写入MySQL
    //配置连接信息
    val url="jdbc:mysql://192.168.95.99:3306/sparkTest"
    val prop=new Properties()
    prop.setProperty("user","root")
    prop.setProperty("password","root123")
    prop.setProperty("driver","com.mysql.jdbc.Driver")

    //写入操作
    println("开始写入MySQL")
    orgDF.write.mode("overwrite").jdbc(url,"orgDF",prop)
    println("写入完成")


  }

}

应用任务

  1. 计算用户的次日留存率
  2. 求当天新增用户总数n
  3. 求当天新增的用户ID与次日登录的用户ID的交集,得出新增用户次日登录总数m (次日留存数)

任务实现(应用)

import java.text.SimpleDateFormat
import java.util.Properties
import org.apache.spark.sql.expressions.UserDefinedFunction
import org.apache.spark.sql._

object UserAnalysis {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder().appName("clearDemo").master("local[*]").getOrCreate()
    import spark.implicits._
    val sc = spark.sparkContext

    //配置连接MySQL信息
    val url="jdbc:mysql://192.168.95.99:3306/sparkTest"
    val prop=new Properties()
    prop.setProperty("user","root")
    prop.setProperty("password","root123")
    prop.setProperty("driver","com.mysql.jdbc.Driver")

    //读取过滤后的日志数据
    val detailDF: DataFrame = spark.read.jdbc(url,"orgDF",prop)

    //自定义函数UDF:将日期转为时间戳
    val changeTime: UserDefinedFunction = spark.udf.register("changeTime", (v: String) => {

      //获取SimpleDateFormat对象指定日期格式,将传入的日期进行切割去除多余部分,最后用getTime函数获取时间戳
      val time: Long = new SimpleDateFormat("yyyy-MM-dd").parse(v.substring(0, 10)).getTime

      time
    })

    //日志应用:每日用户留存率

    //注册用户数
    val regUser: DataFrame = detailDF
      //过滤出actionName字段为Registered的数据,即为首次注册的用户数据
      .filter(detailDF("actionName")==="Registered")
      //仅展示用户ID“userUID”以及处理过的"event_time"二个字段的数据,分别定义别名
      .select($"userUID" as ("regUID"),changeTime($"event_time") as ("rt"))

    //登录用户数
    val sigUser: DataFrame = detailDF
      //过滤出actionName字段为Signin的数据,即为登录用户
      .filter(detailDF("actionName")==="Signin")
      //仅显示用户ID“userUID”以及处理过后的“event_time”这两个字段数据,分别定义别名
      .select($"userUID" as ("sigUID"),changeTime($"event_time") as ("st"))
      //因为登录信息存在多次的情况,需要去重
      .distinct()

    //每日注册用户数
    val regUserCountDay: DataFrame = regUser
      //根据注册时间进行分组
      .groupBy("rt")
      //统计每天注册数
      .count()
      //将统计列改名为regQt
      .withColumnRenamed("count","regQt")

    //每日注册用户登录数
    //关联注册用户表与登录表
    val regAndSig: DataFrame =regUser.join(sigUser, regUser("regUID") === sigUser("sigUID"))
      //仅显示用户ID“regUID”,用户注册时间"rt",用户登录时间“st”
      .select("regUID", "rt", "st")
      //过滤出用户注册时间与登录时间差为一天的用户数据:此时的日期为时间戳,一天的时间值为86400000
      .filter(regUser("rt")===sigUser("st")-86400000)
      //此时仅需要用户注册时间"rt"
      .select("rt")
      //对时间进行分组
      .groupBy("rt")
      //统计每天新注册用户的登录情况
      .count()
      //将统计列改名为sigQt
      .withColumnRenamed("count","sigQt")

    //关联每日用户注册数与每日注册用户登录数
    val avgReg: Dataset[(Long, Long, Long, Double)] = regAndSig.join(regUserCountDay, "rt")
      //展示日期,每日用户注册数,每日注册用户登录数,以及注册数与登录数的比值
      .map(x => (x.getAs[Long]("rt"), x.getAs[Long]("sigQt"), x.getAs[Long]("regQt"), x.getAs[Long]("sigQt").toDouble / x.getAs[Long]("regQt")))
  }

}

  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Spark离线数据清洗可以使用SparkSQL和DataFrame API来实现。具体步骤如下: 1.读取数据:使用SparkContext的textFile()方法读取数据文件,返回一个RDD[String]类型的对象。 2.将RDD[String]转换为DataFrame:使用SparkSession的createDataFrame()方法将RDD[String]转换为DataFrame类型的对象。 3.过滤数据:使用DataFrame API中的filter()方法过滤掉不符合条件的数据。 4.处理数据:使用DataFrame API中的各种方法对数据进行处理,例如使用withColumnRenamed()方法重命名列名,使用drop()方法删除不需要的列等。 5.保存数据:使用DataFrame API中的write()方法将处理后的数据保存到指定的文件中。 下面是一个示例代码,假设我们有一个日志文件test.log,其中每行数据由8个字段组成,字段之间使用制表符分隔: ```scala import org.apache.spark.sql.{Row, SparkSession} val spark = SparkSession.builder() .appName("Data Cleaning") .master("local[*]") .getOrCreate() // 读取数据 val linesRDD = spark.sparkContext.textFile("test.log") // 将RDD[String]转换为RDD[Row]的形式,并过滤字段数少于8的日志 val rowRDD = linesRDD.map(_.split("\t")) .filter(_.length == 8) .map(x => Row(x(0).trim, x(1).trim, x(2).trim, x(3).trim, x(4).trim, x(5).trim, x(6).trim, x(7).trim)) // 定义DataFrame的schema val schema = spark.read .option("header", "true") .option("inferSchema", "true") .csv("test.log") .schema // 将RDD[Row]转换为DataFrame val df = spark.createDataFrame(rowRDD, schema) // 对数据进行处理 val cleanedDF = df .withColumnRenamed("col1", "new_col1") .drop("col2") // 保存数据 cleanedDF.write .option("header", "true") .csv("cleaned_data") ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值