nginx geo信息分析

使用场景

在 Nginx 中启用 GeoIP2 模块,记录用户访问日志,并通过 Python 脚本进行二次分析。

模块安装

在 Ubuntu 20+ / 22+ 系统中,GeoIP2 模块可以直接通过 apt 安装:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 装模块(Ubuntu 20+/22+ 自带)
sudo apt install libnginx-mod-http-geoip2

# 下载免费的城市库
sudo mkdir -p /usr/share/GeoIP
sudo wget -O /usr/share/GeoIP/GeoLite2-City.mmdb \
https://git.io/GeoLite2-City.mmdb

# 自动补缺失依赖
sudo apt install -f

sudo dpkg --configure -a

文件配置

  1. 在 nginx.conf 中启用 geo 变量

在 http {} 段中加入如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
http {
...
geoip2 /usr/share/GeoIP/GeoLite2-City.mmdb {
auto_reload 5m;
$geo_country country names en;
$geo_region subdivisions 0 names en;
$geo_city city names en;
}

log_format geo_combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'Country=$geo_country Region=$geo_region City=$geo_city';

access_log /var/log/nginx/access_geo.log geo_combined;
...
}

  1. 在 server 块中单独启用日志
1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 443 ssl http2;
...
....
...
# ⭐ 记录带有地理信息的日志
access_log /var/log/nginx/xxxx_geo.log geo_combined;

location / {
try_files $uri $uri/ =404;
}
}

3.启动与验证

1
2
3
4
5
6
7
8
9
10
# 检查配置是否正确
sudo nginx -t

# 重新加载配置
sudo systemctl reload nginx

# 查看实时日志
tail -f /var/log/nginx/xxxx_geo.log


可能端口已经被占用,可以先暂停端口监听:

1
2
3
sudo lsof -ti:80  | xargs -r sudo kill -9
sudo lsof -ti:443 | xargs -r sudo kill -9

如果输出为空说明端口已释放

日志分析

  • 自定义日志解析脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/usr/bin/env python3
import re, csv, os, glob, gzip
from user_agents import parse
from datetime import datetime, timedelta

LOG_DIR = '/var/log/nginx'
LOG_PAT = 'access_geo.log*' # 支持 *.gz
OUT_DIR = os.path.expanduser('~/nginx_geo_daily')

# 昨天的日期
day_str = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
out_csv = os.path.join(OUT_DIR, f'{day_str}.csv')

# 正则:IP、时间、UA、Country、Region、City
line_re = re.compile(
r'(?P<ip>\d+\.\d+\.\d+\.\d+) .*?\[(?P<dt>[^\]]+)\] '
r'".*?" \d+ \d+ ".*?" "(?P<ua>[^"]+)" '
r'Country=(?P<country>[^ ]+) Region=(?P<region>[^ ]+) City=(?P<city>[^ ]+)'
)

rows = []
for path in glob.glob(os.path.join(LOG_DIR, LOG_PAT)):
opener = gzip.open if path.endswith('.gz') else open
with opener(path, 'rt', encoding='utf-8', errors='ignore') as f:
for line in f:
m = line_re.search(line)
if not m:
continue
ua = parse(m.group('ua'))
rows.append({
'datetime' : m.group('dt'),
'ip' : m.group('ip'),
'device' : ua.device.family,
'os' : ua.os.family,
'browser' : ua.browser.family,
'country' : m.group('country'),
'region' : m.group('region'),
'city' : m.group('city'),
})

# 去重 & 排序
rows = sorted({(r['ip'], r['datetime']): r for r in rows}.values(),
key=lambda r: r['datetime'])

os.makedirs(OUT_DIR, exist_ok=True)
with open(out_csv, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f,
fieldnames=['datetime','ip','device','os','browser','country','region','city'])
writer.writeheader()
writer.writerows(rows)

print(f'{len(rows)} records written to {out_csv}')


安装与运行

1
2
3
4
5
6
7
sudo mkdir -p /usr/local/bin /var/log/nginx/geo_daily
sudo tee /usr/local/bin/nginx_geo_daily.py < nginx_geo_daily.py
sudo chmod +x /usr/local/bin/nginx_geo_daily.py

# 手动执行 在/home/xxx/nginx_geo_daily/ 生成文件
/usr/local/bin/nginx_geo_daily.py

  • 定时任务

每天23:05执行

1
2
3
4
5
crontab -e

5 23 * * * /usr/bin/python3 /usr/local/bin/nginx_geo_daily.py >> ~/nginx_geo_daily/cron.log 2>&1