把「一文多发」做成脚本流水线:CDP、滑块、二维码、WAF、限流,这些坑我替你踩完了
参与话题:CSDN+AI:程序员新生产力
环境:Windows 11 + Chrome + CDP(remote-debugging-port)+ Node.js。全自动不现实、纯手动又太低效,本篇讲的就是那条「半自动流水线」怎么搭起来、坑都在哪。
一、先交代背景:为什么要做这件事
我在县级生态环境部门做行政处罚办案。今年 9 月起写了一个七篇的系列稿(《生态环境法典》1242 条的知识库化 + 执法文书 AI 实战),要在 6 个平台同步发布:CSDN、掘金、思否、51CTO、知乎、博客园。
手动发一遍是什么体验?打开编辑器、贴正文、等图片转存、填标签、传封面、写摘要、点发布、扫码确认——一个平台 20 分钟,六个平台两小时起步,而且每一步都可能掉链子:滑块验证码、登录态过期、图片外链被防盗链掐死、发布按钮点不动。
所以我把整条流水线脚本化了。现在是:正文 md 在本地,一条命令从打开编辑器到出二维码,人只需要拿手机扫一次码。单平台操作时间从 20 分钟压到 3 分钟以内。
这篇不讲「怎么用某个自动化框架」,讲的是这一路上真实踩过的坑和对应的解法。全部代码片段都可以直接搬走用。
二、架构:为什么是 CDP 而不是 Playwright
第一版用的是 Playwright,两天就放弃了。原因很朴素:
- 各平台的编辑器全是重前端(CodeMirror、Draft.js、Tiptap),Playwright 的
fill()对它们经常不生效,最后都得绕回evaluate里拿真实对象操作; - 风控对自动化浏览器的识别很敏感,Playwright 的部分指纹过不了滑块;
- 我需要复用日常浏览器的登录态——每天开 WorkBuddy、微信、各种后台都在同一个 Chrome profile 里,重新登录一遍的成本比写代码还高。
最终方案:手动开一个带调试端口的 Chrome,脚本通过 CDP(Chrome DevTools Protocol) WebSocket 连上去操作。
# 启动带调试端口的 Chrome(9333)start chrome.exe --remote-debugging-port=9333--user-data-dir="D:/cdp-profile"--no-first-run// 最小连接骨架:列出标签页 → 按 URL 选中目标 → 双向 WebSocketconstlist=await(awaitfetch('http://127.0.0.1:9333/json/list')).json();consttab=list.find(t=>t.type==='page'&&/editor\.csdn\.net/.test(t.url));constws=newWebSocket(tab.webSocketDebuggerUrl);constsend=(method,params)=>{/* id 配对 + promise 封装,略 */};constev=js=>send('Runtime.evaluate',{expression:js,returnByValue:true});这套骨架 40 行,后面所有平台通用。关键心法:能Runtime.evaluate拿到页面真实对象(如 CodeMirror 实例)就别用键盘鼠标模拟;只有逼不得已才派发输入事件。
三、坑一:Chrome 会「死」,而且死得很安静
第一个坑最阴:脚本跑一半 Chrome 没了。
原因是我用child_process.spawn拉起的 Chrome,父进程(Node 脚本)一退出,Chrome 跟着被系统回收。表现是上一条命令里还活着的 Chrome,下一条命令连 9333 就 connection refused,而且没有任何报错指向这个原因。
解法很蠢但有效:启动 Chrome 和操作脚本必须塞进同一个 shell 命令里,用&&串死:
start chrome.exe --remote-debugging-port=9333...;sleep12;nodemy-task.js配套的还有两个小坑:
- 探测 CDP 活着没,curl 必须加
--noproxy '*'。我机器上有http_proxy=127.0.0.1:64925,不加这个参数,curl 会把127.0.0.1:9333的请求也丢给代理,代理自然 refused——你会误判成「Chrome 挂了」,然后白白重启一遍。 - 计划任务拉不起带 GUI 的 Chrome。
schtasks、WMIWin32_Process.Create、PowerShellStart-Process我全试过,不是被策略拦就是同样被回收。最后老老实实用「一条命令串到底」。
四、坑二:验证码滑块,CDP 拖必死,原生拖秒过
滑块验证码是所有自动化绕不开的墙。我试过 CDP 的Input.dispatchMouseEvent模拟拖动,轨迹畸长(拖 320px 用了 25 秒+)、速度均匀得像机器人,风控一秒识破。
解法:放弃 CDP 模拟,用 Pythonctypes直接调系统的SendInput,以真实硬件级鼠标事件拖动:
importctypes,time,math user32=ctypes.windll.user32 user32.SetProcessDPIAware()# 高分屏必加!否则坐标错位一半defdrag(x1,y1,x2,y2,steps=28):user32.SetCursorPos(int(x1),int(y1))time.sleep(0.25)user32.mouse_event(2,0,0,0,0)# left downforiinrange(1,steps+1):t=i/steps# 先快后慢 + 轻微过冲回拉,模拟人手ease=1-(1-t)**2.2jitter=math.sin(t*9)*1.5user32.SetCursorPos(int(x1+(x2-x1)*ease+jitter),int(y1+(y2-y1)*ease+jitter))time.sleep(0.012)time.sleep(0.18)user32.mouse_event(4,0,0,0,0)# left up要点三条:DPI 感知(4K 屏 1.5 倍缩放,不SetProcessDPIAware坐标直接错一半);轨迹先快后慢(ease-out,人手就是这样);轻微抖动(纯正弦会太规律,叠一点随机更稳)。改造后拖 320px 用时 0.4 秒,阿里云、字节滑块全部一次过。
五、坑三:二维码只有 5 分钟命,人不在就没救
CSDN/掘金发布要微信扫码确认,二维码寿命普遍 5 分钟。人不在电脑前,出码就是浪费。
两个实战解法:
- 出码时机后置:把「出码」放到流程最后一刻——正文、标签、封面全部就位,发布按钮都点完了,才抓二维码。脚本同时轮询登录态,扫上就自动继续,超时(8–13 分钟)自动放弃,下一轮重来。
- 人不在电脑前时的杀手锏:把二维码图片 base64 内嵌成一个静态 HTML,部署成公网页面,把链接发给自己——手机微信里打开、长按识别、确认登录,全程不需要碰电脑。这招真的成功过,能把「人必须在电脑前」这个约束彻底解掉。
六、坑四:状态判断,截图和全文正则都会骗你
自动化最怕的是「不知道操作成没成功」。我踩过三种判状态的坑:
坑 A:文章页没横幅 ≠ 没被拒。思否(SegmentFault)审核被拒后,文章页干干净净,但通知中心躺着「发表未成功」的记录。判断必须去通知中心数条目。
坑 B:全文正则数条目会虚高。最初我用「全文找『发表未成功』关键词 + 前后 90 字符窗口」来数被拒次数,结果窗口重叠把 2 条数成 4 条。正确做法是按 DOM 条目统计:
// 每条通知是一个独立 DOM 节点,数节点不数关键词constn=document.querySelectorAll('.stream-list-item, li.media, .list-group-item').length;坑 C:懒加载图片假裂。掘金文章页img.naturalWidth === 0不代表图裂了,可能只是还没滚到。验收图片修复要看src属性匹配(是否仍是会防盗链的外链域名),别看渲染结果。
七、坑五:限流规则,读不出来只能用血换
每个平台的写操作限额都不写在明面上,只能实测:
- 掘金:「发布 + 更新」合并计数,撞
err_no:1012就是当日额度用尽,重试只会更糟。实测一天 3 次写操作(发+改+删)没触发,但保守按 1 篇/天排期。 - 思否:同一篇文章累计 3 次审核不通过自动删除——而且是按篇计数,不是按账号。所以改稿重提要慎重,最后一发留给最有把握的版本;真被删了,重新发一篇就是全新额度。
- CSDN:每天限 2 篇,一天 1 篇最稳;二维码寿命约 5 分钟,发布面板要点两次(外层「发布文章」只打开面板)。
限流的应对策略只有一条:把「重试」从代码里删掉。失败就留给下一个定时轮次,绝不当场硬刚——硬刚一次,可能赔掉整个账号。
八、坑六:依赖会「自己升级」
Node 由客户端托管时会被静默升级(22.22.2-3→22.22.2-6),所有把版本号写死在配置里的定时任务一夜全哑。解法是在每个脚本入口加自愈:
NODE=$(ls-d/path/to/node/versions/*/node.exe2>/dev/null|tail-1)同类问题:任务有validUntil,过期会静默停跑不报错——定期巡检必须包含「任务还活着吗」这一项。
九、收益清单
| 项目 | 脚本化前 | 脚本化后 |
|---|---|---|
| 单平台发布耗时 | ~20 min | ❤️ min(人工只剩扫码) |
| 六平台全量同步 | 半天 | 2 天内分批完成,零遗漏 |
| 滑块验证码通过率 | ~0%(CDP 拖动) | ~100%(原生拖动) |
| 状态误判导致的重复提交 | 频繁 | 0(DOM 断言 + 幂等设计) |
阅读量顺带交代:六平台合计外部阅读约 2,000,其中 CSDN 占绝对大头(自然搜索流量),掘金/51CTO/知乎基本零分发——数据也反过来证明:该精耕的平台和该放弃的平台,发布完一周就能看出来。
十、写在最后
这套流水线没有用到任何黑科技:CDP、ctypes、一个 WebSocket 骨架、十几个平台的专用脚本。它的价值在于把「人肉流程」变成「人只出现在必须出现的地方」——目前人只剩两个动作:拿起手机扫码,和决定今天发哪篇。
下一步想把「发哪篇」也交给调度器:按各平台限流额度 + 队列状态自动排期,失败自动顺延。等跑稳了再来写下半篇。
(完)
环境备注:Windows 11 / Chrome 155 / Node 22.x / Python 3.13。文中所有脚本均为本地自用,如需完整版可以评论区留言。