二、接口数据采集:从日志到可分析的数据

说实话,接口行为基线这事儿,门槛不在算法,也不在模型。

门槛在哪?在数据。我见过太多团队,算法选得漂亮,模型调得飞起,结果一上生产,全是垃圾进垃圾出。为什么?数据采集那一步就埋了雷。

所以这一章,咱们把数据采集这件事聊透。你想想看,你的检测系统再聪明,如果喂进去的日志是残缺的、错位的、甚至干脆丢了,那后面的基线建立和异常检测,全白搭。

2.1 HTTP请求/响应日志:最原始的金矿

每个接口调用,都会产生一对请求和响应。这是最原始的数据源。我个人习惯,先搞清楚日志里到底有什么。

2.1.1 请求端该抓什么

  • 请求方法:GET、POST、PUT、DELETE,别小看这个字段,它决定了行为的语义。
  • 请求路径:/api/v1/orders,这就定义了操作对象。
  • 请求头:Content-Type、Authorization、User-Agent,这里藏着调用方的身份和意图。
  • 请求参数:Query String + Request Body,这是真正的载荷。
  • 时间戳:请求发起时间,精度要至少到毫秒。
我的经验:曾经有一个项目,用户一直报接口慢,我们查了三天,最后发现是日志里的时间戳只精确到秒。同一秒内的几十个请求,你根本分不清先后顺序,怎么定位延迟?从那以后,我强制要求所有日志时间戳必须带毫秒。

2.1.2 响应端该抓什么

  • HTTP状态码:200、400、500,这是行为健康的直接信号。
  • 响应体大小:单位字节,太大或者太小,都可能是异常信号。
  • 响应时间:从请求到达服务端到响应发出所耗时长。这就是我们常说的延迟。
  • 响应头:比如 Content-Encoding、Set-Cookie,有时能发现配置问题。

你可能会问:响应体内容要不要抓?我建议:不要全量存储。为什么?一是存储成本太可怕,二是涉及用户隐私和敏感数据。我在项目中遇到过,有人把整个支付接口的响应体全部落盘,结果等保审计直接亮了红灯。

2.2 API网关日志:统一的流量入口

如果你的系统接了API网关——无论是Kong、Nginx Plus、还是云上的API Gateway——恭喜你,捡到宝了。

API网关是所有请求的必经之路。在这里采集日志,天然具有全局视角。而且很多网关会帮你做一件事:请求重组的标准化。比如,网关会把原始请求IP、设备指纹、用户ID这些字段,统一注入到日志里。这对于后续建立用户级别的行为基线,太重要了。

2.2.1 网关日志 vs 应用日志,有什么区别?

维度 API网关日志 应用服务日志
覆盖范围 全量流量,包括被限流、鉴权失败的请求 只有到达服务的请求
延迟影响 几乎无侵入,网关转发就好 可能影响应用性能,尤其高并发时
字段丰富度 头部和元数据丰富,但缺业务细节 业务字段完整,团队可控
标准化程度 高,统一格式 低,不同服务可能格式各异
避坑指南:我曾经遇到一个客户,只依赖应用日志做异常检测,结果被DDoS攻击时,大量请求在网关就被拦截了,根本没有进入应用。他们的检测系统全程安静如鸡。所以,网关日志一定要接入,它能看到应用看不到的“门口风景”。

2.3 数据清洗与预处理:从混乱到有序

日志采回来了,但你能直接用吗?不能。原始日志长什么样?嗯,格式乱、有空值、有脏数据、有重复。不做清洗,你后面的分析就是自欺欺人。

2.3.1 常见的“脏数据”有哪些

  • 缺失值:比如响应时间字段忽然是空,可能因为超时没记录。
  • 异常值:响应时间忽然显示 -1 或者 999999,明显是错误占位符。
  • 重复记录:因为重试机制,同一条请求被记录了多次。
  • 格式不统一:有的路径带尾部斜杠 /api/v1/orders/,有的不带,直接拼到一起分析就出问题。
  • 语义不合法:比如状态码填了 999,或者请求体里的 JSON 格式是坏的。

2.3.2 清洗的标准流程(我自己的套路)

  1. 字段提取与重命名:把不同来源的字段统一命名,比如“resp_time”、“response_time”、“rt”都统一成“response_time_ms”。
  2. 格式校验:把时间戳转成统一格式 ISO 8601,把 JSON 体做一次解析校验。
  3. 异常值过滤:响应时间 <= 0 的直接标记为无效。
  4. 去重:基于 (时间戳, 请求ID, 用户ID) 的联合键做去重。
  5. 缺失值处理:关键字段比如状态码、响应时间如果为空,建议直接丢弃这一条。不关键的字段,比如某些自定义头,可以填空字符串。

下面是一段我常用的Python清洗示例,你可以参考一下:

import pandas as pd
import json

# 假设原始日志是DataFrame
df = pd.read_csv('raw_api_logs.csv')

# 1. 统一字段命名
df.rename(columns={
    'resp_time': 'response_time_ms',
    'method': 'http_method'
}, inplace=True)

# 2. 格式校验:时间戳转datetime
df['timestamp'] = pd.to_datetime(df['timestamp'], errors='coerce')

# 3. 异常值过滤
df = df[df['response_time_ms'] > 0]

# 4. 去重
df.drop_duplicates(subset=['timestamp', 'request_id', 'user_id'], inplace=True)

# 5. 缺失值处理:状态码为空则丢弃
df.dropna(subset=['status_code'], inplace=True)

print(f"清洗后剩余记录数: {len(df)}")
关键提醒:清洗不是一味地删数据。每删一条,你都要清楚为什么删。我建议在清洗阶段保留一个“脏数据日志”文件,把被删除的记录和删除原因都记下来。这不只是为了追溯,更是为了日后调整清洗策略。你想想看,如果某天忽然发现大量请求都被你删了,你总要知道发生了什么吧?
API数据采集与清洗流程 HTTP请求/响应日志 API网关日志 其他日志来源 统一日志采集层(Filebeat / Fluentd) 数据清洗与预处理 字段归一化 → 格式校验 → 异常值过滤 → 去重 → 缺失值处理 输出:结构化、一致、可用的“干净”数据集 可用于基线建立的干净数据

2.4 一些关于字段设计的思考

数据清洗完,你会发现一个现实:不同来源的数据,字段结构往往不一样。你做异常检测时,总不能一个模型吃两种格式吧?所以,在清洗阶段就定义一套统一的数据模型,非常重要。

我个人推荐的最小字段集,供你参考:

字段名 类型 含义 来源
timestamp datetime 请求时间 统一来自网关或服务
request_id string 唯一请求标识 网关生成
uri string 归一化的路径(不含参数) 清洗时处理
http_method string GET/POST等 原始日志
status_code int HTTP状态码 原始日志
response_time_ms float 响应耗时(毫秒) 原始日志或计算
user_id string 调用方用户标识 从解析
client_ip string 客户端IP 网关日志
request_body_size int 请求体字节数 原始日志
说个细节:“uri”这个字段,记得做归一化。比如 /api/v1/orders/12345 和 /api/v1/orders/67890,它们本质上是同一个端点,只是参数不同。如果不归一化,你的基线会认为这是两个不同的行为,那分析就没意义了。我一般用正则把路径中的数字ID替换成占位符 :id。

好了,数据采集和预处理这块,我该讲的都讲透了。数据到手了,也洗干净了,接下来呢?嗯,后面的章节,我们会把这些数据喂给模型,开始建立真正的行为基线。但那是后话了,先把眼前这一步做扎实。