华尔街日报两天撞数:cron 抄了前一日,以及我怎么把采集钉死
有一天我打开两个页面:
https://venxine.vip/wallstreet/20260714https://venxine.vip/wallstreet/20260715
道琼斯、标普、纳指、费城半导体,几乎一模一样。
第一反应通常是:路由是不是把两天指到同一份数据了?模板是不是缓存串了?Flask 是不是读错文件了?
都不是。两天各自读的是不同文件。坏的是文件内容本身。
一、先排除「页面层」的嫌疑
venxine.vip 的华尔街日报按日期落盘:
~/.hermes/demo2/{US_DATE}_indices.json
~/.hermes/demo2/{US_DATE}_analysis.json
路由大致是:访问 /wallstreet/20260715,就去读 2026-07-15_*.json。
所以如果两个 URL 点位相同,只有两种可能:
- 两天文件内容本来就相同
- 路由/模板把 15 号读成了 14 号
我把两个 JSON 直接打开对比,答案立刻清楚:2026-07-15_indices.json 里的四大指数,就是 14 号那一套。页面只是忠实地把错误数据渲染了出来。
这是数据管线事故,不是前端事故。
二、撞数长什么样
修复前,7/15 文件里躺着的是 7/14 收盘(后来已备份保留):
| 指数 | 错误的 7/15(抄自 7/14) | 修好后的 7/15 实盘 |
|---|---|---|
| 道琼斯 | 52,508.27 +0.02% | 52,658.64 +0.29% |
| 标普500 | 7,543.59 +0.38% | 7,572.40 +0.38% |
| 纳指 | 26,107.01 +0.90% | 26,269.23 +0.62% |
| 费城半导体 | 12,661.93 +2.54% | 12,398.89 -2.08% |
注意 SOX:错误文件还在写「大涨 2.54%」,修好后其实是回吐 −2.08%。
不只是数字重复,连涨跌方向都反了。分析文案也跟着偏:7/15 一度复用了 7/14 的 CPI / SK 海力士 / IBM 叙事,而不是当天的大科技回流、PPI、威廉姆斯表态那条主线。
一句话:cron 在 7/15 凌晨那次跑偏了,把「看起来像美股收盘」的旧结果,写进了新日期的文件名。
三、根因:不是模型笨,是采集约束太软
这条日报是 Hermes cron:
| 项 | 值 |
|---|---|
| job id | c46a8af737cc |
| 名称 | 华尔街日报 |
| 时间 | 北京时间工作日 06:00(0 6 * * 2-6) |
| 模型 | deepseek-v4-pro(固定 pin,不跟主会话 Grok) |
事故当时,prompt 和挂载的 skill 有几个结构性漏洞:
1. 指数来源太模糊
旧流程大致是:用 web_search 搜「美股收盘」,从新闻摘要里抠点位。
新闻摘要有两个坏习惯:
- 喜欢复述「截至昨晚」或前一交易日的数字
- 不同来源口径混用,且不给你原始 tick
Agent 一旦在摘要里看到整齐的四个点位,就很容易把它们当成「今天收盘」写进 JSON。文件名是新的,内容是旧的。
2. skill 挂错 / 约束不够专
加固前,任务没有牢牢绑在 wallstreet-daily 这条生产 skill 的强制采集流程上。
通用搜索 skill 能「找到点东西」,但不会替你守住「四大指数必须以某某 API 为准」。
3. 落盘前没有去重门禁
即使采错了,只要 JSON 语法正确,就能写进 demo2/。
没有人问一句:这四个点位是不是和昨天文件一模一样?
这三条叠在一起,事故几乎是必然的。不是偶发幻觉,是管线允许「抄旧数」发生。
四、当天怎么修数据
顺序很重要:先止血,再加固。
- 备份坏文件
2026-07-15_indices.json.bak.20260716-
2026-07-15_analysis.json.bak.20260716 -
重采 7/15 四大指数
用新浪行情 JS 接口做交叉源,再对照 CNBC 当日主线。 -
重写 analysis
summary / 新闻主线按 7/15 重写,不再复用 7/14 的 CPI 叙事。 -
公网验证
/wallstreet/20260715显示新点位;/20260714仍是正确的 14 号收盘。两天不再撞数。
页面层一行业务代码都不用改。坏的是输入,修好输入就够了。
五、cron 加固:三道钉子
数据修好只解决「这一天」。要防止明天再抄,得改生产规则。
钉子 1:skills 钉死为 wallstreet-daily
skills: ['wallstreet-daily']
不再让任务在通用搜索 skill 上自由发挥。
华尔街日报有自己的数据格式、字段约定、页面契约,就该挂专属 skill。
钉子 2:四大指数强制 Sina curl(唯一真源)
prompt 里把主指数采集写成硬步骤,不允许跳过:
curl -s --max-time 10 -H "Referer: https://finance.sina.com.cn/" \
"https://hq.sinajs.cn/list=gb_dji,gb_inx,gb_ixic,gb_sox" | iconv -f gbk -t utf-8
映射固定:
| 代码 | 指数 |
|---|---|
gb_dji |
道琼斯 |
gb_inx |
标普500 |
gb_ixic |
纳指 |
gb_sox |
费城半导体 |
规则写死:
- 主指数
price必须来自本次 curl 输出 - 禁止用新闻摘要里的旧收盘数替代
- 文件名日期 = 美东交易日
US_DATE,不是北京日期
RUT / VIX / 美债 / DXY / BTC / WTI 可以走交叉源;但页面上最显眼的四大指数,不再信任「搜到的话」。
钉子 3:落盘前 top3 去重校验
写正式文件前,先写候选:
/tmp/{US_DATE}_indices.candidate.json
再跑校验:若新文件前 3 项(道指 / 标普 / 纳指)的 price 与最近一个历史 *_indices.json 全部相同,直接失败,禁止写入,要求重采。
伪逻辑就一句话:
if DJIA/SPX/IXIC 三点位 == 最近历史日三点位:
FAIL_DUPLICATE_TOP3 → 不许写 demo2
这是我最看重的一道门。
它不判断「今天该涨还是该跌」,只判断「你是不是又把昨天的数贴过来了」。对这种事故,够用,也够狠。
同时 analysis 也加约束:
- 搜索关键词必须带
US_DATE - 禁止把前一交易日主线新闻原样当成本日主线
summary里的指数数字必须与 indices JSON 完全一致
skill 文档 wallstreet-daily/references/cron-prompt.md 里,这次事故被写成了置顶警告,免得以后改 prompt 时把钉子拔掉。
六、加固后的生产流程长什么样
北京时间工作日 06:00,cron 醒来:
0. 确认 CN_TODAY / US_DATE;休市则 [SILENT] 退出
1. Sina curl 拉 DJIA/SPX/IXIC/SOX
2. 交叉源补 RUT/VIX/美债/DXY/BTC/WTI
3. 写 candidate JSON → top3 去重校验
4. 通过后落盘 demo2/{US_DATE}_indices.json
5. 按 US_DATE 搜当日驱动,写 analysis.json
6. JSON 自检 + 输出四大指数表
模型还是 DeepSeek,没有换成 Grok。
原因和模型分工那篇一致:定时任务要稳、要可预期、要 pin 死,不跟主会话追新。
这次事故也说明:换更强模型解决不了「源没钉死」。
源软,什么模型都可能抄旧数;源硬,普通模型也能交出对的表。
七、这类事故的通用教训
不只华尔街日报,任何「每天落一份 JSON 给网页吃」的 cron 都可以套:
-
页面相同,先比文件,再查渲染。
很多「前端 bug」其实是上游写脏了。 -
搜索摘要不是行情源。
新闻适合解释「为什么涨跌」,不适合当唯一收盘价来源。 -
文件名新,不代表内容新。
要有跨日去重或哈希对比,而不是只检查「文件写成功了吗」。 -
专属 skill + 强制命令,比一段散文 prompt 更抗漂。
「请获取美股收盘」太软;「必须 curl 这个 URL,parts[1] 是价」才硬。 -
修数据与修管线要分开做。
只修当天 JSON,明天还会再坏;只改 prompt 不修当天,线上脏数据还在。
八、现状
截至 2026-07-16:
- 7/14、7/15 公网页点位已分开,且与备份中的坏文件可对照
- cron
c46a8af737ccskills =wallstreet-daily - 主指数唯一真源 = Sina JS API
- 落盘前 top3 与历史日去重
- 坏文件备份仍在
~/.hermes/demo2/backups/,事故可复核
下次 06:00 再跑时,如果 Agent 又想偷懒贴昨天的数,校验脚本会先把它拦下。
数据采集这事,信任很便宜,门禁比较贵。贵一点是对的。