4. 审计日志数据预处理:数据清洗、缺失值处理、时间戳标准化、去重
好,咱们进入第4章。拿到原始审计日志后,别急着往模型里灌。你想想看,真实环境里的日志,那叫一个“脏”。我个人习惯,先把预处理这步做扎实,后面建模才能省心。这一步做不好,模型看到的全是噪音,再牛的算法也白搭。
说白了,预处理就是给日志数据“洗澡”。把脏东西洗掉,把缺的补上,把时间格式统一,把重复的扔掉。做完这四步,数据才能用。
4.1 数据清洗 —— 去掉“垃圾”
数据清洗,我第一个要处理的就是编码问题。我在项目中遇到过,某次从遗留系统导出的日志,里面夹杂着乱码字符,导致解析脚本直接崩溃。为什么会这样?因为不同系统默认编码不一致,最常见的就是 UTF-8 和 GBK 混用。
所以,第一步是统一编码。我通常强制转为 UTF-8,并忽略掉无法识别的字符。另外,还要清理空格、制表符、换行符,以及那些肉眼看不见的控制字符。
常见清洗操作清单
- 编码统一: 将日志字符串统一为 UTF-8 编码,处理编解码错误(如 Python 的
errors='ignore')。 - 特殊字符去除: 移除不可见控制字符(ASCII 0-31)、异常空格(全角、非断行空格)。
- 字段格式校准: 比如 IP 地址、端口号、用户名的格式必须符合预期,去掉前后引号或多余的括号。
4.2 缺失值处理 —— 补还是不补?
缺失值,是审计日志里的常客。你可能会问:为什么日志里会有缺失值?
- 系统部分字段采集失败(比如目标端口字段没打出来)。
- 不同设备记录的字段集不同(防火墙和操作系统日志的字段天然不一样)。
- 日志在传输过程中被截断。
处理缺失值,我的原则是:能不补就不补,能丢弃就丢弃。特别是对于异常检测,你觉得强行填充一个“正常值”进去,会不会让模型误以为那是正常行为?
| 处理策略 | 适用场景 | 我在项目中的经验 |
|---|---|---|
| 直接丢弃 | 缺失字段占比小于5%,且不影响关联分析 | 最常用,省事。但如果缺失太严重,会损失样本量。 |
| 填充默认值 | 分类字段(如用户角色缺失,填"unknown") | 注意,默认值本身可能会成为异常模式。我见过所有“unknown”被模型判定为异常。 |
| 前向后向填充 | 时间序列字段(如CPU使用率短暂缺失) | 对审计日志来说,这个要慎用。因为前后行为可能完全不同,填充会掩盖突变。 |
| 模型预测填充 | 连续数值字段,且缺失率在20%以内 | 我基本不用。这相当于引入了一个小模型,它自己的误差会传染给主模型。 |
4.3 时间戳标准化 —— 统一“语言”
时间戳,是审计日志的灵魂。没了它,你怎么做时序分析?但现实是,各种系统的时间格式千奇百怪。
- 有的用 UNIX 时间戳(秒或毫秒)。
- 有的用 ISO 8601 标准(
2023-08-15T14:30:00Z)。 - 有的用自定义格式(
08/15/2023 02:30:00 PM)。 - 最头疼的是:时区不一致。
我建议,统一转为 UTC 时间的 ISO 8601 格式,精确到毫秒。为什么是 UTC?因为当你处理跨地域、跨时区的日志时,用 UTC 可以避免所有时区转换的歧义。嗯,这里要注意,转换时一定要指定时区,不然 Python 的 pandas.to_datetime() 会把“2020-01-01 00:00:00”当成你本机时区的 00:00,那就出大问题了。
标准化流程
- 识别原始格式: 用正则或启发式规则判断是 UNIX 时间戳还是字符串时间。
- 统一转为 datetime 对象: 使用
pandas.to_datetime()并传入utc=True参数。 - 格式输出: 全部输出为
%Y-%m-%dT%H:%M:%S.%fZ。
# 我个人常用的标准化代码片段
import pandas as pd
def standardize_timestamp(ts: str) -> pd.Timestamp:
try:
# 尝试 UNIX 时间戳(秒)
return pd.to_datetime(int(ts), unit='s', utc=True)
except (ValueError, TypeError):
try:
# 尝试 ISO 格式并自动处理时区
parsed = pd.to_datetime(ts, utc=True)
# 确保没有 NaT
if pd.isna(parsed):
raise ValueError
return parsed
except:
# 实在不行,返回 NaT 后续丢弃
return pd.NaT
# 对整列应用
df['timestamp'] = df['timestamp'].apply(standardize_timestamp)
# 丢弃无法解析的时间戳
df = df.dropna(subset=['timestamp'])
4.4 去重 —— 干掉“复印件”
日志重复,比你想象中要常见。网络重传、日志采集器重复读取、应用 bug 都可能导致同一条日志出现多次。对于异常检测来说,重复样本会让模型错误地“加强”对正常模式的记忆,甚至把一些本不频繁的异常行为因为重复而变成“看似正常”。
我一般分两步走:
- 全字段去重: 所有字段完全一样,直接删掉,只保留第一条。这是最稳妥的。
- 部分字段去重(滑动窗口): 有些情况是“准重复”。比如用户 A 在同一秒内连续失败 5 次登录,只有毫秒不同。这到底是重复还是真正的爆破?我会用滑动窗口,记录“时间+源IP+事件类型”,设置一个 5 秒的窗口,如果窗口内出现 3 条以上非常相似(只差毫秒或序号),我就只保留第一条,但会新增一个计数特征
repeat_count。
4.5 本章知识图谱
下面这张图,是我对审计日志数据预处理的整体框架总结。你可以把它当一个操作路径图来看。
你看,整个流程是串行的。前一步的结果直接影响后一步。特别是缺失值处理和去重,如果你先去了重,可能会把一些带缺失字段但唯一的时间点样本删掉,导致后续时间分析有漏洞。所以,顺序很重要。