应使用 re 提取 ip 和日期,datetime.strptime 解析日期,defaultdict(set) 统计每日独立 ip 数;处理 .gz 文件需用 gzip.open;过滤 ip 为 "-" 或空白;捕获异常并记录警告。

用 re 和 datetime 提取日志中的日期与IP
Nginx 默认日志格式中,时间戳在方括号内(如 [10/Jan/2024:08:32:15 +0800]),IP 在行首。直接按空格切分不可靠,因为请求路径、UA 字段含空格。必须用正则提取关键字段:
-
^(\S+)捕获 IP(开头非空格字符序列) -
\[(\d{2}/\w{3}/\d{4}):捕获日期部分(忽略具体时分秒)
注意:月份是英文缩写(Jan、Feb),需用 datetime.strptime(..., "%d/%b/%Y") 解析,%b 才能正确识别;用 %B 会失败。
用 collections.defaultdict(set) 去重统计每日IP
独立访客数 = 每日出现的不重复 IP 总数。不能用 list 累加再 set(),内存爆炸;也不能对每天做全量 set 再求长度——日志量大时频繁创建大集合效率低。
- 推荐:
defaultdict(set),键为date(datetime.date对象),值为该日所有 IP 的集合 - 每读一行,执行
ip_by_date[date].add(ip),自动去重 - 最后用
{date: len(ips) for date, ips in ip_by_date.items()}得到结果
不用 Counter ——它统计频次,不是去重;也不建议用 pandas 读全量日志再 groupby,小脚本没必要引入重量依赖。
处理多文件、压缩日志(.gz)时别硬编码打开方式
生产环境常保留 access.log.1.gz、access.log.2.gz 等压缩归档。如果只用 open(),遇到 .gz 直接报错 UnicodeDecodeError 或乱码。
- 先用
pathlib.Path(file).suffix判断后缀 -
.gz→ 用gzip.open(file, "rt", encoding="utf-8") - 其他 → 用
open(file, "r", encoding="utf-8") - 避免用
file.endswith(".gz"),万一有.log.gz.bak就误判
另外,Nginx 日志默认编码是 UTF-8,但某些系统或自定义配置可能用 latin-1;若遇到解码错误,捕获 UnicodeDecodeError 后 fallback 到 errors="ignore" 更稳妥,而不是直接崩溃。
真实日志里 - 和空 IP 需过滤
有些代理或异常请求会导致 IP 字段为 -(连字符),或为空白。这类记录不应计入独立访客:
- 正则匹配后检查
if ip != "-" and ip.strip() - 别只写
if ip——字符串"-"是真值,会被误留 - 也别跳过整行:时间字段仍有效,可用于观察异常流量时间分布
还有少数日志行可能因截断、编码损坏而无法匹配正则,用 try/except 包裹解析逻辑,记录行号和原始内容到 warning 日志,比静默丢弃更利于排障。
真正难的不是算数,而是日志格式漂移——比如运维某天改了 log_format,加了 $http_x_forwarded_for,IP 就不再是第一个字段。上线前务必用 head -20 access.log 实际抽样验证正则是否还 match。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











