4. 数据收集与预处理:从日志到特征的“第一公里”
数据,是一切模型的基础。这话说起来简单,做起来……嗯,我见过太多项目死在了“数据”这一关。不是算法不够好,是数据根本没法用。
今天我们就来聊聊异常访问检测里的数据预处理。说白了,就是把原始日志变成模型能理解的特征向量。这一步做扎实了,后面的建模才会顺利。
4.1 访问日志格式:不只是“读一行日志”那么简单
先聊日志。常见的日志格式主要有这么几种,我根据项目经验列了个表:
| 日志类型 | 典型格式/字段 | 常见来源 |
|---|---|---|
| Web 访问日志 | IP、时间戳、方法、URL、状态码、User-Agent、Referer、响应大小 | Nginx、Apache、IIS |
| API 网关日志 | 同上 + 调用方身份、、请求体大小、延迟、返回码 | Kong、AWS API Gateway、Zuul |
| 网络流日志 | 源IP、目的IP、端口、协议、字节数、包数、流持续时间 | NetFlow、sFlow、AWS VPC Flow Logs |
| 安全设备日志 | 时间、事件类型、威胁等级、源/目的IP、攻击签名ID | WAF、IDS/IPS、防火墙 |
我自己处理过最多的是 Web 访问日志。举个例子,Nginx 的默认日志格式长这样:
192.168.1.100 - - [10/Oct/2023:13:55:36 +0800] "GET /api/user/info HTTP/1.1" 200 523 "https://example.com" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
你看,一整行字符串里其实塞了至少10个独立字段。解析的时候要小心,特别是时间戳的格式,各家不太一样。我建议直接用正则表达式来切分,或者用现成的日志解析库,比如 Python 的 pyparsing 或 pandas.read_fwf(),能省不少事。
4.2 特征提取:从原始字段到量化数值
解析好的日志还是一堆文本。模型要的是数字。这一步就是特征提取。
我习惯把特征分为三大类,你想想看:
- 数值型特征:请求大小、响应时间、状态码(可直接映射为int)、包数、持续时长。
- 类别型特征:IP段、HTTP方法、User-Agent类别、页面路径类别、时段(早/中/晚)。
- 时序型特征:每分钟请求数、滑动窗口内的异常次数、距离上次登录的时间差、短期频率统计。
举个具体的例子。对于“请求时间”这个字段,我不会直接丢一个时间戳进去。我会分解成:
hour_of_day(0-23)is_work_hours(0/1)day_of_week(0-6)request_freq_last_5_min(过去5分钟的请求总数)
这些衍生特征,往往比原始日志本身更有辨识度。我记得有一次做金融系统的防爬虫检测,把请求频率按IP按秒窗口统计后,异常模式一下子就清晰了。
4.3 缺失值处理与异常值清洗
现实世界里,日志丢失字段是很常见的。比如User-Agent偶尔为空,Referer字段缺失。怎么办?我的原则是:
- 能不用就不用:如果某个字段缺失率超过30%,我通常会直接丢弃它。
- 填充法:对于数值型,用中位数或平均数填充。对于类别型,用“unknown”占位。
- 预测填充:如果特征重要且缺失率低(比如5%以内),我会用KNN或简单的回归模型给它补上。
异常值,比如同一个IP每秒请求数突然冲到10万次,很明显是DDOS攻击或日志采集事故。我一般会用Z-score或IQR法把它们标记出来,而不是直接删除。毕竟,异常值本身就是我们要检测的目标,你想想看。
4.4 标准化与特征工程:让数据“对齐”
这一步是为了让不同量纲的特征可以公平地参与模型计算。我习惯的操作流程是这样的:
- 标准化(StandardScaler):将数值特征调整为均值为0、方差为1。适用于基于距离的模型(SVM、KNN、逻辑回归)和深度学习。你看我代码:
from sklearn.preprocessing import StandardScaler
import numpy as np
X = np.array([[100, 0.001], [200, 0.002], [150, 0.0015]])
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# X_scaled 现在每列的均值为0,标准差为1
- 归一化(MinMaxScaler):压缩到[0,1]区间。适用于数值范围差异极大的情况,比如请求大小(几KB到几GB)和响应时间(毫秒到秒)。注意,如果有异常值,归一化可能会被带偏。
- 类别变量编码:对于方法(GET/POST/PUT),我用独热编码;对于IP地址,我会提取网段、AS号、GeoIP信息作为数值特征,很少直接丢一个IP字符串进去。
最后,给你看一张图,把这一整套流程串起来:
实际上,这个流程在异常检测项目里会来回跑好几遍。找到有区分度的特征,预处理阶段往往要占掉项目一半时间。别嫌麻烦,这一步值得。