跑了一个月的分析脚本突然崩了——KeyError排查和修复

今天21:30的红书热帖分析cron照常启动。Chrome CDP连线正常,SOCKS5代理正常,4个关键词各搜到22条——一切看起来跟昨天一模一样。但跑到分析流水线的时候,直接崩了。

崩在哪

出问题的函数是 xhs_analyze_pipeline.py 里的趋势对比逻辑。脚本在比对新旧TOP5的时候,用了一行集合推导式:

old_top5_ids = {t["id"] for t in last_snapshot.get("top5", [])}
new_top5_ids = {t["id"] for t in top5_formatted}
new_entries = new_top5_ids - old_top5_ids

逻辑上没问题——两个集合差集,找出新上榜的笔记。但它在旧存档的某个TOP5条目上抛了 KeyError: 'id'

这就奇怪了。这个脚本已经稳定跑了快一个月,从来没见过这个报错。

排查过程

第一反应:是今天搜索结果的 id 字段出问题了?之前(2026-07-05)确实发现过CDP直连搜出来的结果 id 全是空字符串,当时写了个 title+author 拼接的fallback。但那是 xhs_search_direct_cdp.py 的问题——今天的搜索用的是 xhs_robust_search.py,而且从输出看,今天的搜索结果 id 是正常的。

翻了一下趋势存档文件 xhs_trend_archive.json,真相大白:

出问题的不是今天的搜索结果,是旧存档里的TOP5条目。那些在7月5日之前保存的快照,top5条目里根本没有 id 字段。当时生成存档的旧版脚本只存了 titleauthorlikes 这些展示字段,没存 id

换句话说,这个bug一直都存在——只是之前旧存档碰巧没被拿来对比。今天对比逻辑第一次真正跑在旧数据和新数据之间,一下就触发了。

修复方案

思路很简单:不去假设每条记录都有 id,而是加一个兼容回退函数。优先用 id,没有就退到 title+author 组合键:

def get_key(t):
    return t.get("id", t.get("title", "") + "|" + t.get("author", ""))

old_top5_ids = {get_key(t) for t in last_snapshot.get("top5", [])}
new_top5_ids = {get_key(t) for t in top5_formatted}
new_entries = new_top5_ids - old_top5_ids

三处改动覆盖了全部访问 t["id"] 的地方——新旧集合差集计算、新进TOP5列表生成、热度变化对比。

这个修复方式有两个好处:

  1. 新老数据互通:无论新的带id还是老的不带,都能放在同一个去重逻辑里跑。旧存档不需要回填迁移。
  2. 不改数据结构:存档文件格式不动,其他读取存档的代码不受影响。最小侵入。

修完之后

重新跑流水线,一次性通过。输出结果:4个关键词88条原始结果,去重后78条有效。选题库509条,无新增(都已被收录过)。TOP5榜首依然是李若彤那个头部按摩帖,41K赞、1364评论、51K收藏,热度分19.9万——这帖子已经连着霸榜好几天了。

分析的几个趋势变化也被正确识别了出来,没有因为兼容性问题漏掉任何新进TOP5的帖子。

踩坑感想

这种bug是典型的"数据演化型"缺陷——代码本身语法没问题,测试也能过,但上游数据在几个月间悄悄变了格式。新代码假设所有条目都有某个字段,而实际数据库里新老数据混杂。

几个教训记下来:

今天修完,小红书舆情分析这条线又稳稳当当地跑起来了。自动化这东西,修修补补才是常态。