本文只展示核心代码,完整代码见文末链接。

Web Log Analysis

  1. 提取需要的log信息,包括time, traffic, ip, web address
  2. 进一步解析第一步获得的log信息,如把ip转换为对应的省份,从网址中提取出访问内容和内容ID,最后将信息转换为parquet格式。

(1)按日期和内容(video)的ID进行分组,并根据访问次数进行倒序排序。

(2)按日期,内容(video)的ID和省份进行分组,并根据访问次数排名取前3。

最后将(1)和(2)数据写入MySQL。

注意:(1)写入数据库时分partition写入,而非逐条写入。

(2)先filter出公用的df并进行cache

(3)下面代码应该能进一步优化,例如将videoAccessTopNStat的try/catch中生成partition list和StatDAO.inserDayVideoAccessTopN(list)中生成batch应该可以合并,避免两次遍历。

设计和编写思路:

1.设计输入参数args(如inputPath和outputPath)

2.设计转换的工具类,包括StructType(需要提取什么信息,分别是什么格式),parseLog(split并提取各index的信息,用try/catch包裹,设置默认输出)。其中对时间的提取可另外定义一个工具类,包括inputFormat,outputFormat,getTime和parse。而对地域的提取,可另外定义一个IpUtils,引入开源代码ipdatabase。这些工具类写完后都要在自身main方法中测试。最后生成DF。

3.filter出commonDF。

4.实现特定的数据统计

5.输出数据,如果写入MySQL,就另外创建一个StatDAO类,包括获取链接,分批写入数据和release链接。

//Step One:

/**
* 将原始日志数据进行解析,返回信息包括visit time, url, traffic, ip
* @param .log, example: 183.162.52.7 - - [10/Nov/2016:00:01:02 +0800]
* "POST /api3/getadv HTTP/1.1" ...
* @return partitioned files, example: 1970-01-01 08:00:00\t-
* \t813\t183.162.52.7
*/ if (args.length != 2) {
println("Usage: logCleanYarn <inputPath> <outputPath>")
System.exit(1)
} val Array(inputPath, outputPath) = args val spark = SparkSession.builder().getOrCreate() val access = spark.sparkContext.textFile(inputPath) //access.take(10).foreach(println) val splited = access.map(line => { val splits = line.split(" ")
val ip = splits(0)
val time = splits(3) + " " + splits(4)
val url = splits(11).replaceAll("\"", "") //remove quotation mark
val traffic = splits(9)
// (ip, DataUtils.parse(time), url, traffic) DataUtils.parse(time) + "\t" + url + "\t" + traffic + "\t" + ip
}) splited.saveAsTextFile(outputPath) spark.stop() /**
* 用于解析日志时间
*/
object DataUtils { //input_format: [10/Nov/2016:00:01:02 +0800]
val YYYYMMDDHHMM_TIME_FORMAT = FastDateFormat.getInstance("dd/MMM/yyyy:HH:mm:SS Z", Locale.ENGLISH) //output_format: yyyy-MM-dd HH:mm:ss
val TARGET_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss") def getTime(time: String) = {
try {
YYYYMMDDHHMM_TIME_FORMAT.parse(time.substring(time.indexOf("[") + 1, time.lastIndexOf("]"))).getTime
} catch {
case _ => 0l
}
} /**
* example: [10/Nov/2016:00:01:02 +0800] ==> 2016-11-10 00:01:00
*/
def parse(time: String) = {
TARGET_FORMAT.format(new Date(getTime(time)))
} // def main(args: Array[String]): Unit = {
// println(parse("[10/Nov/2016:00:01:02 +0800]"))
// }
}
//Step Two:

/**
* 将第一步解析出来的数据转化为DataFrame,并保存为一份parquet文件。
*/ if (args.length != 2) {
println("Usage: logCleanYarn <inputPath> <outputPath>")
System.exit(1)
} val Array(inputPath, outputPath) = args val spark = SparkSession.builder().getOrCreate() val access = spark.sparkContext.textFile(inputPath) // access.take(10).foreach(println) val accessDF = spark.createDataFrame(access.map(line => AccessConvertUtil.parseLog(line)), AccessConvertUtil.struct) // accessDF.printSchema()
// accessDF.show(false) accessDF.coalesce(1).write.format("parquet").partitionBy("day")
.save(outputPath) spark.stop() /**
* 工具类,定义了schema和进一步解析log的方法
*/
object AccessConvertUtil { val struct = StructType(Seq(
StructField("url", StringType),
StructField("cmsType", StringType),
StructField("cmsId", IntegerType),
StructField("traffic", IntegerType),
StructField("ip", StringType),
StructField("city", StringType),
StructField("time", StringType),
StructField("day", StringType)
)) /**
* 进一步解析log,如转化数据类型,解析网址,ip映射具体省份,最后以Row输出
*/
def parseLog(log: String) = { try{
val splited = log.split("\t") val url = splited(1)
val traffic = splited(2).toInt
val ip = splited(3) // 网址:"http://www.xxx.com/article/101"中article为网页内容,101为article的ID
val domain = "http://www.xxx.com/"
val cms = url.substring(url.indexOf(domain) + domain.length)
val cmsTypeId = cms.split("/") var cmsType = ""
var cmsId = 0
if (cmsTypeId.length > 1) {
cmsType = cmsTypeId(0)
cmsId = cmsTypeId(1).toInt
} val city = IpUtils.getCity(ip)
val time = splited(0)
val day = time.substring(0, 10).replaceAll("-", "") Row(url, cmsType, cmsId, traffic, ip, city, time, day)
} catch {
case _ => {
Row(null, null, null, null, null, null, null, null)
}
}
}
} /**
* Ip工具类,将IP映射为省份,利用开源代码ipdatabase
* https://github.com/wzhe06/ipdatabase
*/
object IpUtils { def getCity(ip: String) = {
IpHelper.findRegionByIp(ip)
} def main(args: Array[String]): Unit = {
println(getCity("58.30.15.255"))
}
}
//Step Three:

/**
* 在第二步的结果数据中,按日期和video的ID进行分组,并根据访问次数进行倒序排序。
* 最后将数据写入MySQL。
*/ if (args.length != 2) {
println("Usage: logCleanYarn <inputPath> <day>")
System.exit(1)
} val Array(inputPath, day) = args val spark = SparkSession.builder()
.config("spark.sql.sources.partitionColumnTypeInference.enabled", "false")
.getOrCreate() val accessDF = spark.read.format("parquet").load(inputPath) // accessDF.printSchema()
// accessDF.show(false) //预先筛选和cache后面两个函数要复用的df
import spark.implicits._
val commonDF = accessDF.filter($"day" === day && $"cmsType" === "video")
commonDF.cache() //删除已有的内容,避免重复
StatDAO.deleteData(day) //groupBy video
videoAccessTopNStat(spark, commonDF) //groupBy city
cityAccessTopNStat(spark, commonDF) commonDF.unpersist(true) // videoAccessTopDF.show(false) spark.stop() /**
* 两个样例类,用于储存不同数据类型,应用于下面两个方法。
*/
case class DayVideoAccessStat(day: String, cmsId: Long, times: Long)
case class DayCityVideoAccessStat(day: String, cmsId: Long, city: String, times: Long, timesRank: Int) /**
* 按内容ID分组后排序,并把结果写到Mysql
*/
def videoAccessTopNStat(spark: SparkSession, comDF: DataFrame): Unit = { import spark.implicits._
val videoAccessTopNStat = comDF
.groupBy($"day", $"cmsId")
.agg(count("cmsId").as("times"))
.orderBy(desc("times")) try {
videoAccessTopNStat.foreachPartition(partitionOfRecords =>{
val list = new ListBuffer[DayVideoAccessStat] partitionOfRecords.foreach(info => {
val day = info.getAs[String]("day")
val cmsId = info.getAs[Long]("cmsId")
val times = info.getAs[Long]("times") list.append(DayVideoAccessStat(day, cmsId, times))
}) StatDAO.inserDayVideoAccessTopN(list)
})
} catch {
case e:Exception => e.printStackTrace()
}
} /**
* 按内容ID和省份分组后排名,并把结果写到Mysql
*/
def cityAccessTopNStat(spark: SparkSession, comDF: DataFrame): Unit = { import spark.implicits._ val videoAccessTopNStat = comDF
.groupBy($"day", $"city", $"cmsId")
.agg(count("cmsId").as("times")) val windowSpec = Window.partitionBy($"city").orderBy(desc("times"))
val videoAccessTopNStatDF = videoAccessTopNStat.select(expr("*"), rank().over(windowSpec).as("times_rank"))
.filter($"times_rank" <= 3) try {
videoAccessTopNStatDF.foreachPartition(partitionOfRecords => {
val list = new ListBuffer[DayCityVideoAccessStat] partitionOfRecords.foreach(info => {
val day = info.getAs[String]("day")
val cmsId = info.getAs[Long]("cmsId")
val city = info.getAs[String]("city")
val times = info.getAs[Long]("times")
val timesRank = info.getAs[Int]("times_rank") list.append(DayCityVideoAccessStat(day, cmsId, city, times, timesRank))
}) StatDAO.inserDayCityVideoAccessTopN(list)
})
} catch {
case e: Exception => e.printStackTrace()
}
} /**
* 分组后排序方法
*/
def videoAccessSortedStat(spark: SparkSession, accessDF: DataFrame) : Unit = {
import spark.implicits._ val sortedStat= accessDF
.filter($"day" === "20170511" && $"cmsType" === "video")
.groupBy($"day", $"cmsId")
.agg(count("cmsId").as("times"))
.orderBy(desc("times")) // 分块创建存储每条信息的list,并调用函数将数据写到到MySQL
try {
sortedStat.foreachPartition(partitionOfRecords =>{
val list = new ListBuffer[DayVideoAccessStat] partitionOfRecords.foreach(info => {
val day = info.getAs[String]("day")
val cmsId = info.getAs[Long]("cmsId")
val times = info.getAs[Long]("times") list.append(DayVideoAccessStat(day, cmsId, times))
}) StatDAO.inserDayVideoAccessSortedStat(list)
})
} catch {
case e:Exception => e.printStackTrace()
}
}
//Step Three:

/**
* 工具类,提供两类方法:
* 1.连接数据库,将数据写入MySQL,并释放连接的方法。
* 2.删除MySQL中已存在的(相同entry的数据)
*/
object StatDAO { def inserDayVideoAccessTopN(list: ListBuffer[DayVideoAccessStat]): Unit = { var connection: Connection = null
var pstmt: PreparedStatement = null try{
connection = MySQLUtils.getConnect() val sql = "insert into day_video_access_topn_stat(day, cms_id, times) values (?, ?, ?)"
val pstmt = connection.prepareStatement(sql) connection.setAutoCommit(false) for (ele <- list) {
pstmt.setString(1, ele.day)
pstmt.setLong(2, ele.cmsId)
pstmt.setLong(3, ele.times) pstmt.addBatch()
} pstmt.executeBatch()
connection.commit() } catch {
case e:Exception => e.printStackTrace()
} finally {
MySQLUtils.release(connection, pstmt)
}
} def inserDayCityVideoAccessTopN(list: ListBuffer[DayCityVideoAccessStat]): Unit = { var connection: Connection = null
var pstmt: PreparedStatement = null try{
connection = MySQLUtils.getConnect() val sql = "insert into day_video_city_access_topn_stat(day, cms_id, city, times, times_rank) values (?, ?, ?, ?, ?)"
val pstmt = connection.prepareStatement(sql) connection.setAutoCommit(false) for (ele <- list) {
pstmt.setString(1, ele.day)
pstmt.setLong(2, ele.cmsId)
pstmt.setString(3, ele.city)
pstmt.setLong(4, ele.times)
pstmt.setInt(5, ele.timesRank) pstmt.addBatch()
} pstmt.executeBatch()
connection.commit() } catch {
case e:Exception => e.printStackTrace()
} finally {
MySQLUtils.release(connection, pstmt)
}
} def deleteData(day: String): Unit = { val tables = Array("day_video_access_topn_stat", "day_video_city_access_topn_stat")
var connection: Connection = null
var pstmt: PreparedStatement = null try {
connection = MySQLUtils.getConnect() for (table <- tables) {
val sql = s"delete from $table where day = ?"
val pstmt = connection.prepareStatement(sql)
pstmt.setString(1, day)
pstmt.executeUpdate() }
} catch {
case e: Exception => e.printStackTrace()
} finally {
MySQLUtils.release(connection, pstmt)
} }
} /**
* 工具类,包含连接数据库和释放连接的方法。
*/
object MySQLUtils { def getConnect() = {
DriverManager.getConnection("jdbc:mysql://localhost:3306/log_project","root", "password")
} def release(connection: Connection, pstmt: PreparedStatement): Unit ={
try{
if (pstmt != null) {
pstmt.close()
}
} catch {
case e: Exception => e.printStackTrace()
} finally {
if (connection != null) {
connection.close()
}
}
} def main(args: Array[String]): Unit = {
println(getConnect())
}
}

参考:

大数据 Spark SQL慕课网日志分析

GitHub源码

基于Spark的网站日志分析的更多相关文章

  1. 基于 Spark 的文本情感分析

    转载自:https://www.ibm.com/developerworks/cn/cognitive/library/cc-1606-spark-seniment-analysis/index.ht ...

  2. Hadoop学习笔记—20.网站日志分析项目案例(一)项目介绍

    网站日志分析项目案例(一)项目介绍:当前页面 网站日志分析项目案例(二)数据清洗:http://www.cnblogs.com/edisonchou/p/4458219.html 网站日志分析项目案例 ...

  3. Hadoop学习笔记—20.网站日志分析项目案例(二)数据清洗

    网站日志分析项目案例(一)项目介绍:http://www.cnblogs.com/edisonchou/p/4449082.html 网站日志分析项目案例(二)数据清洗:当前页面 网站日志分析项目案例 ...

  4. Hadoop学习笔记—20.网站日志分析项目案例(三)统计分析

    网站日志分析项目案例(一)项目介绍:http://www.cnblogs.com/edisonchou/p/4449082.html 网站日志分析项目案例(二)数据清洗:http://www.cnbl ...

  5. Hadoop学习笔记—20.网站日志分析项目案例

    1.1 项目来源 本次要实践的数据日志来源于国内某技术学习论坛,该论坛由某培训机构主办,汇聚了众多技术学习者,每天都有人发帖.回帖,如图1所示. 图1 项目来源网站-技术学习论坛 本次实践的目的就在于 ...

  6. Apache 网站日志分析

    1.获得访问前 10 位的 ip 地址 [root@apache ~]# cat access_log |awk '{print $1}'|sort|uniq -c|sort -nr|head -10 ...

  7. spark实战之网站日志分析

    前面一篇应该算是比较详细的介绍了spark的基础知识,在了解了一些spark的知识之后相必大家对spark应该不算陌生了吧!如果你之前写过MapReduce,现在对spark也很熟悉的话我想你再也不想 ...

  8. IIS 网站日志分析

    最近由于ADSL代理总出问题,导致爬虫服务器总被目标网站封,由于请求内容总是空,前端APP获取不到想要的内容就一直刷新,导致爬虫服务器请求更加繁忙. 爬虫服务器每执行完一个流程,都会给统计服务器Pos ...

  9. shell脚本实现网站日志分析统计

    如何用shell脚本分析与统计每天的访问日志,并发送到电子邮箱,以方便每天了解网站情况.今天脚本小编为大家介绍一款不错的shell脚本,可以实现如上功能. 本脚本统计了:1.总访问量2.总带宽3.独立 ...

随机推荐

  1. 【译】x86程序员手册00 - 翻译起因

    从上一次学习MIT的操作系统课程又过去了一年.上次学习并没有坚持下去.想来虽有种种原因,其还在自身无法坚持罢了.故此次再鼓起勇气重新学习,发现课程都已由2014改版为2016了.但大部分内容并没有改变 ...

  2. 不用float也可以让div横向显示

    display: inline-block; vertical-align: top; 就这两个属性,给div设置上,div就不会换行显示啦,而且还不影响横向的其他元素的显示.

  3. python dns请求

    一.DNS dns(domain name system)域名系统 ,主要用来把主机名转换成ip地址.其至今能存在的原因有两个: 能使人们记住名字,而不是ip地址: 允许服务器改变地址,但使用相同的名 ...

  4. 小白年薪26万,为什么Python岗位薪资越来越高?

    人工智能和大数据概念的兴起,带动了Python的快速增长——Python语言逻辑简洁.入门简单.生态丰富,几乎成为几个新兴领域的不二选择.而除了这两个领域,Python还有更多的适用领域:爬虫.web ...

  5. 怎么用最短时间高效而踏实地学习Linux?

    在技术行业里,人才的唯一衡量标准就是技术能力,而技术能力,就代表着你的薪资.职位.话语权.很多人都经历过,跟自己同时入行甚至入行还晚的人,成长速度却远超自己,短短两三年就拉开了差距. 秘密就在于,有些 ...

  6. 在引入的css或者js文件后面加参数的作用

    有时候可能会遇到js或者css文件引用后传递参数: css和js带参数(形如.css?v=与.js?v=) <script type=”text/javascript” src=”jb51.js ...

  7. Uedior上传大文件超时报错

    出错原因: 1.php超时等待时间太短 2.uedior中设置了请求超时,提示信息: 上传失败,请重试 先解决第一个问题: 设置php.ini中的max_execution_time 为0 (意思是h ...

  8. postgres主从配置

    运维开发技术交流群欢迎大家加入一起学习(QQ:722381733) 开始部署postgres主从(如果没不会安装postgres的请去上一个博文中查看) 这里我使用了两台服务器部署 主:192.168 ...

  9. 在Docker上构建mysql容器

    1.查看docker上的镜像是否有 mysql,如果没有下载则列表中没有  [root@holly holly]# docker images; 如果没有只会看到如下结构 REPOSITORY  TA ...

  10. ReportNG 替换testng过程中遇到的问题

    1. Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/collectio ...