#IT明星不是梦#利用Python进行网站日志分析

网站的访问日志是一个很是重要的文件,经过分析访问日志,可以挖掘出不少有价值的信息。本文介绍如何利用Python对一个真实网站的访问日志进行分析,文中将综合运用Python文件操做、字符串处理、列表、集合、字典等相关知识点。本文所用的访问日志access_log来自我我的的云服务器,你们能够从文末的附件中下载。php

1.提取指定日期的日志

下面是一条典型的网站访问日志,客户端访问网站中的每一个资源都会产生一条日志。web

193.112.9.107 - - [25/Jan/2020:06:32:58 +0800] "GET /robots.txt HTTP/1.1" 404 208 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0"编程

每条日志都由空格分隔为九部分,其中比较重要的是:浏览器

  • 第1部分,193.112.9.107 ,客户端的IP地址。
  • 第4部分,[25/Jan/2020:06:32:58 +0800],用户访问请求发生的时间。
  • 第5部分,GET /robots.txt HTTP/1.1,客户端发来的HTTP请求报文首部的第一行信息。这部分采用“请求方法 请求资源 请求协议”的格式表示,是日志中最重要的部分。“GET /robots.txt HTTP/1.1”表示客户端以GET方法请求访问服务器的/robots.txt文件,所使用的HTTP协议版本为HTTP/1.1。
  • 第6部分, “404”,HTTP响应状态码。状态码用于表示用户的请求是否成功,若是该值为200,则表示用户的访问成功,不然就可能存在问题。通常来讲,以2开头的状态码都可以表示用户的访问成功,以3开头的状态码表示用户的请求被页面从新定向到了其它位置,以4开头的状态码表示客户端遇到了错误,以5开头的状态码表示服务器遇到了错误。
  • 第7部分,“208”,响应报文的大小,单位字节,这个数值不包括响应报文的首部。把日志记录中的这些值加起来就能够得知服务器在必定时间内发送了多少数据。
  • 第9部分, "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0",表示客户端发来的HTTP请求报文中首部“User-Agent”的值,即发出请求的应用程序,一般都是浏览器。

一个日志文件中会包含不少天的日志记录,而咱们一般都是针对某一天进行日志分析,因此首先须要从日志文件中把咱们要分析的那一天的日志提取出来。
好比要提取1月25日产生的日志,能够执行下面的代码:安全

>>> with open('access_log','r') as f1, open('access_log-0125','w') as f2:
...     for line in f1:
...             if '25/Jan/2020' in line:
...                     f2.write(line)

在这段代码中,以r读取模式打开日志文件access_log,做为文件对象f1。以w写入模式建立文件access_log-0125,做为文件对象f2。
而后遍历f1中的每一行,并判断是否包含关键字“25/Jan/2020”,若是有的话,就将这行写入到f2中。
这样就提取出了1月25日的全部日志记录,并保存到了文件access_log-0125中。下面咱们就针对文件access_log-0125进行分析。服务器

2.统计PV和UV

PV是指PageView,网站的访问请求数。用户每次对网站中的一个页面的请求或访问均被记录为1个PV,例如某个用户访问了网站中的4个页面,那么PV就+4。并且用户对同一页面的屡次访问,PV也是累计的。
UV是指UniqueView,网站的独立访客数,访问网站的一个IP被视为一个访客,在同一天内相同的IP只被计算一次。
于是,咱们只要取出每条日志中的IP并统计数量,那么就能够获得PV,将IP去重,就能够获得UV。
执行下面的代码,将每条日志的IP提取出来,并存放到列表ips中。app

>>> ips = []
>>> with open('access_log-0125','r') as f:
...     for line in f:
...             ips.append(line.split()[0])

在这段代码中,首先定义了一个空列表ips,而后打开文件access_log-0125并对其进行遍历,每遍历一行,就将该行以空格做为分隔符分割成一个列表,并取出列表中的第一个元素(也就是IP地址),再追加到列表ips中。
下面咱们只要统计列表ips的长度就是PV,将列表元素去重以后,再统计长度就是UV。去重这里采用了set()函数,将列表转换为集合,利用Python集合自己的特性,简单高效的完成去重操做。运维

>>> pv = len(ips)
>>> uv = len(set(ips))
>>> print(pv,uv)
1011 48

3.统计网站出错页面比例

网站的出错比例是很重要的一份数据,直接关系到网站的用户体验。要统计用户访问出错的比例,能够经过统计每一个请求的HTTP状态码获得,状态码为2xx或3xx的,视为访问正确,状态码为4xx或5xx,则视为访问出错。
首先能够提取全部页面的状态码,并保存到列表中。ssh

>>> codes = []
>>> with open('access_log-0125','r') as f:
...     for line in f:
...             codes.append(line.split()[8])

再统计出每种状态码的出现次数,保存到字典中:ide

>>> ret = {}
>>> for i in codes:
...     if i not in ret:
...             ret[i] = codes.count(i)
... 
>>> 
>>> ret
{'200': 192, '404': 796, '"-"': 4, '400': 13, '403': 3, '401': 2, '405': 1}

上面这段代码用到了字典,这里是对存放状态码的列表codes进行遍历,从中取出状态码做为字典的键,并统计这种状态码在列表codes中出现的次数,做为字典的值。
若是要统计404页面的比例,能够执行下面的代码:

>>> ret['404']/sum(ret.values())
0.7873392680514342

在这段代码中,ret['404']表示从字典ret中取出键为‘404’的元素的值,也就是404状态码的个数。ret.values()表示取出字典中全部元素的值,再用sum()函数求和,获得全部状态码的总数量。二者的比值也就是错误页面的比例了。
从结果中能够看出,我这个网站的页面出错比例特别高,居然达到了78.7%,若是是一个正常网站,这确定是有问题的。但我这并非一个公开网站,也没有什么有价值的页面,于是大部分访问日志其实都是由一些漏洞扫描软件产生的,这也提醒咱们,随时都有人在对咱们线上的网站进行着各类扫描测试。

4.统计网站热门资源

下面咱们继续统计出每一个页面的用户访问量,并进行排序。
首先仍然是遍历日志文件,取出用户访问的全部页面,并保存到列表中:

>>> webs = []
>>> with open('access_log-0125','r') as f:
...     for line in f:
...             webs.append(line.split()[6])

接着再统计出每一个页面的访问次数,并存放到字典中:

>>> counts = {}
>>> for i in webs:
...     if i not in counts:
...             counts[i] = webs.count(i)
...

按页面的访问量降序排序:

>>> sorted(counts.items(),key=lambda x:x[1],reverse=True)
[('/', 175), ('/robots.txt', 25), ('/phpinfo.php', 6), ('/Admin13790d6a/Login.php', 4), 
……

为了更好地理解上面这个sorted()函数的用法,下面举例说明。好比咱们定义一个名叫services的字典,若是直接用sorted()函数对这个字典排序,默认是按照键进行升序排序。为了显示字典中的全部内容,可使用items()方法,此时,字典中的每一个键值对会被组合成一个元组,而且默认是按照元组中的第一个元素,也就是字典的键进行排序的。

>>> services = {'http':80,'ftp':21,'https':443,'ssh':22}
>>> sorted(services)
['ftp', 'http', 'https', 'ssh']
>>> sorted(services.items())
[('ftp', 21), ('http', 80), ('https', 443), ('ssh', 22)]

若是但愿按照字典中的值进行排序,也就是要按照元组中的第二个元素排序,能够用key参数指定一个lambda表达式,以每一个元组中的第二个元素做为关键字。

>>> sorted(services.items(),key=lambda x:x[1])
[('ftp', 21), ('ssh', 22), ('http', 80), ('https', 443)]

因此这也就解释了以前那个sorted()函数的含义。至于lambda表达式,其实就是一个根据须要能够随时定义使用的小函数,“lambda x:x[1]”,冒号左侧的x是函数要处理的参数,冒号右侧的表达式是函数要执行的操做,最后再将这个表达式的结果返回。

本文属于“Python安全与运维”系列课程的一部分,该系列课程目前已更新到第二部,感兴趣的朋友能够参考:
第一部 Python基本语法 https://edu.51cto.com/sd/53aa7
第二部 Python编程基础 https://edu.51cto.com/sd/d100c

相关文章
相关标签/搜索