使用 Pandas 分析 Apache 日志
本文的作者是 Nikolay Koldunov,本文原文是
Apache log analysis with Pandas
注本文的圖有問題,沒法引用,還是去原文看下,這里作為一個引子。
%pylab inline歡迎來到 pylab,一個基于 matplotlib 的 Python 環境【backend: module://IPython.kernel.zmq.pylab.backend_inline】。想要了解更多信息,請鍵入 'help(pylab)'。
在這個筆記中,我們將展示一個使用 pandas 分析 Apache 訪問日志的簡單示例。這是我第一次使用 pandas,并且我確定會有更好以及更有效率的方式來做這里展示的事情。所以評論,建議和修正我的蹩腳英語是非常歡迎的。你可以給我發送郵件或者是為這個筆記的 github 創建一個 PR。
加載和解析數據
我們將需要 apachelog 模塊,用來解析日志。我們也需要知道設置在 Apache 配置中的日志格式。在我的案例中,我沒有訪問 Apache 配置,但是主機托管服務提供商在他的幫助頁提供了日志格式的描述。下面是它自己的格式以及每個元素的簡單描述:
format = r'%V %h %l %u %t \"%r\" %>s %b \"%i\" \"%{User-Agent}i\" %T'這里(大部分拷貝自這個 SO 文章):
%V - 根據 UseCanonicalName 設置的服務器名字 %h - 遠程主機(客戶端 IP) %l - identity of the user determined by identd (not usually used since not reliable) %u - 由 HTTP authentication 決定的 user name %t - 服務器完成處理這個請求的時間 %r - 來自客戶端的請求行。 ("GET / HTTP/1.0") %>s - 服務器端返回給客戶端的狀態碼(200, 404 等等。) %b - 響應給客戶端的響應報文大小 (in bytes) \"%i\" - Referer is the page that linked to this URL. User-agent - the browser identification string %T - Apache 請求時間 In [3]:import apachelog, sys設置格式:
In [4]:fformat = r'%V %h %l %u %t \"%r\" %>s %b \"%i\" \"%{User-Agent}i\" %T'創建一個解析器:
In [5]:p = apachelog.parser(fformat)簡單字符串:
koldunov.net 85.26.235.202 - - [16/Mar/2013:00:19:43 +0400] "GET /?p=364 HTTP/1.0" 200 65237 "http://koldunov.net/?p=364" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11" 0 In [6]:sample_string = 'koldunov.net 85.26.235.202 - - [16/Mar/2013:00:19:43 +0400] "GET /?p=364 HTTP/1.0" 200 65237 "http://koldunov.net/?p=364" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11" 0' In [7]:data = p.parse(sample_string) In [8]:data Out[8]: {'%>s': '200','%T': '0','%V': 'koldunov.net','%b': '65237','%h': '85.26.235.202','%i': 'http://koldunov.net/?p=364','%l': '-','%r': 'GET /?p=364 HTTP/1.0','%t': '[16/Mar/2013:00:19:43 +0400]','%u': '-','%{User-Agent}i': 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11'}這就是解釋器的工作。現在讓我們加載真實世界的數據(示例文件位于這里和這里):
In [9]:log = open('access_log_for_pandas').readlines()解析每一行,并且創建一個字典列表:
In [10]: log_list = [] for line in log:try:data = p.parse(line)except:sys.stderr.write("Unable to parse %s" % line)data['%t'] = data['%t'][1:12]+' '+data['%t'][13:21]+' '+data['%t'][22:27]log_list.append(data)我們不得不調整時間格式位,否則的話 pandas 將不能解析它。
創建和調整數據幀
這將創建一個字典列表,可以轉化到一個數據幀:
import pandas as pd import numpy as np from pandas import Series, DataFrame, Panel df = DataFrame(log_list)展示數據幀的前兩行:
df[0:2]| 0 | 200 | 0 | www.oceanographers.ru | 26126 | 109.165.31.156 | - | - | GET /index.php?option=com_content&task=section... | 16/Mar/2013 08:00:25 +0400 | - | Mozilla/5.0 (Windows NT 6.1; rv:19.0) Gecko/20... |
| 1 | 200 | 0 | www.oceanographers.ru | 10532 | 109.165.31.156 | http://www.oceanographers.ru/index.php?option=... | - | GET /templates/ja_procyon/css/template_css.css... | 16/Mar/2013 08:00:25 +0400 | - | Mozilla/5.0 (Windows NT 6.1; rv:19.0) Gecko/20... |
我們不準備使用所有的數據,因此讓我們刪除一些列:
del df['%T']; del df['%V']; del df['%i']; del df['%l']; del df['%u']; del df['%{User-Agent}i']并且把這些列重命名成人類可理解的格式:
df = df.rename(columns={'%>s': 'Status', '%b':'b', '%h':'IP', '%r':'Request', '%t': 'Time'})結果數據幀的前 5 行:
df.head()| 0 | 200 | 26126 | 109.165.31.156 | GET /index.php?option=com_content&task=section... | 16/Mar/2013 08:00:25 +0400 |
| 1 | 200 | 10532 | 109.165.31.156 | GET /templates/ja_procyon/css/template_css.css... | 16/Mar/2013 08:00:25 +0400 |
| 2 | 200 | 1853 | 109.165.31.156 | GET /templates/ja_procyon/switcher.js HTTP/1.0 | 16/Mar/2013 08:00:25 +0400 |
| 3 | 200 | 37153 | 109.165.31.156 | GET /includes/js/overlib_mini.js HTTP/1.0 | 16/Mar/2013 08:00:25 +0400 |
| 4 | 200 | 3978 | 109.165.31.156 | GET /modules/ja_transmenu/transmenuh.css HTTP/1.0 | 16/Mar/2013 08:00:25 +0400 |
轉換時間列成 datetime 格式并做一個索引出來(pop 將丟棄原始的 Time 列):
df.index = pd.to_datetime(df.pop('Time'))Status 變量是一個 string 類型,因此我們需要把它轉換成 int:
df['Status'] = df['Status'].astype('int')一些 b 列的行包含 '-' 字符,我們需要使用 astype 轉換它們:
df['b'][93] Out[19]: '-'我們可以為該列使用一個通用的函數,它們將把所有的破折號轉換成 NaN,并且剩余的轉換成 floats,另外把 bytes 轉換成 megabytes:
def dash2nan(x):if x == '-':x = np.nanelse:x = float(x)/1048576.return x df['b'] = df['b'].apply(dash2nan)我相信有一個更優雅的方式來做到這一點。
流量分析
首先,最簡單的散點:從該網站的出口流量:
df['b'].plot() <matplotlib.axes.AxesSubplot at 0xbf7574c>看起來在早上 9 點左右有人從網站下載了一些大的東西。
但是實際上你想知道的第一件事是你的網站有多少的訪問量,以及它們的時間分布。我們從 b 變量的 5 分鐘間隔重新取樣,并計算每個時間跨度的請求數。實際上,在這個示例中不管我們使用哪個變量,這些數字將表明有多少次請求該網站的信息請求。
df_s = df['b'].resample('5t', how='count') df_s.plot() Out[23]: <matplotlib.axes.AxesSubplot at 0xc14588c>![此處輸入圖片的描述][8]
我們不僅僅計算每個時間的請求數,也計算每個時間段的總流量:
df_b = df['b'].resample('10t', how=['count','sum']) df_b['count'].plot( color='r') legend() df_b['sum'].plot(secondary_y=True) Out[24]: <matplotlib.axes.AxesSubplot at 0xc2d53ac>![此處輸入圖片的描述][9]
正如你所看到的,服務器請求數和流量是不一致的,相關性其實并不是非常高:
df_b.corr()|-| count| sum
|count| 1.000000| 0.512629
|sum| 0.512629| 1.000000
我們可以仔細看下早高峰:
df_b['2013-03-16 6:00':'2013-03-16 10:00']['sum'].plot() Out[26]: <matplotlib.axes.AxesSubplot at 0xc3f5dac>![此處輸入圖片的描述][10]
看起來流量峰值是由一個請求引起的。讓我們找出這個請求。選擇所有響應大于 20 Mb 的請求:
df[df['b']>20]| Time | ||||
| 2013-03-16 09:02:59 | 200 | 21.365701 | 77.50.248.20 | GET /books/Bondarenko.pdf HTTP/1.0 |
這是一本書的 pdf 文件,這就解釋了在 2013-03-16 09:02:59 的流量出口峰值。
接近 20 Mb 是一個大的請求(至少對于我們網站),但是服務器響應的典型大小是?響應大小(小于 20Mb)的立方圖看起來像這樣:
cc = df[df['b']<20] cc.b.hist(bins=10) Out[28]: <matplotlib.axes.AxesSubplot at 0xc52374c>![此處輸入圖片的描述][11]
因此,大部分的文件是小于 0.5 Mb。實際上它們甚至更小:
cc = df[df['b']<0.3] cc.b.hist(bins=100) Out[29]: <matplotlib.axes.AxesSubplot at 0xc5760ec>![此處輸入圖片的描述][12]
總結
以上是生活随笔為你收集整理的使用 Pandas 分析 Apache 日志的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梦到亲戚是什么意思
- 下一篇: UI代码练习-视图的层次关系