python - 分析 access 日誌文件
nginx 的 access 日誌格式約定:
#全局變量
# $args 這個變量等於請求行中的參數,同$query_string
# $content_length 請求頭中的Content-length字段。
# $content_type 請求頭中的Content-Type字段。
# $document_root 當前請求在root指令中指定的值。
# $host 請求主機頭字段,否則爲服務器名稱。
# $http_user_agent 客戶端agent信息
# $http_cookie 客戶端cookie信息
# $limit_rate 這個變量可以限制連接速率。
# $request_method 客戶端請求的動作,通常爲GET或POST。
# $remote_addr 客戶端的IP地址。
# $remote_port 客戶端的端口。
# $remote_user 已經經過Auth Basic Module驗證的用戶名。
# $request_filename 當前請求的文件路徑,由root或alias指令與URI請求生成。
# $scheme HTTP方法(如http,https)。
# $server_protocol 請求使用的協議,通常是HTTP/1.0或HTTP/1.1。
# $server_addr 服務器地址,在完成一次系統調用後可以確定這個值。
# $server_name 服務器名稱。
# $server_port 請求到達服務器的端口號。
# $request_uri 包含請求參數的原始URI,不包含主機名,如:”/foo/bar.php?arg=baz”。
# $uri 不帶請求參數的當前URI,$uri不包含主機名,如”/foo/bar.html”。
# $document_uri 與$uri相同。
#參數註釋:
# $time_local #訪問的時間
# $http_host #訪問的服務端域名
# $request #用戶的http請求起始行信息
# $status #http狀態碼,記錄請求返回的狀態碼,例如:200、301、404等
# $body_bytes_sent #服務器發送給客戶端的響應body字節數
# $http_referer #記錄此次請求是從哪個連接訪問過來的,可以根據該參數進行防盜鏈設置。
# $http_x_forwarded_for #當前端有代理服務器時,設置web節點記錄客戶端地址的配置,此參數生效的前提是代理服務器也要進行相關的x_forwarded_for設置
# $request_time #nginx處理請求的時間
# $upstream_response_time #後端應用響應時間
log_format main '"$time_local","$request","$remote_user",'
'"$http_user_agent","$http_referer","$remote_addr","$status","$body_bytes_sent","$upstream_response_time"';
'''
約定:
nginx 的 log 目錄下有兩個目錄bac、analyze
bac 每日備份的 access log,文件命名格式:qmw_access-200425.log
analyze 存放分析完的結果文件。
調用:
python nginx_logs_spliter.py --nginxConf=nginx.conf --nginxDir=/Users/site/nginx --logPrefix=qmw_access
參數:
args.nginxConf nginx配置文件名
args.nginxDir nginx配置文件目錄
args.logPrefix access log 前綴
args.signal 信號:today=分析今天的log yestoday=分析昨天的log,不傳默認分析昨日數據。
windows 部署:
系統執行計劃,調用 bat 。
批處理文件內容:
python nginx_logs_spliter.py --nginxConf=nginx.conf --nginxDir=/Users/site/nginx --logPrefix=qmw_access
'''
nginx_access_analyze.py 源碼
#!/usr/bin/env python3
# coding=utf-8
import os
import sys
import argparse
import codecs
import time,datetime
import re
_version='200426.1'
_debugCount = 50000
_isDebug = True
_isDebug = False
_ipDic = dict() # ip 統計
_reqTimes = dict() # 併發統計(秒)
_statusDic = dict() # 響應碼
_spiderDic = dict() # 爬蟲統計
_userAgentDic = dict() # ua 統計
class requestInfo():
dateTime = None # 請求時間
path = "" # 請求地址
ip = ""
refererUrl = "" # 引用地址
userAgent = ""
scStatus = 0 # 狀態碼
scBytes = 0 # 返回的字節數
timeTaken = 0 # 處理時間
cookie = ""
class reqTimesModel:
times = 0
scBytes = 0
timeTaken = 0
statusDic = None
ipDic = None
class spiderInfo:
name = ''
total = 0
total_404 = 0
subDic = None # dict() # 子站點統計
ipDic = None # dict() # 爬蟲ip 統計
class userAgentInfo:
userAgent=""
total = 0
codeDic = None # 狀態碼統計
ipDic = None # dict() # ip 統計,# 統計時必須新建字典
'''
log 格式:
"2020-04-25 00:25:41","GET /xxx/xxx.jpg HTTP/1.1","-","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36","-","127.0.0.1","499","0","0.402"
log field:
'"$time_local","$request","$remote_user",'
'"$http_user_agent","$http_referer","$remote_addr","$status","$body_bytes_sent","$upstream_response_time"';
'''
dtEnMatch = re.compile('\d+/[a-zA-Z]+/\d+:\d+:\d+:\d+')
dtCnMatch = re.compile('\d+-\d+-\d+\s\d+:\d+:\d+')
clearMatch = re.compile('^(,?)"|"$')
def getContent(txt):
if not txt:
return txt
return clearMatch.sub('',txt)
urlMatch = re.compile('\s.+\s')
def getPath(txt):
if not txt:
return txt
# re.findall('\s.+\s',txt)[0].strip()
arr = urlMatch.findall(txt)
if len(arr) > 0:
return arr[0].strip()
'''
python中時間日期格式化符號:
%y 兩位數的年份表示(00-99)
%Y 四位數的年份表示(000-9999)
%m 月份(01-12)
%d 月內中的一天(0-31)
%H 24小時制小時數(0-23)
%I 12小時制小時數(01-12)
%M 分鐘數(00=59)
%S 秒(00-59)
%a 本地簡化星期名稱
%A 本地完整星期名稱
%b 本地簡化的月份名稱
%B 本地完整的月份名稱
%c 本地相應的日期表示和時間表示
%j 年內的一天(001-366)
%p 本地A.M.或P.M.的等價符
%U 一年中的星期數(00-53)星期天爲星期的開始
%w 星期(0-6),星期天爲星期的開始
%W 一年中的星期數(00-53)星期一爲星期的開始
%x 本地相應的日期表示
%X 本地相應的時間表示
%Z 當前時區的名稱
'''
def getDatetime(txt):
#兼容兩種時間格式
'''
"26/Apr/2020:15:18:33 +0800"
"2020-04-25 00:25:41"
'''
# "26/Apr/2020:15:18:33 +0800"
arr = dtEnMatch.findall(txt)
if len(arr)>0:
dt = datetime.datetime.strptime(arr[0],'%d/%b/%Y:%H:%M:%S')
return dt
# "2020-04-25 00:25:41"
arr = dtCnMatch.findall(txt)
if len(arr)>0:
dt = datetime.datetime.strptime(arr[0],'%Y-%m-%d %H:%M:%S')
return dt
colMatch = re.compile(',?".+?"')
def getInfo(row):
arr = colMatch.findall(row)
if len(arr)<9:
print('arr len < 9 of row spilit')
reqInfo = requestInfo()
reqInfo.dateTime = getDatetime(arr[0])
reqInfo.path = getPath(arr[1])
reqInfo.userAgent = getContent(arr[3])
reqInfo.refererUrl = getContent(arr[4])
reqInfo.ip = getContent(arr[5])
reqInfo.scStatus = getContent(arr[6])
reqInfo.scBytes = getContent(arr[7])
reqInfo.timeTaken = getContent(arr[8])
return reqInfo
def dictTostring(dic,sort=False,limit=100):
items = None
index = 0
if sort:
#按 value 倒序
items = sorted(dic.items(),key=lambda x:x[1],reverse=True)
else:
items = dic.items()
sbr = 'c=%s\t' % str(len(dic))
for (k,v) in items:
if len(sbr)>0:
sbr +='|'
if index > limit:
sbr += 'top %s ...' % limit
break;
index += 1
sbr += '{}:{}'.format(k,v)
return sbr
def analyze(logFileFullName,outputFileFullName,rows,timeCost):
curTime = datetime.datetime.now()
f = open(outputFileFullName,'w+',encoding='utf-8')
#
f.writelines("分析文件 = {}\n".format(logFileFullName))
f.writelines("輸出文件 = {}\n".format(outputFileFullName))
f.writelines("版本號 = +{}\n".format(_version))
f.writelines("\n{}\n".format('~'*80))
#---------------------------------------------------------
f.writelines("#彙總時間\t{}\n".format(curTime.strftime('%Y-%m-%d %H:%M:%S')))
f.writelines("#總記錄數 = {}\n".format(rows))
f.writelines("#搜索爬蟲 = {}\n".format(len(_spiderDic)))
f.writelines("#總ip數= {}\n".format(len(_ipDic)))
f.writelines("#總userAgent數= {}\n".format(len(_userAgentDic)))
# f.writelines("#總目錄量 = {}\n".format(len(_dirDic)))
# f.writelines("#總小時數 = {}\n".format(len(_hourDic)))
f.writelines("#_isDebug = {}\n".format(_isDebug))
f.writelines('#分析耗時 = %s\n' % timeCost)
#---------------------------------------------------------
# status輸出
f.writelines("\n{}\n".format('~'*80))
statusTotal = sum(_statusDic.values())
f.writelines("#響應碼 {}種 共 {} 條\n".format(len(_statusDic),statusTotal))
for (k,v) in _statusDic.items():
f.writelines('{}\t{}%\t{}\n'.format(k
,round(v/statusTotal*100,5)
,v))
#---------------------------------------------------------
# 爬蟲統計出參
spiderList = sorted(_spiderDic.items(),key=lambda x:x[1].total,reverse=True)
print('sort\tspiderSort.size={}\t{}\n'.format(len(spiderList),type(spiderList)))
f.writelines("\n{}\n".format('~'*80))
f.writelines("\n{}\n".format('#爬蟲統計'))
# 爬蟲
for (k,v) in spiderList:
f.writelines('總={}\terr={}\t{}\n'.format(str(v.total).zfill(6)
,str(v.total_404).zfill(6)
,k))
f.writelines("\n{}\n".format('#爬蟲統計 - IP'))
# 爬蟲
for (k,v) in spiderList:
f.writelines('{}\t{}\n'.format(k
,dictTostring(v.ipDic)))
#---------------------------------------------------------
#位置 userAgent
f.writelines("\n{}\n".format('~'*80))
f.writelines("\n{}\ttotal={}\n".format('#userAgent top',len(_userAgentDic)))
userAgentList = sorted(_userAgentDic.items(),key=lambda x:x[1].total,reverse=True)
index = 0
for (k,v) in userAgentList:
if index > 30:
f.writelines('top 30 ...\n')
break
index += 1
f.writelines('{}\t{}\n'.format(str(v.total).zfill(6)
,v.userAgent))
#codd 輸出
f.writelines('------\t響應碼\t%s\n'%(dictTostring(v.codeDic)))
# for (code,ct) in v.codeDic.items():
# f.writelines('------\t\t%s\t%s\n' % (code,ct))
#ip 次數輸出
f.writelines('------\tips統計\t%s\n' % (dictTostring(v.ipDic)))
f.writelines("\n{}\n".format('~'*80))
f.writelines("\n{}\ttotal={}\n".format('#userAgent all',len(_userAgentDic)))
for (k,v) in userAgentList:
index += 1
f.writelines('{}\t{}\n'.format(str(v.total).zfill(6)
,v.userAgent))
#---------------------------------------------------------
# 輸出 所有ip
print("\n\n所有ip - 按請求次數排序 total = {}",len(_ipDic))
# 利用 lambda 定義一個匿名函數 key,參數爲 x 元組(k,v),x[1]是值,x[0]是鍵。reverse參數接受False 或者True 表示是否逆序
ipList = sorted(_ipDic.items(),key=lambda x:x[1],reverse=True) # 按次數排序
print('sort\tipList.size={}\t{}\n'.format(len(ipList),type(ipList)))
f.writelines("\n{}\n".format('~'*80))
f.writelines("#所有ip - 按請求次數排序 = {}\n"
.format(len(ipList)))
top = 0
for (k,v) in ipList:
top += 1
if top >= 100:
f.writelines("print top 100 ... \n")
break
f.writelines('{}\t{}\n'.format(k,v))
#---------------------------------------------------------
#---------------------------------------------------------
print('分析彙總輸出完成\t%s'%outputFileFullName)
def ipTotal(reqInfo):
if reqInfo.ip not in _ipDic.keys():
_ipDic[reqInfo.ip] = 0
_ipDic[reqInfo.ip] += 1
def timesSecondTotal(reqInfo):
key = reqInfo.dateTime.strftime('%Y-%m-%d %H:%M:%S') # datetime to 字符串
if key not in _reqTimes.keys():
item = reqTimesModel()
item.scBytes = reqInfo.scBytes
item.times = 0
item.timeTaken = reqInfo.timeTaken
item.ipDic = dict()
item.statusDic = dict()
_reqTimes[key] = item
item = _reqTimes[key]
item.scBytes += reqInfo.scBytes
item.times += 1
item.timeTaken += reqInfo.timeTaken
# 記錄ip及次數
ip = reqInfo.ip
# dic = item.ipDic
if ip not in item.ipDic.keys():
item.ipDic[ip]=0
item.ipDic[ip] += 1
# 記錄狀態碼
st = reqInfo.scStatus
#dic = item.statusDic
if st not in item.statusDic.keys():
item.statusDic[st]=0
item.statusDic[st] += 1
def statusTotal(reqInfo):
key = reqInfo.scStatus
if key not in _statusDic.keys():
_statusDic[key] = 0
_statusDic[key] += 1
def getSpiderKey(ua):
key = 'other'
if not ua:
return key
# 百度 baidu
if bool(re.search('baidu', ua , re.IGNORECASE)):
return 'baidu-百度'
# 神馬搜索 YisouSpider
if bool(re.search('YisouSpider', ua , re.IGNORECASE)):
return 'shenma-神馬'
# 谷歌 google
if bool(re.search('google', ua , re.IGNORECASE)):
return 'google-谷歌'
# 搜狗 (sogou)
if bool(re.search('sogou', ua , re.IGNORECASE)):
return 'sogou-搜狗'
# 360搜索(360Spider、.360.)
if bool(re.search('360Spider|\.360\.', ua , re.IGNORECASE)):
return '360-搜索'
# 必應 bing
if bool(re.search('bing', ua , re.IGNORECASE)):
return 'bing-必應'
# 搜搜 soso
if bool(re.search('soso', ua , re.IGNORECASE)):
return 'soso-搜搜'
# 雅虎 yahoo
if bool(re.search('yahoo', ua , re.IGNORECASE)):
return 'yahoo-雅虎'
# ahrefs 通過抓取網頁建立索引庫,並提供反向鏈接分析和服務
if bool(re.search('ahrefs', ua , re.IGNORECASE)):
return 'ahrefsBot-爬蟲'
# Semrush 國外的SEO分析爬蟲
if bool(re.search('Semrush', ua , re.IGNORECASE)):
return 'SemrushBot-爬蟲'
# python 爬蟲 https://scrapy.org
if bool(re.search('Scrapy', ua , re.IGNORECASE)):
return 'Scrapy-爬蟲'
return key
def userAgentTotal(reqInfo):
key = reqInfo.userAgent
ip = reqInfo.ip
code = reqInfo.scStatus
if key not in _userAgentDic.keys():
uaInfo = userAgentInfo()
uaInfo.userAgent = key
uaInfo.codeDic = dict()
uaInfo.ipDic = dict() # 必須新建字典,否則統計時將重複
_userAgentDic[key] = uaInfo
uaInfo = _userAgentDic[key]
#ip 統計
if ip not in uaInfo.ipDic.keys():
uaInfo.ipDic[ip] = 0
# code 統計
if code not in uaInfo.codeDic.keys():
uaInfo.codeDic[code] = 0
uaInfo.total += 1
uaInfo.ipDic[ip] += 1
uaInfo.codeDic[code] += 1
def spiderTotal(reqInfo):
ua = reqInfo.userAgent
key = getSpiderKey(ua)
ip = reqInfo.ip
info = spiderInfo()
if key not in _spiderDic.keys():
info.ipDic = dict()
_spiderDic[key] = info
# 總
info = _spiderDic[key]
info.total += 1
# ip
if ip not in info.ipDic.keys():
info.ipDic[ip] = 0
info.ipDic[ip] += 1
# 狀態
if reqInfo.scStatus != '200' and reqInfo.scStatus != '301':
info.total_404 += 1
# #不識別的 ua 統計
# if key == 'other':
# userAgentTotal(reqInfo)
def main(nginxConf,nginxDir, logPrefix,signal):
if not nginxDir or not logPrefix:
print("參數爲空:--nginxDir={} --logPrefix={}".format(nginxDir, logPrefix))
return
if not os.path.exists(nginxDir):
print("文件不存在:--nginxDir={} ".format(nginxDir))
return
conf = os.path.join(nginxDir,nginxConf)
if not os.path.exists(conf):
print("nginx config 不存在:--nginxConf={} ".format(conf))
return
# 今日
today = datetime.datetime.now();
# 日期後綴
yymmdd = ""
if signal == 'today':
yymmdd = today.strftime('%y%m%d')
else:
yestoday = datetime.date.today()-datetime.timedelta(days=1)
yymmdd = yestoday.strftime('%y%m%d')
# 備份文件是否存在
logFileName = "%s-%s.log" % (logPrefix,yymmdd)
logFileFullName = os.path.join(nginxDir,'logs','bac',logFileName)
if not os.path.exists(logFileFullName):
print("log不存在:%s "%logFileFullName)
return
# 讀log
start = datetime.datetime.now()
totalCount = 0
with open(logFileFullName,'r',encoding='utf-8') as f:
for line in f.readlines():
totalCount += 1
with open(logFileFullName,'r',encoding='utf-8') as f:
rows = 0
# 按行統計
while True:
rows += 1
if rows % 10000 == 0:
print('已分析\t%s/%s\t耗時\t%ss' % (rows
,totalCount
,(datetime.datetime.now() - start).seconds))
# ------
if _isDebug and rows>=_debugCount:
print('_isDebug = ',_isDebug)
break
# ------
line = f.readline()
if not line: #等價於if line == "":
break
if line.startswith('#'):
print("跳過註釋內容=>",line)
continue
reqInfo = getInfo(line)
if not reqInfo:
print("解析失敗 reqInfo is null,row = {}".format(line))
continue
# ip 統計,訪問量
ipTotal(reqInfo)
# 併發統計
timesSecondTotal(reqInfo)
# 錯誤碼統計
statusTotal(reqInfo)
# 爬蟲統計
spiderTotal(reqInfo)
# ua 統計
userAgentTotal(reqInfo)
print('已分析完成\t%s/%s' % (rows,totalCount))
#統計時間
timeCost = datetime.datetime.now() - start
# 執行分析
outputFileName = "analyze-%s-%s.log" % (logPrefix,yymmdd)
outputFileFullName = os.path.join(nginxDir,'logs','analyze',outputFileName)
analyze(logFileFullName,outputFileFullName,rows,timeCost)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='manual to this script')
parser.add_argument('--nginxConf', type=str, default = None)
parser.add_argument('--nginxDir', type=str, default = None)
parser.add_argument('--logPrefix', type=str, default= None)
parser.add_argument('--signal', type=str, default= None)
args = parser.parse_args()
sys.exit(main(args.nginxConf,args.nginxDir,args.logPrefix,args.signal))
'''
約定:
nginx 的 log 目錄下有兩個目錄bac、analyze
bac 每日備份的 access log,文件命名格式:qmw_access-200425.log
analyze 存放分析完的結果文件。
調用:
python nginx_access_analyze.py --nginxConf=nginx.conf --nginxDir=/Users/site/nginx --logPrefix=qmw_access
參數:
args.nginxConf nginx配置文件名
args.nginxDir nginx配置文件目錄
args.logPrefix access log 前綴
args.signal 信號:today=分析今天的log yestoday=分析昨天的log,不傳默認分析昨日數據。
windows 部署:
系統執行計劃,調用 bat 。
批處理文件內容:
python nginx_logs_spliter.py --nginxConf=nginx.conf --nginxDir=/Users/site/nginx --logPrefix=qmw_access
'''
分析結果,部分內容
分析文件 = /Users/site/temp/nginx/logs/bac/qmw_access-200425.log
輸出文件 = /Users/site/temp/nginx/logs/analyze/analyze-qmw_access-200425.log
版本號 = +200426.1
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#彙總時間 2020-04-25 16:29:39
#總記錄數 = 394802
#搜索爬蟲 = 9
#總ip數= 107888
#總userAgent數= 1239
#_isDebug = False
#分析耗時 = 0:00:39.931531
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#響應碼 11種 共 394801 條
504 0.07219% 285
200 7.73706% 30546
403 68.78782% 271575
301 23.30136% 91994
304 0.01444% 57
500 0.02736% 108
499 0.01089% 43
400 0.00253% 10
206 0.0228% 90
503 0.0228% 90
408 0.00076% 3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#爬蟲統計
總=357502 err=255417 other
總=024131 err=015927 google-谷歌
總=009713 err=000051 SemrushBot-爬蟲
總=001947 err=000426 baidu-百度
總=001075 err=000196 sogou-搜狗
總=000239 err=000239 shenma-神馬
總=000191 err=000004 bing-必應
總=000002 err=000000 360-搜索
總=000001 err=000001 ahrefsBot-爬蟲
。。。。。。