目录
日志数据内容
- 访问系统属性,操作系统,浏览器
- 访问特征,点击的url,从那个url跳转过来的,页面的停留时间
- 访问信息,session_id,访问ip(访问城市)
用户行为日志分析的意义
- 网站的眼睛
- 网站的神经,在网站的布局是否合理,导航是否清晰,内容是否合理
- 网站的大脑,分析的目标,要做哪些优化。
离线数据处理流程
- 1)数据采集 flume来采集
- 2)数据清洗,采集过来的数据有很多不符合日志规范的脏数据
- 3)数据处理。 按照我们的需求进行相应业务的统计的分析
- 4)处理结果入库 结果可以存放到RDBMS\noSQl
- 5)数据可视化展示 ECharts\HUE\Zeppelin
需求分析
- 统计imooc主站最受欢迎的课程/手机的TOpN访问次数
- 按照地市统计imooc主站最受欢迎的TOPN课程
- 按流量统计imooc主站最受欢迎的TOPN课程
数据清洗
- 需要先将日志文件中的日期格式进行转换
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上的开源项目
-
git clone https://github.com/wzhe06/ipdatabase
-
#编译下载的项目(需要切换到项目目录下) mvn clean package -DskipTests
-
#将依赖的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的连接工具类