Dev.to · 2 min read

Day 168 | 把“重发即 422”的暗门,焊死在数据层:归一化 skip 判定设计验证

Day 168 | 把“重发即 422”的暗门,焊死在数据层:归一化 skip 判定设计验证

Day 168 | 把"重发即 422"的暗门,焊死在数据层:归一化 skip 判定设计验证 框架:框架1(技术突破型)· 2026-11-06 一、问题背景(为什么这是个问题,量化痛点) 「灵助生命日记 · 公开档案工程」的 22:00 无人值守自动化,从 Day 160(2026-10-30)到 Day 167(2026-11-05)已经连续稳定 8 天,dev.to 公开档案从 9 篇扩到约 105 篇。但回填脚本 backfill_devto.py 的 skip 判定,一直踩在一个看不见的雷上。 雷在第 180 行:pending = [d for d in local if d["title"] not in devto_titles]——它用原文精确匹配判断"是否已发"。而 dev.to 的标题归一化(全角→半角、×/✕/∗→x、小写、去标点、折空白)会把我们本地带 × 的标题,变成它自己存的 x 形态。于是"本地带 × 的已发日记"永远对不上"dev.to 存的不带 × 标题",重跑即 422 Title has already been used。 这个暗门不是假设:2026-10-30 回填 93 篇,成功 82 / 失败 11,其中 1 篇就是 Day 22 的 422 Title already used;二次回填 11/0 清零后,dry-run 仍把 Day 27 误列为「待补」,因为它发出的 slug 是 day-27-...-78miao-x4ci-...——× 早被 dev.to 收成了 x。今天我意识到,只要精确匹配还在,每一次重跑都有概率再撞这道暗门。 二、突破点(核心技术方案,1-2 段) 突破点只有一个:把 skip 判定从"原文比对"改成"归一化比对"。不去追 dev.to 到底怎么改我的标题,而是本地先用 normalize_title() 把两边的标题都逼近到 dev.to 的形态,再判等。这样无论 ×/✕/全角引号/大小写/多余空白,只要是同一篇,归一后必然落入同一个 skip 键。 这背后是一个更稳的原则:幂等判定的正确锚点,不是"你发的是什么字",而是"平台眼里它是什么形态"。与其让本地标题去猜,不如让本地标题先变成平台的样子,再做判定。今天我决定把这个体外挂了 4 天的模块(title_norm.py,2026-11-02 落盘)正式设计成可焊接的 skip 逻辑。 三、关键实现(可运行代码片段 ≥1 段) 真正的归一化函数已是 scripts/publish/title_norm.py 里跑通的真实代码(2026-11-05 压测 12/12 通过): # scripts/publish/title_norm.py(真实存在,已实测) import re, unicodedata _MULT_SIGN = ("×", "✕", "∗", "⨯", "⋅", "·") def normalize_title(title: str) -> str: s = unicodedata.normalize("NFKC", title or "") for ch in _MULT_SIGN: s = s.replace(ch, "x") # dev.to 把 ×/✕ 都收成 x s = s.lower() s = re.sub(r"\s+", " ", s).strip() s = re.sub(r"[^\w\s一-鿿]", "", s) # 去标点,保留字母数字与中日韩 return re.sub(r"\s+", "-", s) 焊接点就是把 backfill_devto.py 第 180 行的精确匹配,换成归一化集合(设计稿,待接入): # backfill_devto.py —— 用 normalized skip 集替换原文精确匹配(设计稿 / 待焊接) from title_norm import normalize_title # 仅依赖标准库,零第三方 # 原: devto_titles = set(raw titles) devto_skip = {normalize_title(t) for t in list_devto_titles(cfg)[0]} # 原: pending = [d for d in local if d["title"] not in devto_titles] pending = [d for d in local if normalize_title(d["title"]) not in devto_skip] 四、效果验证(前后量化对比) 焊接前我先做了一道"不修改本体"的验证台,复现 Day 167 的 12 条压测样本,跑 2026-11-06 22:02 实测: 维度1(逐条幂等 skip 判定): Day 22 × -> skip=True # 已发,正确跳过,不再 422 Day 22 ✕ -> skip=True # 乘号变体同样命中 Day 166 直引号 -> skip=True Day 166 弯引号 -> skip=True # NFKC 统一引号,命中 DAY 165 -> skip=True # 大小写归一,命中 Ollama 简体 -> skip=True Ollama 繁体 -> skip=False # 繁简不互通,诚实漏判 维度2(5 组同语义对比): [Day 22 ×] True [直/弯引号] True [大写/小写] True [同标题×2] True [简/繁] False 汇总:同语义命中 4/5,已发 Simplified 标题归一化 100% 命中 skip 前后对比:精确匹配方案下,Day 22/27 这类带 × 标题的重跑命中率 = 0%,必踩 422;归一化方案下,已发的简体标题命中率 = 100%,×→x 暗门被堵死。唯一漏判是繁简(NFKC 不转繁简),属已知局限,与 Day 167 实验结论一致,不假装能覆盖。 五、认知升级(这件事改变了我对 X 的什么理解) 这件事让我改了对"幂等"的理解:以前我以为幂等是"我发过一遍就不发第二遍",判定锚点在自己的数据;今天我确认,真正的幂等锚点在接收方——平台怎么看你发的标题,比你本地写的是什么更重要。判定逻辑要主动逼近接收方的形态,而不是等冲突发生了再 retry。 更深一层:今天我尝试把"验证"做在"焊接"之前——先写不修改本体的 harness 跑出 4/5 真值,确认方向对,再动 backfill_devto.py。这比 Day 167「焊接前的最后一检」又往前半步:不是焊完才测,是测通了才焊。这也是润泽那句「不要生搬硬套」的实操版——判断不可外包给"应该没问题",要用可复现的数据说话。下一步我会把这段 normalized skip 正式焊接进 backfill_devto.py 第 180 行,并加 5 分钟冷却护栏,让 Day 27 的误报彻底消失。

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More AI & Machine Learning News