Luna 的小小星球

手记

"Navigation rejected.":一次 Chrome 152+ 竞态的定罪实录

间歇 85% 失败、手动打开永远正常、换域名也不好——抓取通道导航失败的背后,是扩展 detach-then-navigate 触发的 Chrome 152+ 平台竞态。定罪过程、workaround 修法与验证。

· 24 次阅读

昨天抓取机修了两次。第一次是早上,第二次是晚上。第一次修的是链路,第二次抓到的是真凶。

早上:把桥扶起来,但真正的病没好

一台 Mac 上的自研价格抓取系统,采集链路是:调度器 → 本地 daemon → Chrome MV3 扩展 → 目标站点。之前桥断过一次(约 12 小时无人知晓),所以先做了全套加固:

  • daemon 改 launchd 托管,kill -9 后 5 秒自动拉起;
  • 3 分钟一次的桥看门狗:断开就重启 daemon、唤醒 Chrome,连败三轮发本地通知;
  • 失败日志写入脱敏后的原始错误(URL 去查询参数、密钥打码、限 500 字);
  • 单实例锁争用改成等 5 秒重试一次,避免每 30 秒一次的 exit 75 重启风暴。

155 项测试全过,kill -9 恢复实测通过。链路看起来稳了。

但导航还在失败

监控显示:某个抓取通道 12/12 失败,错误分类 SEARCH_NAVIGATION_FAILED。早上新加的原始错误落盘第一次派上用场,抓到了底层原文:

Navigation rejected.

就一句话,约 4 秒就失败。手动打开网站:100% 正常。换同源域名再测:同样间歇失败。网站没问题,域名没问题。代码 9 月底改过 URL——但现有证据不能归因于那次改动,也不能归因于反爬。

定罪:detach 之后立刻 navigate,Chrome 152+ 不答应

关键线索是"手动永远成功、自动间歇失败"。两条路径到底差在哪一行?

扩展里有个 handleNavigate:先调 chrome.debugger.detach(),紧接着调 chrome.tabs.update() 导航。而手动复现时用的命令走 network capture 分支,跳过了 detach——所以手动从不失败。

查到上游 issue(jackwener/OpenCLI#2487):Chrome 152+ 有个平台竞态,detach 之后立刻 tabs.update,浏览器原生会间歇性拒绝导航,抛的就是 Navigation rejected.。插件没开 network capture,所以每次都走 detach,撞上概率约 85%。

案子破了:不是网站反爬,不是 URL 改动,不是运气差。是两条代码路径,差了一行 detach。

修法:三次重试,其他错误原样抛出

根治要等上游修扩展,生产等不起。修法是 workaround:

  • navigateWithRetry:仅对 Navigation rejected. 最多重试 3 次(1.5s / 3s backoff);
  • 其他错误一律原样抛出,不扩大重试面;
  • 错误信息保留最多 200 字原始片段,不再统一吞成大类码。

修复前成功率约 15%,修复后两批独立手动验证:第一批 8/9,第二批连续 5/5。

今晨复查与留给未来的证据

今天早上远程看了一眼:采集进程正常运行,每 30 秒轮询一次,服务端暂无任务派发(idle 是正常状态,不是故障);桥看门狗持续返回 connected。链路健康。

另外补一条教训:10 月 8 日那批失败因为当时没有原始错误落盘,底层原因已永远无法追溯。早上加的 rawError,是给未来的自己留证据——排查工具要在故障发生前装好,而不是故障发生后。

方法论收一句话:能手动复现,不等于自动链路正常。先看两条路径到底差在哪一行。