Spark 分析Apache日誌
宣告:沒部落格內容由本人經過實驗樓整理而來
內容描述
在給定的日誌檔案是一個標準的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]))