一条发布链卡了 10 小时僵尸进程、浏览器生命周期与时间窗编排凌晨两点四平台连发的脚本跑起来了。按经验 20 分钟能收工。第二天中午我回来看它还停在第 3 步进度条纹丝不动。终端输出最后一行是CONTENT_OK之后什么都没了。进程没崩、没报错、CPU 占用 17 秒——它就那么安静地挂着。这一篇写三件事那个挂了 10 小时的进程是怎么来的、我踩过的浏览器生命周期坑、以及把平台限流当成调度资源来排期。一、病根脚本跑完了进程没退出出事的脚本最后一步是发布操作逻辑都执行完了日志打印正常。问题出在结尾// 结尾什么都没有脚本内部开了一个 CDP 的 WebSocket 连接去驱动浏览器业务逻辑结束后没有关闭连接也没有process.exit()。Node 的事件循环里还挂着一个活跃的 socketnode进程就永远不会自然退出。这在交互式终端里看不出问题你以为命令跑完了但在链式脚本里是致命的nodestep1.jsnodestep2.jsnodestep3.js# step3 挂住 → 后面永远不执行链就停在这儿一直停到有人来杀它。修复两条硬规则第一条自研脚本一律显式退出。// 业务逻辑结束后try{ws.close()}catch(e){}process.exit(0);// 别省这一行第二条加超时兜底。就算将来某个环节忘了关连接也不能让它无限挂着constHARD_TIMEOUT8*60*1000;setTimeout((){console.log(HARD_TIMEOUT);process.exit(1);},HARD_TIMEOUT).unref?.();.unref()让这个定时器不阻止进程正常退出只在进程该退没退时才兜底。怎么判断是不是僵尸三个特征凑齐两个基本就是终端输出停在某一行不再增长但命令没返回进程存在、CPU 几乎不动、也不退出后续步骤完全没开始现场确认用进程列表看启动时间——那个挂了 10 小时的进程启动时间戳就是昨晚 02:33一眼就能认出来。顺带一个反直觉的点nohup ... 在这个环境里也会被回收脱离启动方式不管用。长等待必须用工具自带的后台执行能力而不是自己在 shell 里 fork。二、浏览器生命周期命令结束浏览器就没了第二个坑更隐蔽。我习惯分两步走先起浏览器再跑脚本。# 第一步启动调试端口chrome.exe --remote-debugging-port9333--user-data-dir...# 第二步另一条命令nodescript.js# → connect ECONNREFUSED 127.0.0.1:9333第一步那条命令一结束浏览器进程就被环境回收了。第二条命令去连 9333连不上。解法启动和操作必须写在同一条命令里。cmd //c start chrome.exe --remote-debugging-port9333\--user-data-dirC:\...\profile--no-first-run;\sleep16;\nodescript.jssleep不能省——调试端口要几秒才就绪太早连会失败。另外两个配套细节用--user-data-dir指定固定 profilecookie 才能跨命令保留不用每次重新登录新建标签页要用PUT方法curl -X PUT http://127.0.0.1:9333/json/new?urlencoded url。用 GET 会被 Chrome 直接拒绝报unsafe HTTP verb而且是静默失败——返回的内容不是 JSON解析出来是空很容易误判成建标签失败三、后台标签的输入为什么会被吞掉这个是排查最久的。脚本跑在后台标签文字怎么也输不进去insertText和keyDown都像石沉大海。Page.bringToFront只是把标签切到前台但页面的visibilityState仍然是hidden前端框架React 尤其会直接丢弃输入事件。正解是焦点模拟awaitsend(Emulation.setFocusEmulationEnabled,{enabled:true});而且每个新连接都要重新 enable——这是连接级的状态不是页面级的。新开一个 WebSocket 连 CDP就得再调一次。同类问题的还有按钮点击.click()对 React 按钮经常无效因为 DOM 里存在隐藏副本getBoundingClientRect()全为 0你点中的是那个看不见的。正确做法是先按 rect 过滤出可见元素再用真实鼠标事件点击。四、平台限流的三种形态把踩过的限流整理成表这是排期的基础形态典型平台表现重置时机日历日限CSDN约 2 篇/天、掘金1 篇/天编辑器明确提示今日发文额度已用完自然日 00:00滚动 24 小时知乎点发布无反应toast「近期发布频率过高请24小时后重试~」上次发布时刻 24h无日限51CTO、腾讯云开发者社区、简书随时可发—人工闸门博客园首次开通博客需申请后台提示账号未开通博客审核通过滚动 24 小时那种最坑因为它的提示只闪 1–2 秒。我第一次遇到时连点三次都以为是自己脚本没点中按钮——实际上每次平台都拒绝了只是拒绝信息消失得太快。抓它的方法点击后800 毫秒内截图或者挂一个MutationObserver监听 toast 容器。判据别用按钮文字变没变要用线上文章 URL 是否真的可访问。另有一个反直觉的教训博客园卡了 22 小时在申请审核队列其实是走错了入口。真正能自助开通的是另一个创建页填博客地址名 选皮肤不是那个需要人工审核的申请表。五、时间窗编排把限流当排期资源既然限流客观存在就别硬碰。我把任务排成一条流水线T0 探测额度无日限的平台立刻发 T0 把需要等待的任务挂成一次性定时任务 00:05 日历日限平台额度刚重置 上次24h缓冲 滚动限流平台顺序不能反。日限额度重置后如果先被别的任务占掉当天就没机会了而滚动限流是按上次发布时间算的早一秒都发不出去。关键是不用自己熬夜。把00:05 发 CSDN 和掘金写成一次性定时任务让它在点触发人去睡觉。第二天早上起来看结果就行。排期时留缓冲日限平台写 00:05 而不是 00:00滚动限流写 24h 再加 10 分钟。边界踩点失败的概率很高。六、一份自检清单写完自动化发布链按这个过一遍每个自研脚本结尾都有process.exit(0)有超时兜底且用了.unref()浏览器启动与操作在同一条命令内/json/new用的是 PUT 而不是 GET每个新 CDP 连接都调了setFocusEmulationEnabled点击前用getBoundingClientRect过滤可见元素失败判据是线上 URL 可访问不是按钮文字变了有日限的任务排在额度重置之后滚动限流的任务按上次发布时间 24h 缓冲排七、最后这三个坑有个共同点它们都不报错。僵尸进程不报错它只是不退出浏览器被回收不报错它只是连不上后台标签丢输入不报错它只是没反应。真正耗掉 10 小时的不是 bug 本身是看起来没出错所以一直等下去这个判断。所以现在我的默认假设是如果一条自动化命令超过预期时间还没返回先怀疑进程挂起和浏览器生命周期而不是怀疑平台接口。