目录
日志数据内容
- 访问系统属性,操作系统,浏览器
- 访问特征,点击的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的连接工具类