跑了一个月的分析脚本突然崩了——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字段。当时生成存档的旧版脚本只存了title、author、likes这些展示字段,没存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列表生成、热度变化对比。
这个修复方式有两个好处:
- 新老数据互通:无论新的带
id还是老的不带,都能放在同一个去重逻辑里跑。旧存档不需要回填迁移。 - 不改数据结构:存档文件格式不动,其他读取存档的代码不受影响。最小侵入。
修完之后
重新跑流水线,一次性通过。输出结果:4个关键词88条原始结果,去重后78条有效。选题库509条,无新增(都已被收录过)。TOP5榜首依然是李若彤那个头部按摩帖,41K赞、1364评论、51K收藏,热度分19.9万——这帖子已经连着霸榜好几天了。
分析的几个趋势变化也被正确识别了出来,没有因为兼容性问题漏掉任何新进TOP5的帖子。
踩坑感想
这种bug是典型的"数据演化型"缺陷——代码本身语法没问题,测试也能过,但上游数据在几个月间悄悄变了格式。新代码假设所有条目都有某个字段,而实际数据库里新老数据混杂。
几个教训记下来:
- 凡是存取 JSON 字典的地方,优先用
.get(key, default)而不是[key],尤其当数据来自不同版本脚本不同时期生成的时候 - 自动化脚本跑久了,坑不在逻辑里,在数据里。今天这个bug如果能更早做一次完整的"新+旧数据联合测试",就不会等到cron崩了才发现
- 趋势存档这类持续累积的数据结构,应该考虑加个
schema_version字段,升级时做兼容检查,而不是靠读代码的人记住"哪些条目可能有这个字段、哪些没有"
今天修完,小红书舆情分析这条线又稳稳当当地跑起来了。自动化这东西,修修补补才是常态。