内容简介:我的网站托管在VPS上,使用Nginx提供服务。Nginx每天产生的日志有数千行,但大多数是搜索引擎爬虫和DNS服务器的记录,真实用户(用浏览器访问的)只占一小部分。我想把真实用户的访问记录提取出来,毕竟这份数据对网站来说还是非常重要的。有了这份数据,我能够获得比Google Analytics更详细的统计信息。为了便于管理和检索,不能像以前那样用文本保存了,必须要把它存放在数据库中。于是便有了本文的记录。Nginx的日志默认存放在日志的每一行为一个访问记录,遵从特定的格式,默认的格式配置如下:
我的网站托管在VPS上,使用Nginx提供服务。Nginx每天产生的日志有数千行,但大多数是搜索引擎爬虫和DNS服务器的记录,真实用户(用浏览器访问的)只占一小部分。我想把真实用户的访问记录提取出来,毕竟这份数据对网站来说还是非常重要的。有了这份数据,我能够获得比Google Analytics更详细的统计信息。为了便于管理和检索,不能像以前那样用文本保存了,必须要把它存放在数据库中。于是便有了本文的记录。
日志过滤
日志格式
Nginx的日志默认存放在 /var/log/nginx
目录下, access.log
文件是当天的访问记录。Nginx每天定时将 access.log
压缩,文件名加上日期信息,这便是目录下 .gz
后缀的文件,然后 access.log
文件的内容归零,重新记录。为了防止日志占用太多空间,会定期清理旧的日志文件,在我的服务器上,只保存最近十天的日志。
日志的每一行为一个访问记录,遵从特定的格式,默认的格式配置如下:
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"';
下面是一个访问记录示例(为保护用户隐私,已修改IP),请把各字段对号入座:
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 time_local request status http_user_agent
我用 Python 语言进行文本处理,从一行访问记录中提取出上述感兴趣的信息。接下来是重要的信息过滤。
内容过滤
我们要过滤出真实用户的有效访问,有如下几个特征:
200 GET
HTTP状态码和 HTTP方法 这两个过滤条件非常简单,这里就不多说。 User-Agent 主要用来过滤搜索引擎的爬虫和DNS服务器的访问,主要通过关键字识别完成,需用到正则表达式。正规爬虫的Agent一般都包含Bot、Spider、Crawler、Fetcher等关键词。下面是这个部分的实现代码:
def agent_filter(agent): # 排除掉网络蜘蛛 if re.search(r"[Bb]ot", agent): return False if re.search(r"[Ss]pider", agent): return False if re.search(r"[Cc]rawler", agent): return False if re.search(r"[Ff]etcher", agent): return False # 排除掉DNS服务器 if re.search(r"DNS", agent): return False # 排除掉空的UA if re.search(r"^\-", agent): return False return True
上面的方法只能过滤掉知名的、容易辨别的网络爬虫,其它的诸如RSS订阅器的零星爬取,需要仔细甄别,并添加额外的规则。为了减小工作量,我用IP过滤的方法排除它们。一般来讲,爬虫有个特点,只抓取HTML,不抓取JavaScript代码。我的实现方法是,将通过前面过滤条件的记录保存在一个list中,遍历整个表,将抓取JavaScript的IP看作是正常的浏览器用户,保存到一个set中。再将list遍历一次,只保留set中的IP地址的访问记录。部分代码如下:
valid_user = set() for i in range(len(rlist)): url = rlist[i][2] addr = rlist[i][0] runjs = re.search(r"^\/js\/.*\.js", url) or \ re.search(r"^\/lib\/.*\.js", url) if runjs: if addr not in valid_user: valid_user.add(addr)
通过上面UserAgent和IP过滤的方法,几乎能排除全部的爬虫访问,但也有漏网之鱼。我在日志中发现,偶尔有来自同一网段的多个IP的访问,而且都请求了JavaScript文件。在 ipip.net 上查询它们的地理位置,发现一般都是国内的机房。要排除这种类型的用户,要用更高级的方法,考虑到投入产出比不高,我就没管,这点误差可以容忍,毕竟连Google Analytics都经常识别不出爬虫。
一般来讲,还要排除特定IP的访问,比如自己的IP。方法和User-Agent的类似,就不多说了。
我将这部分代码命名为 filter.py
,输入为Nginx的日志,输出打印格式化的访问记录。完整代码在 这里
。
数据库
数据库软件我选择最常用的MySQL。首先用如下的 SQL 语句在数据库中创建如下的数据表(Table):
CREATE DATABASE IF NOT EXISTS website; USE website; CREATE TABLE IF NOT EXISTS record ( count INT(32) NOT NULL PRIMARY KEY AUTO_INCREMENT, addr VARCHAR(20) NOT NULL, time DATETIME(0) NOT NULL, page VARCHAR(128) NOT NULL );
插入记录的SQL语句如下:
INSERT INTO record (addr, time, page) VALUES ('1.2.3.4', NOW(), '/about/'), ('5.6.7.8', NOW(), '/series/');
现在要用Python操作数据库,我用的是MySQLdb模块。由于Nginx的时间格式和 MySQL 的默认格式不一样,要做一个格式转换。这一过程的代码文件命名为 mysql.py
,将 filter.py
的输出作为输入,将数据插入到数据库中。代码如下:
!/usr/bin/python3 import sys import datetime import MySQLdb def time_format(t): dt = datetime.datetime.strptime(t, '%d/%b/%Y:%H:%M:%S') return str(dt) def insert_record(db, cursor, addr, time, page): sql = "INSERT INTO record (addr, time, page) \ VALUES ('%s', '%s', '%s')" % (addr, time, page) try: cursor.execute(sql) db.commit() except: db.rollback() def run(): db = MySQLdb.connect('localhost', 'username', 'password', 'dbname', charset = 'utf8') cursor = db.cursor() if len(sys.argv) != 2: print("Usage: ./mysql.py record.log") sys.exit() fin = open(sys.argv[1], 'r') while True: line = fin.readline() if not line: break splits = line.split() addr = splits[0] time = time_format(splits[1]) page = splits[2] insert_record(db, cursor, addr, time, page) db.close(); if __name__ == "__main__": run()
定时任务
最好要让整个过程自动化,步骤依次为:
-
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
,所有脚本文件放在 /root/script/traffic/
目录。代码如下:
#!/usr/bin/python3 import os import datetime log_dir = '/var/log/nginx/' current_dir = '/root/script/traffic/' today = datetime.datetime.now().strftime("%Y%m%d") gzfile = log_dir + 'access.log-' + today + '.gz' # cp here cp_cmd = 'cp ' + gzfile + ' ' + current_dir # unzip nowgzfile = current_dir + 'access.log-' + today + '.gz' unzip_cmd = 'gunzip ' + nowgzfile # filter logfile = current_dir + 'access.log-' + today recordfile = 'record.log-' + today filter_cmd = current_dir + 'filter.py ' + logfile + ' > ' + recordfile # save to mysql sql_cmd = current_dir + 'mysql.py ' + recordfile # rm files rm_cmd = 'rm ' + recordfile + ' ' + logfile os.system(cp_cmd) os.system(unzip_cmd) os.system(filter_cmd) os.system(sql_cmd) os.system(rm_cmd)
在我的服务器上,Nginx每天凌晨04:00左右打包 access.log
文件。我需要在每天凌晨04:00自动运行上面的 cmd.py
脚本,运行 crontab -e
命令添加如下的定时任务:
00 04 * * * /root/script/traffic/cmd.py
这样,网站每天的访问记录就会自动添加到MySQL数据库中了。我用下面的SQL语句查询了一下昨天的访问记录:
SELECT addr, time, page FROM record WHERE time BETWEEN STR_TO_DATE('2019-04-24 00:00:00', '%Y-%m-%d %H:%i:%s') AND STR_TO_DATE('2019-04-24 23:59:59', '%Y-%m-%d %H:%i:%s');
页面访问量和Google Analytics的统计相差不大。
总结
其实本文就是用简单的技术实现了自己的需求。数据库是非常重要的技术,而且实践性强,以前在工作中没机会接触,自己又找不到合适的数据库练手。从今天起,我的网站访问记录不仅会自动保存下来,还给我提供了一份非常不错的数据用来练手,这应该是最大的收获吧。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 日志与日志不一样:五种不能忽略的日志源
- 从简单日志到全链路日志 我们应该怎么打日志
- iOS关于日志模式及日志级别
- Logback 之 MDC 日志跟踪、日志自定义
- Logback 之 MDC 日志跟踪、日志自定义
- 使用 logstash 作为 Docker 日志驱动收集日志
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Information
James Gleick / Vintage / 2012-3-6 / USD 16.95
James Gleick, the author of the best sellers Chaos and Genius, now brings us a work just as astonishing and masterly: a revelatory chronicle and meditation that shows how information has become th......一起来看看 《The Information》 这本书的介绍吧!