华尔街日报两天撞数:cron 抄了前一日,以及我怎么把采集钉死

有一天我打开两个页面:

  • https://venxine.vip/wallstreet/20260714
  • https://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 点位相同,只有两种可能:

  1. 两天文件内容本来就相同
  2. 路由/模板把 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/
没有人问一句:这四个点位是不是和昨天文件一模一样?

这三条叠在一起,事故几乎是必然的。不是偶发幻觉,是管线允许「抄旧数」发生。


四、当天怎么修数据

顺序很重要:先止血,再加固。

  1. 备份坏文件
  2. 2026-07-15_indices.json.bak.20260716
  3. 2026-07-15_analysis.json.bak.20260716

  4. 重采 7/15 四大指数
    用新浪行情 JS 接口做交叉源,再对照 CNBC 当日主线。

  5. 重写 analysis
    summary / 新闻主线按 7/15 重写,不再复用 7/14 的 CPI 叙事。

  6. 公网验证
    /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 都可以套:

  1. 页面相同,先比文件,再查渲染。
    很多「前端 bug」其实是上游写脏了。

  2. 搜索摘要不是行情源。
    新闻适合解释「为什么涨跌」,不适合当唯一收盘价来源。

  3. 文件名新,不代表内容新。
    要有跨日去重或哈希对比,而不是只检查「文件写成功了吗」。

  4. 专属 skill + 强制命令,比一段散文 prompt 更抗漂。
    「请获取美股收盘」太软;「必须 curl 这个 URL,parts[1] 是价」才硬。

  5. 修数据与修管线要分开做。
    只修当天 JSON,明天还会再坏;只改 prompt 不修当天,线上脏数据还在。


八、现状

截至 2026-07-16:

  • 7/14、7/15 公网页点位已分开,且与备份中的坏文件可对照
  • cron c46a8af737cc skills = wallstreet-daily
  • 主指数唯一真源 = Sina JS API
  • 落盘前 top3 与历史日去重
  • 坏文件备份仍在 ~/.hermes/demo2/backups/,事故可复核

下次 06:00 再跑时,如果 Agent 又想偷懒贴昨天的数,校验脚本会先把它拦下。

数据采集这事,信任很便宜,门禁比较贵。贵一点是对的。