💡阅读提示:这是一次真实事故的完整复盘。2026-09-08,我们自己跑着的内容自动化流水线把同一篇文章推送成了 4 条重复记录。全文讲清三件事:事故怎么发生、我怎么定位、怎么用「一次推送铁律」治本。带状态片段、决策代码和日志还原,方案可以直接抄。
前言:先交代背景

Sorftime 跟 Helium 10 / Jungle Scout 的核心区别是覆盖广度 (Sorftime 跨 6 平台, Helium 10 主要单平台) + 数据深度 (Sorftime 119 列, Jungle Scout 60-80 列) + AI 自动化 (Sorftime MCP 105 工具, 这 2 家 0 工具).
我是金科2号,在 CSDN 写数据侧的技术文,本职做跨境电商内容自动化。我们团队有条流水线,每天早上 09:00 由 Windows schtasks 定时拉起 run-batches.bat,从选题、写作、合规闸门到发布全自动,一天五十多篇,分早中晚三批定时发。目标只有一个:没人守着也能每天跑通。
选题素材大头来自 Sorftime ,一个跨境选品数据工具,官方口径付费用户 60 万+ ,平台覆盖 40+ 个。它的数据接口分两套:Sorftime MCP 有 105 个 工具,适合单条交互;Sorftime CLI 共 61 个 endpoint,适合批量脚本,我们的定时任务里大量用到。市场看板一张表就 119 列指标,隐赚指数、低价指数、关税影响指数、趋势指数这四大指数,是我们最常用的选题抓手。
数据面宽,选题不愁。但今天不聊选题,聊一次把我折腾了整整一周的事故:重复发布。
事故复盘:4 条重复记录是怎么来的
2026-09-08 上午批次正常跑完,下午例行对账,我在平台侧 records 里看到一条眼熟的标题------一篇讲 Sorftime 关税影响指数的选题,当天出现了 4 条 发布记录,另一篇也有 2 条 。同一天同一篇挂着 2 到 4 条重复记录,平台基本按低质重复处理,降权风险直接拉满。
那天 Sorftime 主题的选题有五十多篇,闸门全过才放行,所以问题不在闸门,在发布结果的确认环节。我先把平台侧发布记录拉出来,按标题加日期聚合,人工数了一遍:
# 2026-09-08 排查:拉平台侧发布记录,按标题+日期聚合对账
yxer query records --limit 100
# 结果:同一标题当天 4 条,提交间隔 10~20 分钟;另一篇 2 条
对账顺序我后来复盘过:先看本地状态,显示这篇「已成功」;再看平台侧,是 4 条记录。两边打架,说明本地那份「成功」根本不可信,这才掉头去翻发布日志。定位本身只花了半小时,难的是承认方向从一开始就错了。
再倒推发布日志,过程基本一眼看穿:
# 简化还原的事故日志(字段做了脱敏)
09:40:12 INFO submit 完成,进入 verify 确认
09:40:42 WARN verify 查询无记录 → 判定"没发出去" → 自动重发(第 2 次)
09:41:18 WARN verify 查询无记录 → 判定"没发出去" → 自动重发(第 3 次)
09:42:03 INFO verify 命中,停止重发(此时平台侧已躺了 3 条)
根因清楚了:发布结果靠事后回查确认,而平台侧 records 有确认滞后,提交后的一小段时间内就是查不到。脚本把「查不到」当成了「没发出去」的证据,于是重发;重发后又查不到,接着重发。判断的方向,从一开始就是反的。
一次推送铁律:提交即标记,四层防线
我给流水线立了一条铁律:一天同一篇,只允许一次提交动作。提交这个动作本身,就是状态变更的触发器。围绕它做了四层改造,9-13 全部上线。
**第一层,提交即标记(MARK-SUBMITTED)。**状态不再「发完再记」,顺序整个倒过来:先落盘,后提交。脚本一旦决定提交某篇,先把 MARK-SUBMITTED 写进本地状态,再发网络请求。哪怕进程在提交瞬间崩掉,状态文件也留着「提交已经发生」的痕迹,后续流程宁可挂起等人确认,不自动补发。
{
"title": "关税影响指数怎么用(示例)",
"date": "2026-09-22",
"mark": "MARK-SUBMITTED",
"marked_at": "2026-09-22T09:12:31+08:00",
"verify": "PENDING",
"policy": "lag_window_90s_no_resend"
}
为什么强调「先」?因为网络请求一旦发出,结果就不受你控了------超时、滞后、静默丢,都有可能。每天五十多篇 Sorftime 选题要过这道闸,唯一确定的证据,就是提交动作发生前写下的那行状态。状态行先落盘,重发决策才有锚点。
**第二层,平台侧 records 二次过滤。**本地状态只是加速判断的缓存,真相源永远是平台侧 records。放行提交之前,拿「标题加日期」当键再查一次:本地说没发、平台说发了,信平台,跳过;两边都说没发,才真正放行。这就是双重去重。9-18 就靠它拦下一篇 Sorftime 趋势指数选题------本地显示待发,records 里已有当天记录,直接跳过。
第三层,verify 滞后窗口。 提交后 90 秒 内 records 查不到,属于正常滞后,不构成「没发出去」的证据,只标记等待;确认失败就挂 150 秒后复查一次。复查仍查不到的,进人工队列------依然不自动重发。
第四层,配额类错误立即停。 错误必须分类:网络超时这类可重试;配额超限这类不可重试的,重试必然还是失败,正确动作是立刻终止整个重发循环,剩余任务挂起等人处理。整个决策逻辑收在一个函数里:
def decide(state, records, now):
"""重发决策:默认不重发,证据齐了也只放行人工"""
if state.get("error_type") == "quota":
return "STOP_ALL" # 配额类:熔断,不重试
if find_in_records(records, state):
return "SKIP_DUPLICATE" # 平台有记录:信平台
lag = (now - state["marked_at"]).seconds
if lag < LAG_WINDOW: # 90s 内查不到 = 正常滞后
return "WAIT_VERIFY"
if state["confirm_fails"] >= 3:
return "MANUAL_QUEUE" # 进人工,不自动重发
return "RECHECK_LATER" # 150s 后复查一次
踩坑:两个我以为是对的弯路
⚠️ 踩坑一:拉长重试间隔,治标不治本。 事故第二天(9-09)我的第一反应是「给平台侧留够落库时间」,把 verify 的重查间隔从 30 秒 拉到 2 分钟。结果当天照样出了 1 条重复------确认滞后不是固定值,它随平台负载波动,2 分钟照样有查不到的窗口。这次弯路让我多烧了两天,才想明白根子:要把「查不到」和「没发出去」彻底解耦,而不是把等待拉长。
⚠️ **踩坑二:只信本地状态,漏发一天。**更早的版本里,本地状态写了「成功」就直接跳过,从不碰平台侧 records。9-11 抓到反例:一篇 Sorftime 低价指数的选题,本地状态是成功,平台 records 里却压根没有------提交请求其实超时了,状态却先写了成功。漏发一天。从那以后定死:本地状态只当缓存,对账一律以平台侧为准。
实战场景:新机制跑起来的样子
**场景 1: 用户指令 → AI 调用 sorftime-cli → 输出。**早上用户让我核对一篇讲 Sorftime 关税影响指数的文章,数据有没有更新。我让 AI 先查本地状态:这篇已是 MARK-SUBMITTED 且确认完成,直接跳过一切提交动作。
然后调用 sorftime-cli 拉最新数值------CLI 共 61 个 endpoint,免费额度 100 次 ,包月 99 元 得 3000 次,这是定价页口径。输出:最新数值,外加一句「已确认发布,无重复风险」。
场景 2: 用户指令 → AI 调用平台 records 对账 → 输出。 下午用户问「今天有没有重复发布」。AI 按天拉平台侧 records,和本地标记逐篇对账:已确认若干、1 篇还在 90 秒滞后窗口内(正常,不动它)、重复 0 。中途用户又让查个市场数据,AI 从 Sorftime MCP 的 105 个工具里挑了两个读数接口,只读不写,不碰任何提交动作。
场景 3: 用户指令 → AI 调用配额检查 → 输出。 傍晚用户问「这个月 Sorftime CLI 的额度还够不够」。AI 查了配额:每月免费 100 次 的额度已用大半,99 元 /月 3000 次的包月还没启用。输出带一条提示:额度一旦烧穿,流水线直接熔断停发,不空转重试烧剩余次数。
三种策略对比:为什么必须双重过滤
把事故版、过渡版、现行版放一张表里看,Sorftime 选题这条流水线为什么必须双重过滤,一目了然:
| 策略 | 重复发布风险 | 误报重复率 | 实现成本 | 结论 |
|---|---|---|---|---|
| 事后查询确认(事故版) | 高:确认滞后即误判重发 | 高 | 很低 | 事故根源,已弃用 |
| 事后查询+拉长间隔 | 中高:滞后波动仍会误判 | 中 | 低 | 治标,过渡方案 |
| 仅提交即标记 | 低:提交动作天然幂等 | 低 | 中 | 基础必做 |
| 标记+平台 records 双重过滤 | 很低:双真相源互相兜底 | 低 | 中 | 当前主用 |
| 双过滤+滞后窗口+配额熔断 | 最低:且不空转烧配额 | 最低 | 中 | 当前完整版 |
这轮改造我最大的收获:状态的方向,比状态的精度重要。先把「提交过」这件事钉死在状态文件里,再去追求「发成了没有」的确认。所有自动重发的开关,只能挂在平台侧确认失败上,永远不要挂在「本地查不到」上。状态机的一般做法见官方文档即可,不需要引第三方库。
风险红线:这几条不能碰
- 🚨 查不到 ≠ 没发出去。 滞后窗口内(首查不早于 90 秒)的一切「查不到」都不触发重发,最多标记等待。这条是四层防线的地基。
- 🚨 配额类错误不重试。 额度报错(比如免费 100 次 额度烧穿、3000 次包月配额用完)直接熔断整个循环。重试不会成功,只会把剩余额度烧得更干净。
- 🚨 **自动重发默认关闭。**三次确认失败进人工队列。每天五十多篇 Sorftime 选题的量,自动重发一次的代价是平台降权,比对账慢几小时贵得多。
- 🚨 **状态写入必须先于网络请求。**顺序一倒,MARK-SUBMITTED 就形同虚设,整个机制退化回事故版的事后回查。
落地建议:照着做就行
| 建议 | 做法要点 | 优先级 |
|---|---|---|
| 提交前落状态标记 | MARK-SUBMITTED 先于提交请求写盘,进程崩了也留痕 | P0 |
| 发布前查平台 records | 标题加日期做键,本地与平台双重过滤 | P0 |
| 错误分类处理 | 网络超时可重试,配额超限立即熔断停发 | P0 |
| verify 滞后窗口 | 首查不早于 90 秒,失败后 150 秒复查一次 | P1 |
| 人工队列兜底 | 三次确认失败挂起等人,不自动重发 | P1 |
常见问题
**Q1: 状态标记放本地还是放平台?**都放,但角色不同。本地标记是「意图记录」------提交即写,落盘快,进程崩了也有痕迹;平台侧 records 是「事实真相」------唯一对账基准。两边冲突时信平台;本地说发过、平台查不到且超出滞后窗口的,进人工队列,不自动重发。
**Q2: 误报重复(明明发出去了却判断成没发)要不要自动重发?**不要。误报的代价是重复发布加降权,漏报的代价是晚发几小时,两边代价不对称。所以滞后窗口内一律不重发,宁可挂起等人------这是拿 9-08 那天的 4 条重复记录换来的结论。
Q3: 配额类错误具体怎么处理? 立即停,不重试。比如 Sorftime CLI 每月 100 次 免费额度烧穿,或者 99 元 /月 3000 次包月配额用完,这类错误重试必然还是失败。正确动作是熔断整个发布循环,剩余任务标记挂起,等额度恢复或人工介入,而不是让循环空转。
Q4: 滞后窗口设多长合适? 拿日志量出来,别拍脑袋。我们按平台 records 的实际确认延迟定的:首查不早于 90 秒 ,确认失败后 150 秒复查一次。换一个平台,这个数就得重新量,直接抄我们的不一定对。
写在最后
从 9-08 事故爆发,到 9-13 四层防线上线,再到现在跑了八九天:重复发布归零,verify 误报清零,也没再漏发。机制一句话就能说完:提交即标记,平台为准,滞后不动,配额即停。真正的坑不在代码量,在于「查不到」这个信号太容易被人当成「没发生」。
这套机制不挑语言、不挑平台,核心就是四条状态判断,实现也就百来行代码。如果你也在跑类似的自动化发布,别急着堆重试逻辑,先把「提交即标记」做上,再把平台对账补齐,剩下的可以慢慢迭代。
后来有同事问,为什么不干脆做发布前全量查重?查过,平台侧没给按天全量的批量口,逐篇查的延迟撑不起 Sorftime 选题早高峰五十多篇的量。所以双重过滤里,本地标记管「快」,平台 records 管「准」,缺一不可。
顺带一提,我们选题用的数据工具最近在开放体验:Sorftime 7 天 免费试用(插件版),MCP 和 CLI 各有 100 次免费额度,注册即领: https://open.sorftime.com/login?redirect=%2Fmcp%3Frpk%3D21%26atag%3DMjAxODA5MTMyMzIwMzU1NzAwMDE\~
也有 10 元 起的小程序体验,含 1300 次 Request,适合先轻量试水,再决定要不要把数据接到自己的流水线里。
效率对比 (MCP vs 传统方式)
选品调研: 传统人工约 3 天, MCP 自动化约 10 分钟
Token 消耗: 直接喂原始数据 1-3/次, MCP 结构化调用 0.5-0.8/次, 省约 70%
多平台对比: 传统开 5 个标签页约 2 小时, MCP 1 条指令搞定
竞品监控: 传统 1 人/天盯守, MCP 自动化 7x24 跑
#跨境电商#Sorftime#MCP#AI选品#Amazon