分析Nginx日志并存入MySQL
需求
我的网站托管在VPS上,使用Nginx提供服务。Nginx的日志中大多数是搜索引擎爬虫和DNS服务器的访问记录,真实用户的只占一小部分。我想把真实用户的访问记录提取出来,这样便能获得比Google Analytics更详细的统计信息。
为了便于管理和检索,不能像以前那样用文本保存了,必须要把它存放在数据库中。本文介绍如何用Python分析Nginx日志,从文本中提取感兴趣的信息,然后存入到MySQL数据库中。
日志过滤
日志格式
Nginx的日志默认存放在/var/log/nginx
目录下,access.log
文件是当天的访问记录。Nginx每天定时将access.log
压缩,文件名加上日期信息,这便是目录下.gz
后缀的文件,然后access.log
文件会归零,重新记录。为了防止日志占用太多空间,会定期清理旧的日志文件,在我的服务器上,只保存最近十天的日志。
日志的每一行是一次访问的记录,遵从特定的格式,默认的格式配置如下:
1 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' |
下面是一次访问在日志中的记录内容(为保护用户隐私,已修改IP),请把各字段对号入座:
1 | 1.1.1.1 - - [18/Apr/2019:01:57:50 +0800] "GET / HTTP/1.1" 200 65567 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36" "-" |
下面是我感兴趣的内容:
remote_addr
:访问者的IP地址time_local
:访问时间request
:HTTP请求类型status
:HTTP状态码http_user_agent
:简称UA,包含用户信息
我用Python语言进行文本处理,从一行访问记录中提取出上述感兴趣的信息。接下来是重要的内容过滤。
内容过滤
真实用户的有效访问有如下几个特征:
- HTTP状态码为
200
,判断status
字段即可 - HTTP方法为
GET
,从request
字段中提取HTTP方法即可 - User-Agent表明为浏览器访问
HTTP状态码和HTTP方法这两个过滤条件非常简单,这里就不多说。User-Agent主要用来过滤搜索引擎的爬虫和DNS服务器的访问,主要通过识别关键字完成,需用到正则表达式。正规爬虫的Agent一般都包含Bot、Spider、Crawler、Fetcher等关键词。下面是这个部分的实现代码:
1 | def agent_filter(agent): |
上面的方法只能过滤掉知名的、容易辨别的网络爬虫,其它的诸如RSS订阅器的零星爬取,需要仔细甄别,并添加额外的规则。为了减小工作量,我用IP过滤的方法排除它们。一般来讲,爬虫有个特点,只抓取HTML,不抓取JavaScript代码。我的实现方法是,将通过前面过滤条件的记录保存在一个list中,遍历整个表,将抓取JavaScript的IP看作是正常的浏览器用户,保存到一个set中。再将list遍历一次,只保留set中的IP地址的访问记录。部分代码如下:
1 | valid_user = set() |
通过上面UserAgent和IP过滤的方法,几乎能排除全部的爬虫访问,但也有漏网之鱼。我在日志中发现,偶尔有来自同一网段的多个IP的访问,而且都请求了JavaScript文件。在ipip.net上查询它们的地理位置,发现一般都是国内的机房。要排除这种类型的用户,要用更高级的方法。考虑到它们伪装得这么辛苦,就暂时先放它们一马,这点误差可以容忍,毕竟连Google Analytics都经常识别不出爬虫。
一般来讲,还要排除特定IP的访问,比如自己的IP,方法和User-Agent的类似,就不多说了。
我将这部分代码命名为filter.py
,输入为Nginx的日志,输出打印格式化的访问记录。完整代码在这里,这部分代码的实现需根据自己的情况进行适配。
数据库
数据库软件我选择最常用的MySQL。首先用如下的SQL语句在数据库中创建如下的数据表(Table):
1 | CREATE DATABASE IF NOT EXISTS website; |
插入记录的SQL语句如下:
1 | INSERT INTO record (addr, time, page) |
现在要用Python操作数据库,我用的是MySQLdb模块。由于Nginx的时间格式和MySQL的默认格式不一样,要做格式转换。该过程的代码文件命名为mysql.py
,将filter.py
的输出作为输入,将数据插入到数据库中。代码如下:
1 | #!/usr/bin/python3 |
定时任务
最好让整个过程自动化,步骤依次为:
- Nginx每天的特定时刻将
access.log
打包为.gz
文件,文件名中包含当天的日期,以今天为例,20190425 - 将压缩日志从Nginx的目录拷贝到当前目录,例如文件名为
access.log-20190425.gz
- 解压文件,得到
access.log-20190425
- 过滤日志,得到格式化输出,运行
./filter.py access.log-20190425 > record.log-20190425
- 将访问记录插入到MySQL中,运行
./mysql.py record.log-20190425
- 删除处理完的文本文件
由于Shell的语法过于恶心,就不奉陪了。我用Python的os模块运行命令,文件命名为cmd.py
,并将其他的脚本文件和cmd.py
放到同一个目录中。cmd.py
的内容如下:
1 | #!/usr/bin/python3 |
在我的服务器上,Nginx每天凌晨04:00左右打包access.log
文件。我需要在每天凌晨04:00自动运行上面的cmd.py
脚本,运行crontab -e
命令添加如下的定时任务:
1 | 00 04 * * * /root/script/traffic/cmd.py |
这样,网站每天的访问记录就会自动添加到MySQL数据库中了。我用下面的SQL语句查询了昨天的访问记录:
1 | SELECT addr, time, page FROM record |
页面访问量和Google Analytics的统计相差不大。
总结
其实本文就是用简单的技术实现了自己的需求。数据库是非常重要的技术,而且实践性强,以前在工作中没机会接触,自己又找不到合适的数据库练手。从今天起,我的网站访问记录不仅会自动保存下来,还给我提供了一份非常不错的数据用来练手,这应该是最大的收获吧。