1. 程式人生 > >Spark 分析Apache日誌

Spark 分析Apache日誌

宣告:沒部落格內容由本人經過實驗樓整理而來

內容描述

在給定的日誌檔案是一個標準的Apache2 程式產生的access.log檔案,根據業務需求,我們需要分析得到下面幾方面的資料:

  1. 統計每日PV和獨立IP
  2. 統計每種不同的HTTP狀態對應的訪問數
  3. 統計不同獨立IP的訪問量
  4. 統計不同頁面的訪問量

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日誌內容從左到右依次是:

  1. 遠端IP地址
  2. 客戶端記錄
  3. 瀏覽者記錄
  4. 請求的時間,包括三項內容:

    • 日期
    • 時間
    • 時區
  5. 伺服器收到的請求,包括三項內容:

    • METHOD:請求的方法,GET/POST等
    • RESOURCE:請求的目標連結地址
    • PROTOCOL:HTTP版本
  6. 狀態程式碼,表示請求是否成功
  7. 傳送的位元組數
  8. 發出請求時所在的URL
  9. 客戶端的詳細資訊:作業系統及瀏覽器等

這裡我們提取的資訊為:
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正則表示式
為了避免一些雜亂的無法解析的資料干擾,我們使用正則表示式做兩件事情,一個是過濾掉無法解析的日誌,一個是解析獲得需要的資料元組

實現兩個函式:

  1. filterWithParse:過濾無法解析的日誌記錄
  2. 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]))