声明:没博客内容由本人经过实验楼整理而来
内容描述
在给定的日志文件是一个标准的Apache2 程序产生的access.log文件,根据业务需求,我们需要分析得到下面几方面的数据:
- 统计每日PV和独立IP
- 统计每种不同的HTTP状态对应的访问数
- 统计不同独立IP的访问量
- 统计不同页面的访问量
Apache日志格式
首先下载apache
日志文件
wget http://labfile.oss.aliyuncs.com/courses/456/access.log
首先打开pyspark
// 读入数据
>>> logRDD=sc.textFile("access.log")
这里是一条 Apache日志
180.76.15.161 - - [06/Dec/2014:06:49:26 +0800] "GET / HTTP/1.1" 200 10604 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
Apache日志内容从左到右依次是:
- 远程IP地址
- 客户端记录
- 浏览者记录
请求的时间,包括三项内容:
- 日期
- 时间
- 时区
服务器收到的请求,包括三项内容:
- METHOD:请求的方法,GET/POST等
- RESOURCE:请求的目标链接地址
- PROTOCOL:HTTP版本
- 状态代码,表示请求是否成功
- 发送的字节数
- 发出请求时所在的URL
- 客户端的详细信息:操作系统及浏览器等
这里我们提取的信息为:
1. 客户端的IP
2. 请求日期
3. 请求的URL
4. HTTP状态码(如200)
我们使用正则表达式来提取所要的信息。
pattern=r'^(\S+) (\S+) (\S+) \[([\w/]+)([\w:/]+)\s([+\-]\d{4})\] \"(\S+) (\S+) (\S+)\" (\d{3}) (\d+)'
关于正则表达式请参考:Python正则表达式
为了避免一些杂乱的无法解析的数据干扰,我们使用正则表达式做两件事情,一个是过滤掉无法解析的日志,一个是解析获得需要的数据元组
实现两个函数:
- filterWithParse:过滤无法解析的日志记录
- parseLog:解析日志记录,得到所需信息的元组
import re
pattern=r'^(\S+) (\S+) (\S+) \[([\w/]+)([\w:/]+)\s([+\-]\d{4})\] \"(\S+) (\S+) (\S+)\" (\d{3}) (\d+)'
def filterWithParse(s):
m = re.match(pattern, s)
if m:
return True
return False
def parseLog(s):
m = re.match(pattern, s)
clientIP = m.group(1)
requestDate = m.group(4)
requestURL = m.group(8)
status = m.group(10)
return (clientIP, requestDate, requestURL, status)
将上述代码复制到pyspark交互模式中。
解析文件
执行第一次map操作,获得解析后的(客户端IP,请求日期,请求URL和HTTP状态码)信息:
>>> logRDDv1 = logRDD.filter(filterWithParse).map(parseLog)
统计每日的PV
所谓的PV(page view),即每一天的页面访问量
首先查看一下总共有多少的访问量,这里用count()函数去统计。这些访问量包含了很多天的总和,因此我们需要对其进行处理。
// 统计总的访问量
>>> logRDDv1.count()
5122
>>>from operator import add
>>>logRDDv2 = logRDDv1.map(lambda x: (x[1], 1)).reduceByKey(add)
// 保存
>>>logRDDv2.sortByKey().saveAsTextFile('/tmp/DPV')
首先进行map
操作,生成一个元组(日期,个数),这里要注意,x[1]代表的是日期,然后进行reduce
操作对相同的key值进行相加。
打开/tmp/DPV中的文件可以看到,其进行了统计。
1 (u'06/Dec/2014', 340)
2 (u'07/Dec/2014', 778)
3 (u'08/Dec/2014', 910)
4 (u'09/Dec/2014', 1282)
5 (u'10/Dec/2014', 526)
统计每日的独立IP
注意与PV的不同,PV中一个IP可能在同一天访问了多出此页面,而IP一天只记录此IP访问1次。这点很重要。
首先进行一次map
操作,生成(日期,IP)的元组,对logRDDv3中的元素进行去重操作(使用distinct()函数),logRDDv4中每个元组都不相同,表示每天的一个独立IP访问:
>>> logRDDv3 = logRDDv1.map(lambda x: (x[1], x[0]))
>>> logRDDv4 = logRDDv3.distinct()
执行reduce操作,将日期相同的记录条数相加获得最终的每日独立IP数:
// 这里的x[0]是日期
>>> logRDDv5 = logRDDv4.map(lambda x: (x[0], 1)).reduceByKey(add)
// 这里进行交换是的显示,没有保存
>>> DIP = logRDDv5.collect()
>>> DIP
[(u'06/Dec/2014', 124), (u'13/Dec/2014', 46), (u'11/Dec/2014', 177), (u'08/Dec/2014', 174), (u'12/Dec/2014', 170), (u'07/Dec/2014', 166), (u'10/Dec/2014', 155), (u'09/Dec/2014', 148)]
可以看到每日独立的IP少于PV,这是因为同一个IP可能在一天内访问多次。
统计每种不同的HTTP状态对应的访问数
比较简单,直接给出代码。
>>> logRDDv6=logRDDv1.map(lambda x:(x[3],1)).reduceByKey(add)
>>> status=logRDDv6.sortByKey().collect()
>>> status
[(u'200', 3533), (u'206', 3), (u'301', 331), (u'304', 104), (u'400', 8), (u'403', 3), (u'404', 1134), (u'405', 6)]
统计不同页面的访问量
将其组成元组(requestIP,1)然后进行统计。注意进行排序是对第二个元素进行降序的排列。
logRDDv8 = logRDDv1.map(lambda x: (x[2], 1)).reduceByKey(add)
PagePV = logRDDv8.sortBy(lambda x: x[1], ascending=False).collect()
通过查看PagePV我们发现有大量的js文件的访问,这不是我们需要的内容,因此我们增加一个去除列表,凡是访问文件的后缀名属于列表则不过滤掉,重新实现上述算法:
stopList = ['jpg', 'ico', 'png', 'gif', 'css', 'txt', 'asp']
def filterWithStop(s):
for c in stopList:
if s.endswith('.'+c):
return False
return True
// 如果为False 不进行过滤,为True进行过滤
logRDDv9 = logRDDv1.filter(lambda x: filterWithStop(x[2]))