应使用行首锚定的ipv4正则^((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\s),配合空行与注释行过滤、内网ip剔除及逐行迭代处理,避免误匹配与内存溢出。

怎么用正则从Nginx日志里准确提取IP地址
直接用 ^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) 这种简单模式会匹配失败或误匹配,比如日志里可能有内网地址、IPv6、代理头(X-Forwarded-For)或注释行。Nginx默认日志格式中真实客户端IP通常在第一列,但必须排除空行、注释行和非法格式。
推荐正则:^((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\s
- 开头
^锚定行首,避免匹配到日志体内的IP(如Referer里的URL) - 用非捕获组
(?:...)和重复三次确保是标准IPv4四段结构 - 结尾加
\s确保后面紧跟空格,排除类似192.168.1.1000这种超长段误匹配 - 实际读取时建议先用
line.strip()去掉换行和空格,再判断line and not line.startswith("#")
为什么不能直接用 set(line.split()[0]) 提取IP
因为Nginx日志字段间是空格分隔,但某些UA字符串含空格,导致 split() 后索引错位;更常见的是日志启用了 $http_x_forwarded_for 或 $real_ip_remote_addr,此时第一字段可能是代理IP而非真实客户端IP。
正确做法是:先按正则搜索,只取第一个成功匹配的IP,且限定最多匹配一次(避免一行多个IP干扰):
import re
ip_pattern = re.compile(r'^((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\s')
ips = set()
with open('/var/log/nginx/access.log') as f:
for line in f:
if not line.strip() or line.startswith('#'):
continue
m = ip_pattern.match(line)
if m:
ips.add(m.group(1))
处理大日志文件时内存爆掉怎么办
直接 readlines() 或把全部IP塞进一个 set 会导致GB级日志吃光内存,尤其当IP量极大(千万级)时,set 的哈希表开销明显。
- 用逐行迭代 +
match()(不是search()),避免构建中间列表 - 如果只需独立IP数(不要具体IP列表),可改用
collections.Counter配合磁盘暂存,或用Bloom Filter近似去重(精度可调) - 更稳妥的折中:每读取10万行做一次
gc.collect(),并用sys.getsizeof(ips)监控集合大小 - 注意:Python 3.7+ 的
set内存效率已优化,但单个IP字符串仍占约50字节,千万IP ≈ 500MB,别在1G内存机器上硬扛
真实场景下哪些IP该过滤掉
统计“独立IP数”若用于访问量分析,不剔除以下IP会导致结果严重失真:
- 内网地址:
127.0.0.1、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、100.64.0.0/10(CGNAT) - 监控/健康检查流量:固定UA(如
curl/7.68.0)、高频短间隔请求(需结合时间戳,单靠IP无法识别) - CDN回源IP:如阿里云WAF回源地址段(
100.100.0.0/16),这类IP不代表真实用户 - 建议预定义过滤函数:
def is_valid_ip(ip): return not ip.startswith(("127.", "10.", "172.16.", "172.17.", "172.18.", "172.19.", "172.20.", "172.21.", "172.22.", "172.23.", "172.24.", "172.25.", "172.26.", "172.27.", "172.28.", "172.29.", "172.30.", "172.31.", "192.168.", "100.64."))
IPv6支持要单独处理,ipaddress.ip_address() 虽可靠但性能差,高吞吐场景建议跳过或用预编译正则。真正难的不是提取,是定义清楚——你到底想数“谁”,而不是“什么长得像IP”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











