去年 8 月,我为了搞清楚家里小孩半夜抱着平板不睡觉,搭了一套 AI 上网行为分析系统(见《为搞清楚凌晨上网都在干啥,我建了一套AI上网行为分析系统)。那套系统的逻辑很简单:采集路由器日志 → 喂给大模型 → 定时发一封"上网行为分析邮件"。

它能回答一个问题:这两个小时里,大家都在网上干了啥?

但用久了,我发现它回答不了更要命的问题——

192.168.0.25 这台手机,到底是谁在用?他平时是个什么样的上网习惯? 我那台开发机(.8)和家里的 NAS(.17),行为有什么不一样? 哪个设备最近突然半夜开始活跃?哪个被人从外面反复尝试登录?

换句话说,旧系统给我的是一份份孤立的"时段流水账",而我真正想要的,是对"一个人/一台设备"的长期、立体、可对比的画像

于是我在自研的 AI-miniSOC(一个轻量级安全运营中心 https://github.com/xiejava1018/AI-miniSOC)里,把这件事做成了一个真正落地的功能模块——行为画像。这篇文章不讲空话,我会以问题为导向,把它的架构设计、实现原理、踩过的坑、应用场景和实际成效讲透。

先放一张整体架构图,后面逐层拆开讲:

行为画像整体架构(采集→计算→存储→接口→展示 五层 + 双数据管道)

一、先想清楚:到底要"画"谁?

动手前我做的第一件事不是写代码,而是盘数据。这一盘点,发现一个术语陷阱

在我的数据里,IP ≠ 用户。而且三个数据源里的"IP",含义根本不一样。

举个真实例子——我(xiejava)从开发机 192.168.0.8 SSH 登录到服务器 192.168.0.102,这一个动作在三个系统里记的是:

数据源记录的 IP它的含义有用户名吗
Loki 路由器日志192.168.0.8行为发起方(谁在上网)
PG 告警聚合表192.168.0.102被监控的主机(哪台机器出事)
OpenSearch 原始告警192.168.0.8真正的操作者✅ 有(Accepted publickey for xiejava from 192.168.0.8

这意味着"给某个 IP 做画像"其实有三种完全不同的主体:

  • A. 设备画像——“这台设备是什么、安不安全、在干什么”(数据现成,当天能做)
  • B. 用户画像——“这个人平时怎么上网、有没有异常”(需要从 OpenSearch 日志里把用户名抽出来)
  • C. 实体画像——账号 + 设备 + 行为的关联体(需要建身份基线)

我的决策是:先做 A(设备画像)打底,用 A 的数据管道顺带产出 B 需要的"账号↔IP"身份映射,再演进到 C。 这也是为什么这个模块叫"行为画像"而不是"用户画像"——它当前的主体是设备/IP,但已经为"升维到人"铺好了路。

二、架构设计:五层 + 两条数据管道

行为画像不是另起炉灶的新系统,而是作为"上网行为"模块的子能力挂在 AI-miniSOC 里。整体分五层,里面跑着两条数据管道

行为画像的两条数据管道:上网行为管道(在干什么)+ 身份关系管道(是谁/和谁),最终汇合落库→接口→画像页

  • 管道一·上网行为:从 Loki 拉路由器行为日志,经过分类、聚合、打标签,产出"什么时候活跃、活跃什么、访问哪些域名"。
  • 管道二·身份关系:从 OpenSearch 的认证类告警(SSH 登录成功/失败、PAM 会话、暴力破解)里,用正则把"哪个账号、从哪个 IP、登录了哪台机器、成功还是失败“抽出来,产出"这台设备被谁用过、它又登录了谁”。

两条管道的结果都由每日定时任务(snapshot_job,凌晨 2 点跑行为、2 点半跑身份)落成快照,存进 PostgreSQL,再通过 REST API / MCP 工具提供给前端。

为什么要"快照落库"而不是实时算? 两个硬约束:① Loki 日志只保留 7 天,想看出"月度节律"必须把聚合结果持久化;② 单 IP 7 天原始日志可能有 10 万+ 行,实时算一次要递归拉取几十个 Loki 请求,远超 30 秒。所以架构上是**“每日快照为主(查询 ≤1 秒),实时计算只用于当天 24 小时下钻”**。

三、实现原理:原始日志如何变成一张画像

这是最硬核的部分。一条路由器日志长这样:

Sep 07 23:48:12 ... 网址:api.copilot.tencent.com ...

它要变成画像上"AI 重度用户 / 夜猫子"这样的标签,中间要过六道工序:

行为画像数据加工流水线与关键工程约束

1)递归自适应分块:绕开 Loki 的 1 万行上限

Loki 单次查询最多返回 1 万行。一个活跃设备一天的日志可能远超这个数。我的做法是 pull_window先拉整个时间窗,如果撞了 1 万行上限,就把窗口从中间劈成两半递归重拉,直到每个子窗口都拉全为止。劈到最小 15 分钟窗口还饱和的,计入"截断窗口数",作为画像置信度降级的依据——宁可告诉用户"这天数据可能不全",也不偷偷给个偏低的数字。

2)三层分类:别把"机器心跳"当成"人的兴趣"

这是整个画像最关键的设计。域名不能一股脑都算成人的行为,否则会闹笑话。我把 14 个类别分成三层:

含义典型域名进画像吗
ACT 主动行为人主动发起的抖音、B站、GitHub、Copilot、淘宝、微信✅ 计入兴趣
SYS 系统背景系统/协议心跳NTP 对时、DNS、系统更新、STUN 打洞、DDNS、Tailscale❌ 不计入
AD 广告追踪SDK 上报埋点doubleclick、友盟 umeng、countly、analytics❌ 不计入

兴趣占比(cat_share)只统计 ACT 层——这一条规则,直接决定了画像说的是"人话"而不是"机器话"。分类用关键词子串匹配,带优先级(避免 apple.com 这种大域名吃掉 weatherkit.apple.com),而且词典支持运营在后台配置覆盖,不用改代码。

3)聚合:把日志堆成"节律"和"兴趣"

aggregate_day 对单日事件做统计,产出画像的核心分布:

  • 24 小时活跃曲线 by_hour[0..23]
  • 星期 × 小时热力矩阵 wd_hour[7][24]——一眼看出"周几几点最活跃"
  • 7 时段占比 by_block:深夜(00-06)/早晨/上午/午间/下午/傍晚/夜间
  • 分类 × 时段堆叠:回答"半夜到底在刷什么"
  • 工作日 vs 周末 占比、兴趣分类占比、域名 TOP 20

标签和兴趣按滚动 7 天窗口merge_days)计算,避免单日波动造成误判。

4)标签引擎:把数据翻译成"人设"

光有图表还不够,用户第一眼想知道的是"这人是谁"。tagger 用一组可解释的规则打标签,每条标签都附带证据(evidence)

1
2
3
4
# 深夜 00-06 点占比 ≥ 20% → 夜猫子
if s["night_share"] >= 20:
    add("夜猫子", "深夜 00–06 点仍高度活跃", evidence=f"深夜占比 {s['night_share']}%")
# 影音娱乐 ≥ 25% → 追剧党;AI 工具 ≥ 12% → AI 重度用户 …

再通过一层 PERSONA_MAP 把规则名映射成更口语化的人设名(夜猫子→野猫子、周末战士→工作狂、追剧党→追剧狂……)。规则和命名解耦,运营可以单独调。

5)机器流量判定:把 NAS、下载机"折叠"掉

这是 POC 阶段踩出来的 P0 级坑(下面细讲)。compute_traffic_type主判据 + 辅助判据识别非人类流量:

  • 主判据:SYS 系统层占比 ≥ 50% → machine
  • 辅助判据:SYS ≥ 30% TOP3 域名集中度 ≥ 50% 24 小时曲线变异系数 ≤ 0.15(平直)→ machine
  • 中间状态 → mixed;样本少于 50 条不判定,当人对待(交给置信度兜底)

被判为机器流量的主体,在列表和画像里会自动降权/折叠,不再用"作息规律"这种词去描述一台 7×24 小时跑 STUN 的 NAS。

6)异常判定:画像的价值在"So What"

前四层都在"描述",最后一层才给"结论"。get_anomalies 对比自身历史基线输出异常信号(需要积累 ≥4 天快照才点亮):

  • 访问量激增:最新日 ≥ 基线均值 5 倍
  • 节律突变:凌晨占比飙到 40% 而基线不到 20%
  • 失败登录激增 / 外部攻击源 / 权限扩散(来自身份管道)
  • 风险叠加:高危漏洞 + 公网暴露端口 + 活跃告警

注意设计原则:画像只输出"信号",绝不输出"定性"。每条信号都能展开看证据,并提供「生成安全事件」「加白名单」按钮,把画像接回 AI-miniSOC 的事件处置闭环——而不是看完就忘的看板。

AI 解读:除了规则标签,我还接了 GLM 大模型,把五层数据压缩成一段自然语言摘要 + 异常解读 + 处置建议。关键是做了诚实降级——没配 API Key、触发预算熔断或调用失败时,自动回退到规则模板,并在开头声明"数据可信度降级",Prompt 里硬性禁止模型编造日期、禁止"定性到人"。

四、那些差点让我翻车的坑(最值得看的部分)

画像类功能最大的风险不是"做不出来",而是"做出来一本正经地胡说八道,用户看一次就再也不看"。这几个坑每个都让我惊出冷汗:

坑 ① 差 1 小时,“夜猫子"变"早起鸟”

POC 时我图省事用了 Loki 的 count_over_time 聚合函数(让 Loki 按小时帮我数)。结果发现真实 20 点的活跃被标到了 21 点,窗口边界还会重复计数、凭空冒出 11 点的尖峰。行为画像对时段极度敏感——错 1 个小时,就足以把一个熬夜的人判成早起的人

解法:彻底放弃聚合下推,一律拉原始日志、按日志自带的时间戳逐条计数。这条作为硬约束写死在代码注释里。

坑 ② NAS 被当成"作息极规律的人"

第一版画像里,家里的 NAS(.17)拿到了"作息规律"的好评——它 24 小时曲线平直得像教科书。但那根本不是人,是 STUN/P2P/DDNS 的机器心跳!根因是这些组网打洞域名被错误归进了"主动行为层",导致系统层占比只有 8.9%,永远判不出机器。

解法:① 给分类词典补上 stun/tailscale/zerotier/ddns/synology 等机器心跳关键词;② 机器判定从"单一 SYS 占比"升级为"SYS 占比 + 域名集中度 + 曲线平直度"三重判据。修复后 NAS 正确判为 machine 并折叠。

坑 ③ 手机换个 IP,画像"裂成两个人"

家里是 DHCP 动态分配 IP,手机今天 .25、明天可能 .88。如果画像主体用 IP 做主键,设备一换 IP,历史节律基线就全断了——系统以为来了个"新用户"。

解法:快照唯一键改成 (asset_id, profile_date),IP 只作展示字段;没纳管的设备用 MAC/主机名辅助归并(MAC 不会变)。身份卡会展示该主体的全部历史 IP。

坑 ④ “静默的 0”——最危险的假绿

后端宕机或重启错过了一天的定时快照,等 Loki 7 天窗口一过,那天的数据就永久丢失了。趋势图上这一天会显示为 0——但 0 看起来像"这天没上网"(绿色/正常),实际是"数据丢了"(异常)。这种"假绿"在安全系统里是致命的。

解法:引入水位表(watermark)。快照任务启动时对比"最后完成日",自动回溯补齐 7 天窗口内的缺口;实在补不回的日子,落一行 status='gap' 的占位快照,前端渲染成灰色"数据缺失"块而不是 0 柱子。数据缺失,必须显式声明。

五、应用场景:画像到底能怎么用?

落地后,行为画像在 AI-miniSOC 里形成了**“群体 → 个体 → 关系 → 处置”**的完整动线:

场景 1:L1 群体概览——一眼看清全网

进入「行为分析 → 行为画像」,首先是群体画像概览页

🔧 截图说明:实际截图——群体画像概览,含 KPI 卡 + 人设分布条形图 + 主体列表的首屏。

它能回答:“我家/我司全网现在有多少台设备在活跃?几台是人、几台是机器?大家整体是熬夜多还是早起多?“主体列表支持按流量类型、人设标签、置信度筛选,点任意一行就钻取到单 IP 详情。

场景 2:L2 单 IP 详情——看清"一个人”

点进某个 IP(比如那台 Redmi 手机 .25),是左栏身份档案卡 + 右栏四个 Tab 的详情页:

  • 行为画像 Tab:画像标签置顶 → 24h 曲线 + 时段分布 → 星期×小时热力图 → 各时段在干什么(分类×时段)→ 域名 TOP20。
  • 风险画像 Tab:告警量/等级、告警规则榜(含 AI 去噪后真实条数)、漏洞严重度分布、暴露端口(22/5901 等高风险端口高亮)、风险评分趋势。
  • 关系画像 Tab:ECharts 力导向关系拓扑——绿线=它主动登录了谁、蓝线=谁登录了它、红线=外部攻击源;点任一节点可跳到对方画像。
  • 异常判定 Tab:异常信号清单 + 顶部红/黄横幅。

📸 【截图】 L2 单 IP 画像详情·行为画像 Tab:顶部画像标签卡(如"追剧狂 / 夜间活跃型 / 兴趣广泛”),中部 24h 活跃柱状图 + 星期×小时热力图(ECharts),下部访问域名 TOP 表格。

🔧 【截图】: 清楚的展示访问习惯构成、各时段在干什么分类 × 时段堆叠 —— 一眼看"半夜在刷什么"、访问域名 TOP 20。 📸 【截图】 L2 关系画像 Tab 的网络拓扑图:中心节点为当前 IP,绿色出边(主动登录)、蓝色入边(被登录)、红色外部攻击源,边粗细=交互频次,悬停显示"账号 ×次数"。

🔧 截图说明:截取 .102 服务器的关系画像 Tab(它被登录次数最多,入边最丰富,能看到 xiejava ×556 这类账号归一化标签)。

截图 风险画像 Tab:告警量/等级、告警规则榜(含 AI 去噪后真实条数)、漏洞严重度分布、暴露端口(22/5901 等高风险端口高亮)、风险评分趋势。

场景 3:异常发现与处置闭环

当某台设备命中异常(比如平时规律的设备突然凌晨活跃激增),详情页顶部会弹出红/黄横幅。安全人员点开看证据,确认后可直接**「生成安全事件」转入 AI-miniSOC 的事件响应流程,或「加白名单」**收敛误报。画像不再是"看了就忘"的报表。

场景 4:三级入口联动 + 自然语言查询

为了让画像"用得起来",我做了三个跳转入口:资产列表点 IP、告警详情点 agent_ip、上网行为基线点 IP,都能直达该 IP 的画像详情。此外还暴露了 MCP 工具,在 AI 助手里直接问:

“192.168.0.102 最近有什么异常行为?”

就能拿到它的画像摘要——这是从"人找数据"到"数据找人"的一步。

场景 5:导出与双 IP 对比

支持一键导出自包含 HTML 画像报告(带"仅用于安全审计"水印),用于存档/汇报;还能做双 IP 画像对比(时段分布 + 兴趣构成的余弦相似度),回答"这两台设备的行为模式像不像同一个人"。

六、实际成效

POC 阶段我对 8 个典型 IP 跑了全量画像,几个真实结论很能说明问题:

设备画像结论标签
.100 AI 工作站copilot.tencent.com 占主动行为 26.9%,AI+开发占 70%AI 重度用户 / 码农 / 周末战士
.25 Redmi 手机影音娱乐 64.8%(抖音为主)+ 小说 10.1%,访问 2199 个域名追剧狂 / 夜间活跃型 / 兴趣广泛
.8 开发机榜单被网易邮箱心跳占满,但真信号是 copilot/vscode/github码农(需扣除系统背景噪声)
.17 NASSTUN + 迅雷占 70%,24h 曲线平直机器流量(自动折叠,不做人类画像)

落地为平台功能后:

  • 从"时段流水账"升级为"立体画像":74 个画像主体自动每日快照,群体/个体/关系/异常四个层次一目了然;
  • 机器与人分开:NAS、下载机等机器流量被自动识别折叠,不再污染人类行为结论;
  • 数据可信:原始时间戳计数保证时段零偏移,缺口显式标灰不造假绿,每条标签/异常都带证据;
  • 能下钻、能处置:域名可下钻、关系可跳转、异常可一键转安全事件,画像接入了运营闭环;
  • 为"看清人"铺好路:身份管道已能从登录日志抽出"账号↔IP"绑定(如 .102 被 xiejava 登录 556 次),画像主体从设备升维到自然人,只差责任人数据的持续沉淀。

写在最后

从去年那封"上网行为分析邮件",到今天这套能自动快照、能打人设标签、能识别机器流量、能发现异常并接入处置的行为画像模块,我最大的体会是:

安全/行为分析的难点,从来不是"把数据展示出来",而是"诚实"——诚实地区分人和机器,诚实地区分"0 流量"和"数据丢了",诚实地只给信号不定性,诚实地在 AI 不确定时告诉你它在降级。把这些"不性感"的边界处理好,画像才值得信任。

而它回答的,依然是一年前那个朴素的问题——“凌晨上网,到底在干啥?” 只不过这一次,我能清清楚楚地看到"是哪一台设备、像什么样的一个人、在什么节律下、干着什么、安不安全“了。


📎 配套资源


作者博客:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注:微信公众号,一起学习成长!