spark日志分析(三)—— 手机行为日志

一、数据描述

样例分析使用的是两条手机用户产生的日志信息:op.log
内容如下:


1593136280858|{"cm":{"ln":"-55.0","sv":"V2.9.6","os":"8.0.4","g":"C6816QZ0@gmail.com","mid":"489","nw":"3G","l":"es","vc":"4","hw":"640*960","ar":"MX","uid":"489","t":"1593123253541","la":"5.2","md":"sumsung-18","vn":"1.3.4","ba":"Sumsung","sr":"I"},"ap":"app","et":[{"ett":"1593050051366","en":"loading","kv":{"extend2":"","loading_time":"14","action":"3","extend1":"","type":"2","type1":"201","loading_way":"1"}},{"ett":"1593108791764","en":"ad","kv":{"activityId":"1","displayMills":"78522","entry":"1","action":"1","contentType":"0"}},{"ett":"1593111271266","en":"notification","kv":{"ap_time":"1593097087883","action":"1","type":"1","content":""}},{"ett":"1593066033562","en":"active_background","kv":{"active_source":"3"}},{"ett":"1593135644347","en":"comment","kv":{"p_comment_id":1,"addtime":"1593097573725","praise_count":973,"other_id":5,"comment_id":9,"reply_count":40,"userid":7,"content":"辑赤蹲慰鸽抿肘捎"}}]}
1593136280858|{"cm":{"ln":"-114.9","sv":"V2.7.8","os":"8.0.4","g":"NW0S962J@gmail.com","mid":"490","nw":"3G","l":"pt","vc":"8","hw":"640*1136","ar":"MX","uid":"490","t":"1593121224789","la":"-44.4","md":"Huawei-8","vn":"1.0.1","ba":"Huawei","sr":"O"},"ap":"app","et":[{"ett":"1593063223807","en":"loading","kv":{"extend2":"","loading_time":"0","action":"3","extend1":"","type":"1","type1":"102","loading_way":"1"}},{"ett":"1593095105466","en":"ad","kv":{"activityId":"1","displayMills":"1966","entry":"3","action":"2","contentType":"0"}},{"ett":"1593051718208","en":"notification","kv":{"ap_time":"1593095336265","action":"2","type":"3","content":""}},{"ett":"1593100021275","en":"comment","kv":{"p_comment_id":4,"addtime":"1593098946009","praise_count":220,"other_id":4,"comment_id":9,"reply_count":151,"userid":4,"content":"抄应螟皮釉倔掉汉蛋蕾街羡晶"}},{"ett":"1593105344120","en":"praise","kv":{"target_id":9,"id":7,"type":1,"add_time":"1593098545976","userid":8}}]}

在这里插入图片描述


初步分析可知,日志整体并不是一个 json 的格式文件,每条日志可分为两部分, 1593136280858 | {json} ,使用 split("|") 切分,然后对日志主体 json 格式内容分析

日志中有部分乱码,但不影响分析,此处不做处理

这里推荐一个网站: https://www.json.cn/

取第一条数据 json 部分主体内容,复制粘贴,可以帮助快速精准的分析 json 的结构内容

如下如,可以看到主体 json 格式内容分为三部分: cm , ap , et
在这里插入图片描述
再对 cm , ap , et 分别进行展开


展开 cm

在这里插入图片描述


展开 ap ,只有一个键值对

"ap":"app"

展开 et

在这里插入图片描述
et 中又包含了5个 json串 ,每个 json串中有三个键值对: ett , en , kv

et 内容如下所示:

    "et":[
        {
            "ett":"1593050051366",
            "en":"loading",
            "kv":{
                "extend2":"",
                "loading_time":"14",
                "action":"3",
                "extend1":"",
                "type":"2",
                "type1":"201",
                "loading_way":"1"
            }
        },
        {
            "ett":"1593108791764",
            "en":"ad",
            "kv":{
                "activityId":"1",
                "displayMills":"78522",
                "entry":"1",
                "action":"1",
                "contentType":"0"
            }
        },
        {
            "ett":"1593111271266",
            "en":"notification",
            "kv":{
                "ap_time":"1593097087883",
                "action":"1",
                "type":"1",
                "content":""
            }
        },
        {
            "ett":"1593066033562",
            "en":"active_background",
            "kv":{
                "active_source":"3"
            }
        },
        {
            "ett":"1593135644347",
            "en":"comment",
            "kv":{
                "p_comment_id":1,
                "addtime":"1593097573725",
                "praise_count":973,
                "other_id":5,
                "comment_id":9,
                "reply_count":40,
                "userid":7,
                "content":"辑赤蹲慰鸽抿肘捎"
            }
        }
    ]

分析可知 kv 中 主体内容长度格式并不一致,这里对 kv 不做进一步展开操作


二、需求:使用spark查询提取日志中的数据,转成 sql 表字段结构化数据

表结构如下图所示:
在这里插入图片描述

  1. hdfs创建目录 /app/data/,将日志数据 op.log 上传至该目录
hdfs dfs -mkdir -p /app/data/
hdfs dfs -put op.log /app/data/
  1. 将数据加载至RDD
scala> val fileRDD = sc.textFile("hdfs://hadoop-single:9000/app/data/op.log")
  1. 切片,提取json格式内容

考虑到每一行的初始 id 编号可能也很重要,此处将id编号也作为json格式键值对形式 { " id " : “1593136280858” } 替换插入到json主体内容中

val opJsonStr1 = fileRDD.map(x=>x.split('|')).map(x=>(x(0),x(1))).map(x=>{
var st = x._2; 
st.replaceFirst("\\{","{\"id\":\""+x._1+"\"," )
})

注: 这里说明一下 replaceFirst() 中匹配替换的内容
replaceFirst(str1, str2) 是将匹配到的第一个字符串 str1 替换成 str2
放到代码中的本意是将 1593136280858|{"cm":{ ... 中第一个 { 替换为 {"id":"1593136280858", ,这样将日志开头的编号作为 id 放入到 json主体内容中 ,切片时即可保留编号
需要说明的是 replaceFirst("\\{","{\"id\":\""+x._1+"\"," ) 中的反斜杠 \ 都是表示转义

  • 第一个参数 "\\{" 中有两个反斜杠是因为匹配 { 需要一个 \ 转义,此时就是 \{ ,放到字符窜中是这样的 "\{" ,但是 java 语言中 \ 真实存在的话也是需要转义符的,这时就需要再加一个反斜杠来转义原先的反斜杠 "\\{" , 所以此处有两个反斜杠
  • 第二个参数 "{\"id\":\""+x._1+"\"," ,其中 "{\"id\":\"" 表示的是一个字符串, "\"," 表示的是一个字符串,用变量 +x._1+ 连接,变量 x._1 是指编号id 1593136280858
  1. RDD 转换成 DataFrame
scala> val opJsonDF = opJsonStr1.toDF
  1. 打印 Schema 信息
scala> opJsonDF.printSchema

没有指定头信息,默认为value,为string类型,但它的值是json串
在这里插入图片描述

  1. 查看数据
scala> opJsonDF.show(false)
scala> opJsonDF.select($"value").show()

在这里插入图片描述

7.获取json主体内容中四个键 id , cm , ap , et 的值

使用 get_json_object(column A, A.key) 方法,如果 dataFrame 中字段 A 的值为 json串 , 那么该方法可以获取字段 A 中某一个键 key 的所包含的值

val opJsonObj1 = opJsonDF.select(get_json_object($"value","$.id").alias("id"),get_json_object($"value","$.cm").alias("cm"),get_json_object($"value","$.ap").alias("ap"),get_json_object($"value","$.et").alias("et"))

在这里插入图片描述

在这里插入图片描述

  1. 继续使用 get_json_object(column A, A.key) 方法, 将 cm 展开,获取其中每一个键:ln,sv,os,g,mid… 的值
lnsvosgmidnvlvchwaruidtlamdvnbasr
“-55.0”“V2.9.6”“8.0.4”“C6819QZ0@mail.com”“489”“3G”“es”4640*960“MX”“489”“1593123253541”“5.2”“sumsung-18”“1.3.4”“Sunsung”
val opJsonObj2 = opJsonObj1.select($"id",$"ap",get_json_object($"cm","$.ln").alias("ln"),get_json_object($"cm","$.sv").alias("sv"),get_json_object($"cm","$.os").alias("os"),get_json_object($"cm","$.g").alias("g"),get_json_object($"cm","$.mid").alias("mid"),get_json_object($"cm","$.nw").alias("nw"),get_json_object($"cm","$.l").alias("l"),get_json_object($"cm","$.vc").alias("vc"),get_json_object($"cm","$.hw").alias("hw"),get_json_object($"cm","$.ar").alias("ar"),get_json_object($"cm","$.uid").alias("uid"),get_json_object($"cm","$.t").alias("t"),get_json_object($"cm","$.la").alias("la"),get_json_object($"cm","$.md").alias("md"),get_json_object($"cm","$.vn").alias("vn"),get_json_object($"cm","$.ba").alias("ba"),get_json_object($"cm","$.sr").alias("sr"),$"et")

在这里插入图片描述
在这里插入图片描述

  1. 查询字段 et 的值
opJsonObj1.select($"et").show(false)

在这里插入图片描述

  1. 导入 spark sql 相关工具包
import spark.implicits._
import org.apache.spark.sql.functions._
import org.apache.spark.sql.types._
import org.apache.spark.sql._
  1. 提取 et 中键 ett , en , kv 的值,放入到array集合中,将整个ArrayType类型的字段重命名为 events
val opJsonObj3 =  opJsonObj2.select($"id",$"ap",$"ln",$"sv",$"os",$"g",$"mid",$"nw",$"l",$"vc",$"hw",$"ar",$"uid",$"t",$"la",$"md",$"vn",$"ba",$"sr",from_json($"et", ArrayType(StructType(StructField("ett",StringType)::StructField("en",StringType)::StructField("kv",StringType)::Nil))).alias("events"))

在这里插入图片描述
在这里插入图片描述

注: spark中,一个StructType对象,可以有多个StructField,同时也可以用名字(name)来提取,就想当于Map可以用key来提取value,但是他StructType提取的是整条字段的信息
在源码中structType是一个case class,如下:

case class StructType(fields: Array[StructField]) extends DataType with Seq[StructField] {}

它是继承Seq的,也就是说Seq的操作,它都拥有,但是从形式上来说,每个元素是用 StructField包住的

语法:(false表示字段可以为空)

val struct = StructType(
    StructField("a", IntegerType) ::
    StructField("b", LongType, false) ::
    StructField("c", BooleanType, false) :: Nil)
  1. 使用 explode 函数将 events 展开
val opJsonObj4 = opJsonObj3.select($"id",$"ap",$"ln",$"sv",$"os",$"g",$"mid",$"nw",$"l",$"vc",$"hw",$"ar",$"uid",$"t",$"la",$"md",$"vn",$"ba",$"sr",explode($"events").alias("events"))

字段 events 中原先包含两行数据,即两条日志数据 etArrayType 类型集合,每个集合中又含有5个元素,使用 explode 将每个集合展开,实现 列转行,这样展开后的每条数据合并之前查询的 id , cm(cm中每一个键已单独展开) , ap ,可以使整张表的 结构扁平化

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  1. 分别查询 events 中所包含的三个属性 ett , en , kv,将 events 一分为三 ,结构更加扁平化
val opJsonObj5 =  opJsonObj4.select($"id",$"ap",$"ln",$"sv",$"os",$"g",$"mid",$"nw",$"l",$"vc",$"hw",$"ar",$"uid",$"t",$"la",$"md",$"vn",$"ba",$"sr",$"events.ett",$"events.en",$"events.kv")

在这里插入图片描述

在这里插入图片描述

  1. opJsonObj5 中的数据加载到临时表中
opJsonObj5.createOrReplaceTempView("optable")
  1. 查询表数据
spark.sql("select * from optable").show()

在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值