日志文件是用于记录系统操作事件的记录文件或文件集合,可分为事件日志和消息日志。具有处理历史数据、诊断问题的追踪以及理解系统的活动等重要作用。有了日志文件,就可以了解到网站的访问频率、网站是否受到了恶意的攻击等。
Python可以使用PySpark进行日志类文件的分析。这里使用PySpark的原因在于每天的日志量是不断累加的,日志文件中的数据可以说得上是大数据。使用PySpar分析大数据的日志文件也是显得尤为重要,原理也是分布式计算mapreduce的基本原理,很多的分析方法也来自于wordcount的精典程序。本篇只要侧重于在实际的数据分析过程中PySpark的方法和思路的分析。
实现对日志文件的分析,必须了解日志文件,俗话说,知已知彼,百战百胜。只有对日志文件了解的基础之上,才能更准确地定位到分析数据的相关维度,进而得到有价值的分析结果。
日志文件如下图所示。
从图中所示的日志文件中取出单条日志记录,如下图所示。
对于单条记录中可以看到,第一个数字“199.72.81.55” 是一个地址,也就是客户端的IP地址,后面大部分是两个“-”号线,先不关心具体的意义,“[]”里面包含的是时间,时间是由“年月日时分秒”组成的。接在后面的是“GET”或者“POST”请求方面,图中显示的大部分都是“GET”方式,证明这部分日志中大部分用户的访问来源于浏览器的访问。请求方式后面是具体的请求地址,也就是用户访问网站,网站的后面显示“HTTP/1.0”是网站的请求方式,“HTTP”证明网站的请求是超文件协议的方式,也就是浏览器中网址的正常访问。在“HTTP”协议标注的后面是数字“200”或者“304”等等,这就是网站访问后的状态码,一般200表示网站访问成功。最后还有一串数字是访问文件的大小。
对于这样的网站,首先要提取数据,而数据中最重要是时间数据,时间的数据为了分析的方便,比如“网站每个月的点击量”、“网站每年的点击量”等问题分析的方便,可以把时间的每一个部分分离,或者把“[]”内时间的字符串组合用datetime格式来处理,这样可以更好的提出时间点来进行有价值问题的数据分析。
从日志数据中取出单独的时间点,如下图所示。
日志数据中的这个时间点可以看作是字符串,如果把两边的“[]”去掉,字符串中索引位置从0到1时间点的某个日,第一个“/”后面索引位置为3到5表示时间点的某个月,第二个“/”后面索引位置为7到10表示时间点的某个年,第一个“:”后面的索引位置为12到13表示时间点的某个小时,第二个“:”后面的索引位置为15到16表示时间点的某个分钟,第三个“:”后面索引位置为18到19表示时间点的某个秒数。针对这样的数字意义,可以实现一个方法把传入的时间点字符串解析成datetime类型的“年月日时分秒”。代码如下。
month_map = {'Jan': 1, 'Feb': 2, 'Mar':3, 'Apr':4, 'May':5, 'Jun':6, 'Jul':7,
'Aug':8, 'Sep': 9, 'Oct':10, 'Nov': 11, 'Dec': 12}
def parse_apache_time(s):
return datetime.datetime(int(s[7:11]),
month_map[s[3:6]],
int(s[0:2]),
int(s[12:14]),
int(s[15:17]),
int(s[18:20]))
代码中定义month_map变量的目的是把时间点字符串中英文表述的月份转换成阿拉伯数字。
日志数据中的每一行数据格式前面已做过介绍,但需要把数据格式中的某个有意见的数字取出,才可以进行有意义的分析。如分析“每天的访问用户的成功率”,这时候必须把“200”这个访问成功的状态码取出才能进行分析,否则没有意义。也不能使用find()方法去进行字符串查找,因为表征“200”请求成功的信息在字符串中只有那一个位置的地方才是正确的,如果网站名字中出现了“200”不代表是一个成功访问的标志,必须找到日志数据字符串中表征请求成功位置的那个“200”才是有意义的。
日志数据每一行字符串中用空格隔开了每个表征意义的点,根据每个表征数据意义的点的具体数据特点,可以用正则表达式去匹配这样的字符串。具体正则表达式的格式如下,读者可自行分析表达式的意义。
'^(\S+) (\S+) (\S+) \[([\w:/]+\s[+\-]\d{4})\] "(\S+) (\S+)\s*(\S*)" (\d{3}) (\S+)'
注意表达式中用符号“()”来括起来了很多的格式,这是由于正则表达式可以用group()获取分段截获的字符串,把这样的正则表达式赋值给变量,然后调用search方法,把结果保存在变量match中后,match.group(N)就会返回第N组括号匹配的字符。
这样就可以把日志数据中一行数据格式的意义数据进行匹配,代码如下。
def parseApacheLogLine(logline):
APACHE_ACCESS_LOG_PATTERN = '^(\S+) (\S+) (\S+) \[([\w:/]+\s[+\-]\d{4})\] "(\S+) (\S+)\s*(\S*)" (\d{3}) (\S+)'
match = re.search(APACHE_ACCESS_LOG_PATTERN, logline)
if match is None:
return (logline, 0)
size_field = match.group(9)
if size_field == '-':
size = long(0)
else:
size = long(match.group(9))
return (Row(
host = match.group(1),
date_time = parse_apache_time(match.group(4)),
method = match.group(5),
endpoint = match.group(6),
protocol = match.group(7),
response_code = int(match.group(8)),
content_size = size
), 1)
前面分析了日志文件中数据的具体处理方式,现在就对日志文件进行读取,再调用上述方法进行处理,就进行了不同意义数据的提取。PySpark读取数据是通过SparkSession来完成的,通过实例化SparkSession,再调用sparkContext程序入口,最后通过textFile读取文件,由于文件是在hadoop分布式存储服务器上的,因此读取文件的地址协议就是hdfs。代码如下。
from pyspark.sql import SparkSession
spark=SparkSession.builder.master(“local”).appName(“log_analyse”).getOrCreate()
sc=spark.sparkContext
files=sc.textFile(“hdfs://access_log100”)
读取文件后对每一行调用前面完成的parseApacheLogLine方法,也就是对读出的files结果RDD进行map处理,这样可以对每一个行进行处理。
mapfile_log=files.map(parseApacheLogLine)
读取的数据中每一行维度就包括了host,datetime,method,endpoint,protocol,responsecode,content_size。
下面就是就具体问题的分析,为了分析的速度问题,日志只截取了前面100条,目的在于说明分析问题的方法。
对日志文件进行“每日访问中客户端的数量”分析,其分析结果可以有利于查看网站的访问是否超负荷,如果访问次数超出一定的上限,很可能达到了网站的运营能力。
针对这个问题,需要对“每天”和“客户端”两个维度进行提取,提取后以天数为key,对天数进行分组,求出以天数分组后把客户端去重,再求出客户端的个数。代码如下。
dayToHostPairTuple =mapfile_log.map(lambda log: (log.date_time.day, log.host)).distinct()
dayGroupedHosts = dayToHostPairTuple.groupByKey()
dayHostCount = dayGroupedHosts.map(lambda (day, hosts): (day, len(hosts)))
dailyHosts = dayHostCount.sortByKey()
Print(dailyHosts.collect())
代码中distinct()表示的是去重的意思,groupByKey根据key值进行分组,分组后map方法,参数lambda表达式中对hosts主机使用len方法求长度,也可以用count来求个数,最后sortByKey()进行排序输出。其会得到“每天访问网站客户端的数量”输出。
如下图所示。
由于日志文档截取的都是1天的数据,所以图中结果仅做参考。
对日志文件进行“主机访问频率分析”,不分天数,只是对日志文件中主机的每一次访问作次数统计,通过统计可以得到哪一个客户端访问次数比较多,很可能是我们的死粉客户,哪一个客户端访问次数比较少,访问少的原因是什么,再去关心用户访问了哪些频道等。
针对这样的分析,直接提取日志文件中的host主机,对主机进行统计计数,也就是mapreduce中map的wordcount逻辑,一个主机记录1次,由reduceByKey方法去完成汇总的主机记录的统计。代码如下。
执行结果如下图所示。
ostCountPairTuple = mapfile_log.map(lambda log: (log.host, 1))
hostSum = hostCountPairTuple.reduceByKey(lambda a, b : a + b)
print(hostSum.collect())
这段代码相对比较简单,其实就是wordCount的一个简易版,后面还可以sortByKey进行排序输出结果。
如下图所示。
对日志文件进行“主机访问频率分析”,不分主机和天数,只是对网站服务的稳定性进行测试,关注到网站的整体效果,避免用户因为网站服务的不稳定而产生流失客户。
针对这样问题的分析,直接提取日志文件中各种http请求状态码,对状态码进行统计,查看不同的状态码对应的不同的效果。也就是mapreduce中map的wordcount逻辑,一个状态码记录1次,由reductByKey方法去完成汇总的客户端访问状态码的统计。代码如下。
status_code_map= mapfile_log.map(lambda log: (log.response_code, 1))
responseCodeToCount= status_code_map.reduceByKey(lambda a, b : a + b)
print(responseCodeToCount.collect())
通过前面对日志的一些样例分析,读者也会发现,常用的大数据的分析方法也都是map把数据记录,然后使用reduceByKey进行数据的汇总,再用sortByKey()进行数据汇总后的排序,整个分析思路大部分也离不开精典的wordCount统计词频程序。
执行结果如下图所示。