相信各位web后端的小伙伴对Nginx并不陌生,它是是一款面向性能设计的HTTP服务器,具有占有内存少,稳定性高等优势。所以很多个人网站,或者公司都会选择使用nginx作为服务器。在使用nginx的时候,每一个http请求都会产生一条日志,通过python分析日志我们可以清楚的了解网站的pv,uv等一些重要数据。
在服务器上我们通常使用logrotate来分割当天日志进行分析, 假设我当天结束分割出的的日志名字为log20101001.gz, 我们使用python的gzip库来读取这个压缩文件所以我们可以直接使用gzip库来打开文件
class an_log(object):
"""分析记录"""
def __init__(self, filename):
self.filename = filename
self.picid_value = {} # 一个用于存储所有pv,uv的字典
def read_log(self):
f = gzip.open(self.filename, 'r')
for line in f:
all_line = line.split()
一般一条nginx数据是这样的:
- 180.171.241.42 tb.lifehp.com - - [30/Oct/2016:23:58:03 +0800] "GET /index.php?m=newbar&a=operate&type=1&op=0&pa
看起来很杂乱无章,其实在分析pv,uv的时候我们着重需要的数据一共只有几个, 一个是ip:- 180.171.241.42
,
一个是参数:GET/index.php?m=newbar&a=operate&type=1&op=0¶m=A000184&adsid=14&picid=141&time=1477842977818 HTTP/1.1
, 一个是参数:GET/index.php?m=newbar&a=operate&type=1&op=0¶m=A000184&adsid=14&picid=141&time=1477842977818 HTTP/1.1
每一个pv是由这些参数共同决定的,如果这些参数全部一致那就是一个pv,如果在参数一致的情况下ip从没出现过那就是一个uv, 所以我们只需要解析这些参数就可以
def read_log(self):
f = gzip.open(self.filename, 'r')
for line in f:
all_line = line.split()
try:
a_line = dict(k.split('=') for k in all_line[8].split('&'))
# 把所有参数解析到一个字典中,key为参数名,value为参数值类似于{type:1,param:A00184}
ip = all_line[1] # 获取ip
media_id = a_line['param']
op = a_line['op']
except:
continue
try:
adsid = a_line.get('adsid', 'null')
picid = a_line.get('picid', 'null')
area_name = a_line.get('area', 'null')
os = a_line.get('os', '0')
except:
continue
使用try,except语句是因为很多日志并不是我们想要的类似于GET /small/v10/css/tlbs.css HTTP/1.1
,当碰到这种类型的日志的时候,生成字典后通过try如果提取不到我们需要的参数就说明这条日志不需要,通过continue直接分下一条日志 pic = picid + media_id + adsid + op + area_name + os # 把参数组合生成唯一键名
如果键名存在就直接对pv,uv数值操作
if pic in self.picid_value.keys():
self.analysis_pv(pic)
self.analysis_uv(pic, ip)
pv += 1 uv通过判断ip是否存在来判断是否加一
def analysis_pv(self, pic,):
self.picid_value[pic]['pv'] += 1
return self.picid_value
def analysis_uv(self, pic, ip):
if ip not in self.picid_value[pic]['ip']:
self.picid_value[pic]['uv'] += 1
self.picid_value[pic]['ip'].add(ip)
return self.picid_value
这里ip要使用set集合,如果使用列表 每次判断ip是否存在都要进行一次遍历时间复杂度为O(n),而集合判断是否存在时间复杂度为O(1)大大提高性能
如果键名不存在则进行初始化键
if pic in self.picid_value.keys():
self.analysis_pv(pic)
self.analysis_uv(pic, ip)
else:
self.picid_value[pic] = {}
self.picid_value[pic]['picid'] = picid
self.picid_value[pic]['adsid'] = adsid
self.picid_value[pic]['media_id'] = media_id
self.picid_value[pic]['op'] = op_name_dict[op]
self.picid_value[pic]['os'] = os_name_dict[os]
self.picid_value[pic]['pv'] = 0
self.picid_value[pic]['uv'] = 0
self.picid_value[pic]['ip'] =set()
self.picid_value[pic]['area']=area_name_dict[area_name]
self.analysis_pv(pic)
self.analysis_uv(pic, ip)
最后返回一个字典
return self.picid_value
然后把分析的数据写入excel,这里我使用的是xlwt库
def write_excel(self, excel_name):
workbook = xlwt.Workbook()
worksheet = workbook.add_sheet('toolbar')
cl_name = [u'媒体', 'op', 'os', 'adsid', 'picid', u'位置', 'pv', 'uv']
c = 0
for data in cl_name:
worksheet.write(0, c, data)
c += 1
row_list = ['media_id', 'op', 'os', 'adsid', 'picid', 'area', 'pv', 'uv']
r = 1
for pic_name in self.picid_value:
cl = 0
for data_name in row_list:
worksheet.write(r, cl, self.picid_value[pic_name][data_name])
cl += 1
r += 1
workbook.save(excel_name)c
使用argparse添加一些参数说明,使用的时候直接 -h就能看到各种参数名字与作用
if __name__ == '__main__':
parser = argparse.ArgumentParser('log statistic')
parser.add_argument('-f', '--file', default=None, help='filename')
args = parser.parse_args()
log_value = an_log(args.file)
if log_value:
log_value.read_log()
file_name1 = 'example2.xls'
log_value.write_excel(file_name1)
也可以使用各种python数据分析库来进行分析,添加分析参数只需要在in_value方法中添加各种相应的参数字段就可以。水平有限,代码还有很多需要改进的地方,如果各位有什么好的想法和建议也欢迎反馈。点击阅读原文可访问作者博客。
原文发布时间为:2016-11-19
本文作者:熊球
本文来自云栖社区合作伙伴“Python中文社区”,了解相关信息可以关注“Python中文社区”微信公众号