Spark数据分析及处理用例
文章目录
- 用例1:数据清洗
- 用例2:用户留存分析
- 用例3:活跃用户分析
- 用例4:活跃用户地域信息分析
- 用例5:用户浏览深度分析
本项目用到的文件获取如下,提取码: 6xdx
点我获取文件
注意:本文都是在spark-shell环境下完成
用例1:数据清洗
读入日志文件并转化为RDD[Row]类型
- 按照Tab切割数据
- 过滤掉字段数量少于8个的
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.{StructType, StructField, StringType}
val schemaString = "event_time url method status sip user_uip action_prepend action_client"
val fields = schemaString.split("\\s+").map(fieldName => StructField(fieldName, StringType, nullable = true))
val schema = StructType(fields)
val rdd = sc.textFile("file:///data/test.log").map(_.split("\t")).filter(_.size==8)
val rowRDD = rdd.map(x=>Row(x(0), x(1),x(2),x(3), x(4),x(5),x(6), x(7)))
val df = spark.createDataFrame(rowRDD,schema)
对数据进行清洗
日志拆分字段:
event_time
url
method
status
sip
user_uip
action_prepend
action_client
- 按照第一列和第二列对数据进行去重
- 过滤掉状态码非200
- 过滤掉event_time为空的数据
- 将url按照”&”以及”=”切割
保存数据: 将数据写入mysql表中
//按照第一列和第二列对数据进行去重 使用 df的dropDuplicates方法
//过滤掉状态码非200
//过滤掉event_time为空的数据
val ds = df.dropDuplicates("event_time","url").filter(x=>x(3)=="200").filter(x=>x(0).toString().trim !=null && x(0).toString().trim!="")
//将url 按照 ? 进行分割, 取出第二段并按照& 进行分割, 分割出每一组参数,最后按照= 切割, 变成键值对的形式转化成Map集合
val ds2 = ds.map(x=>{val s = x.getAs[String]("url").split("\\?")(1);val m= s.split("&").map(_.split("=")).filter(_.size==2).map(x=>(x(0),x(1))).toMap;(x.getAs[String]("event_time"),m.getOrElse("userUID", ""),m.getOrElse("userSID", ""),m.getOrElse("actionBegin", ""),m.getOrElse("actionEnd", ""),m.getOrElse("actionType", ""), m.getOrElse("actionName", ""),m.getOrElse("actionValue", ""), m.getOrElse("actionTest", ""),m.getOrElse("ifEquipment", ""),x.getAs[String]("method"),x.getAs[String]("status"),x.getAs[String]("sip"),x.getAs[String]("user_uip"),x.getAs[String]("action_prepend"),x.getAs[String]("action_client"))})
val df2 = ds2.toDF("event_time", "user_uid", "user_sid", "action_begin",
"action_end", "action_type", "action_name", "action_value",
"action_test", "if_equipment", "method", "status", "sip",
"user_uip", "action_prepend", "action_client")
//保存数据,将数据写入mysql表中
val url = "jdbc:mysql://localhost:3306/test"
val prop = new java.util.Properties
prop.setProperty("user", "root")
prop.setProperty("password", "sunyong")
prop.setProperty("driver","com.mysql.jdbc.Driver")
df2.write.mode("overwrite").jdbc(url,"logs",prop)
用例2:用户留存分析
计算用户的次日留存率
- 求当天新增用户总数n
- 求当天新增的用户ID与次日登录的用户ID的交集,得出新增用户次日登录总数m (次日留存数)
- m/n*100%
计算用户的次周留存率
val logs = spark.read.jdbc(url,"logs2",prop)
logs.cache()
// 1.将action_name=='Registered'抽取为一张注册表
val registered = logs.filter($"action_name" === "Registered").withColumnRenamed("event_time","register_time").select("user_uid","register_time")
// 2.将action_name=='Signin'抽取为一张登陆表
val signin = logs.filter($"action_name" === "Signin").withColumnRenamed("event_time","signin_time").select("user_uid","signin_time")
// 3.将注册表和登陆表通过user_sid进行join操作
val joined = registered.join(signin, registered("user_uid") === signin("user_uid"), "left").drop(signin("user_uid"))
joined.createOrReplaceTempView("j")
// 将时间取前10位并转化为时间戳
val register2signin = spark.sql("select user_uid,register_time,signin_time,unix_timestamp(substr(register_time,1,10),'yyyy-MM-dd')registered_date ,unix_timestamp(substr(signin_time,1,10),'yyyy-MM-dd') signin_date from j")
// 4.若统计次日留存则过滤出来日期相差天数为1的, 若统计七日留存则过滤出来日期相差天数为7的
// 5.对日期进行分组操作,在进行累加求和即可得出次日留存和七日留存
// 次日留存
val day_retention = register2signin.filter($"registered_date" === $"signin_date" - 86400).groupBy($"registered_date",$"user_uid").count().groupBy("registered_date").count()
// 七日留存
val week_retenetion = register2signin.filter($"registered_date" === $"signin_date" - 604800).groupBy("registered_date","user_uid").count().groupBy("registered_date").count()
// 写入数据库
day_retention.write.mode("overwrite").jdbc(url, "day_retention", prop)//只有两天的数据 最后只有一条数据
week_retenetion.write.mode("overwrite").jdbc(url,"week_retention", prop)//只有两天数据,无法得到周留存,数据为空
用例3:活跃用户分析
统计分析需求
- 读取数据库,统计每天的活跃用户数
- 统计规则:有看课和买课行为的用户才属于活跃用户
- 对UID进行去重
val logs = spark.read.jdbc(url,"logs2",prop)
// 1.将学习的和买课的日志过滤出来
// 2.将event_time 的字段的前十个字符取出来作为日期
// 3.按照用户id 去重
// 4.按照日期进行分组
// 5.统计人数
val activeUserCount= logs.filter($"action_name" === "StartLearn" || $"action_name" === "BuyCourse").map(x => {(x.getAs("user_uid").toString, x.getAs("event_time").toString.substring(0, 10))}).withColumnRenamed("_2", "date").distinct().groupBy("date").count().orderBy("date").cache()
// 写入mysql
activeUserCount.write.mode("overwrite").jdbc(url, "activeUserCount", prop)
用例4:活跃用户地域信息分析
统计分析需求
- 读取原始日志数据
- 解析url获取用户的访问IP
- 通过IP库获得IP对应的省市区地址(读取文件转换后存入MySQL)
- 求出每个地域人数的所占百分比
// 从mysql中读取数据
val url = "jdbc:mysql://localhost:3306/test"
val prop = new java.util.Properties
prop.setProperty("user", "root")
prop.setProperty("password", "sunyong")
prop.setProperty("driver","com.mysql.jdbc.Driver")
val logs = spark.read.jdbc(url, "logs2", prop).cache()
// 统计一下日志总条数
val cnt = logs.count()
// 注册udf 函数 1.ip 解析为整数形式 2.求占比
import org.apache.spark.sql.expressions.UserDefinedFunction
import org.apache.spark.sql.functions._
val rate: UserDefinedFunction = udf((x: Double) => x / cnt)
val ip2Int: UserDefinedFunction = udf((x:String)=>{val y = x.split("\\.");y(0).toLong*256*256*256+y(1).toLong*256*256+y(2).toLong*256+y(3).toLong})
//使用纯真IP地址查询工具生成ip 地址文件
//将IP数据文件转换成整数形式存入MySQL
case class IP(startip:String,endip:String,city:String,company:String)
val df = sc.textFile("file:///data/IP.txt").map(_.split("\\s+")).filter(_.size==4).map(x=>IP(x(0),x(1),x(2),x(3))).toDF
//转换成整数形式存到数据库中
val ip = df.withColumn("startIp_Int",ip2Int($"startip")).withColumn("endIp_Int",ip2Int($"endip")).drop("startip").drop("endip")
ip.write.mode("overwrite").jdbc(url, "ip", prop)
//读取ip数据 创建临时表
val ipDF = spark.read.jdbc(url, "ip", prop).cache()
ipDF.createOrReplaceTempView("ip")
//对日志数据进行处理 并创建临时表
val regionIp = logs.select("user_uid","user_uip").filter(x => x.getAs("user_uip").toString.size>1).withColumn("ip",ip2Int($"user_uip")).drop("user_uip")
regionIp.createOrReplaceTempView("region")
// 将ip数据表与日志临时表进行join然后计算
val grouped = spark.sql("select * from ip i join region r on r.ip between i.startIp_Int and endIp_Int").select("user_uid","city").groupBy("city", "user_uid").count().dropDuplicates("user_uid").groupBy("city").count()
//最终结果存入数据库
grouped.orderBy(grouped("count").desc).withColumn("rate", rate(grouped.col("count"))).write.mode("overwrite").jdbc(url, "region", prop)
- 结果如下图
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200817171812779.png?x-oss-
process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N1bl8wMTI4,size_16,color_FFFFFF,t_70#pic_center)
用例5:用户浏览深度分析
统计分析需求
- 读取日志信息,以天为计量单位,通过depth值来表示用户的浏览深度
- 统计每个depth阶段的用户的个数,反映出每个url的访问人数,针对性的优化页面,来提高网站的转化率,对用户产生粘性
- 计算规则:当前url的个数作为depth的值
1)一个用户今天浏览了三个页面
2)一个url今天被50个人访问
val logs = spark.read.jdbc(url,"logs2",prop)
// 1.过滤出action_prepend 字段的长度大于10的
// 2.将event_time 这一列的时间转换为日期
import org.apache.spark.sql.expressions.UserDefinedFunction
import org.apache.spark.sql.functions._
val time2date:UserDefinedFunction = udf((x:String)=>x.substring(0, 10))
val filtered = logs.filter(x => x.getAs("action_prepend").toString.length > 10).withColumn("date", time2date(logs.col("event_time"))).cache()
// 3.对"date", "user_sid", "action_prepend" 进行分组统计,之后再对"date", "count"进行分组统计.
// 4.统计url
// 统计用户浏览的url 的个数
val user_url = filtered.groupBy("date", "user_uid", "action_prepend").count().groupBy("date", "user_uid").count().orderBy("date", "count")
// 统计url被用户浏览次数
val url_count = filtered.groupBy("date", "action_prepend", "user_uid").count().groupBy("date", "action_prepend").count().orderBy("date", "count")
//写入数据库
user_url.write.mode("overwrite").jdbc(url, "user_url", prop)
url_count.write.mode("overwrite").jdbc(url, "url_count", prop)