用 Playwright 做多平台内容分发,我踩过的 8 个坑

用 Playwright 做多平台内容分发,我踩过的 8 个坑

最近在做一个「把文章自动发布到多个自媒体平台」的桌面工具,技术栈是 Electron + Playwright。 原以为难点在反爬,做完才发现:真正耗时间的是那些「看起来能用、实际静默出错」的细节。 把踩过的 8 个坑记下来,供同样在做 RPA 的同学避坑。

1. 编辑器在 iframe 里,你的选择器「全部失效」

第一次跑头条号,所有元素定位全 MISS,一度以为是反爬。 抓了 DOM 地图才发现:编辑器在 iframe 里,只在主框架找当然找不到。

解决办法是让解析器自动在主框架和子框架之间回退,并把「命中的框架」作为降级信号记录下来。

2. 内容不在 DOM 里(CodeMirror / Monaco)

掘金用的是 ByteMD + CodeMirror 5。它的隐藏 textarea 只是输入代理,正文内容在 CodeMirror 自己的模型里。

这意味着两件事:

  1. 往 textarea 的 value 里塞内容是没用的(编辑器不认)
  2. 校验也不能读 DOM ------ 读 textContent 永远是空的,会把「注入成功」误判成「注入失败」

正确做法是走编辑器自己的 API:向上找到持有 CodeMirror 实例的容器,调用 setValue。

3. 假成功:最危险的故障类型

有一次日志显示「发布成功」,我去平台上一看:根本没发出去。

原因是发布成功的判定复用了「草稿保存成功」的信号,而上一次草稿保存成功的提示还挂在页面上。

教训:成功判据必须高特异性。现在我的判定优先级是:

  1. URL 跳到明确的成功页(如 creation/success/文章ID)
  2. 出现明确的成功文案
  3. 两者都没有 → 如实报「结果未知,不得自动重试」,绝不猜

4. 坐标点击必须先滚动

为了模拟真人,我用贝塞尔曲线算鼠标轨迹,然后用原始坐标点击。 结果:页面下方的控件(分类、标签、封面)点了完全没反应。

原因:boundingBox() 对屏幕外元素返回的是相对页面的坐标,而鼠标点击用的是视口坐标。 加一行 scrollIntoViewIfNeeded() 就好了 ------ 但这一行让我排查了几个小时。

5. 隐藏的 file input 永远等不到 visible

上传图片用的 inputtype=file 几乎都是 0×0 的隐藏元素(样式美化需要)。 而自动化框架默认等元素「可见」,于是永远超时。改用等待「已挂载」(attached)即可。

顺带一提:有些平台的上传控件是点击按钮后才动态创建的,所以还得「先点按钮 → 再找 input」, 而且不能拿第一个 ------ 第一个往往是「上传封面」。

6. 单文件 input 不能一次传多张

setInputFiles 传多张时直接报错:Non-multiple file input can only accept single file。

得逐张传、逐张等(等图片真的回填成平台 CDN 地址再传下一张)。 必须校验回填的 URL 是不是平台域名 ------ 是 blob: 或本地路径就说明没传完,这种文章发出去就是「图裂」。

7. __name is not defined:page.evaluate 的隐形杀手

这个坑最隐蔽。用 tsx 跑 TypeScript 时,esbuild 为了保留函数名,会把 const clean = (s) => s.trim() 编译成 const clean = __name((s) => s.trim(), "clean")。

这个函数被 page.evaluate 序列化送进浏览器时,页面里没有 __name,整个回调直接抛 ReferenceError。 而外层通常写着 catch 兜底,于是表现为「数据莫名取不到」,极难定位。

两个解法:注入一个恒等 shim(window.__name = fn => fn),或者干脆别在 evaluate 里写具名函数表达式。 另外一条经验:evaluate 里的异常一定要带出来,不要静默吞掉。

8. 平台差异比想象的多

举几个真实例子:

| 差异 | 表现 | | --- | --- | | 草稿保存 | CSDN 有「保存草稿」按钮;掘金是自动保存,根本没有按钮 | | 登录入口 | CSDN 的密码登录藏在底部一个图标后面;掘金是明文 tab | | 发布流程 | 一步发布 vs「点发布 → 填分类标签 → 再点确定并发布」 | | 会话 Cookie | 有的平台 uuid_tt_dd 这类设备 ID 未登录也存在,拿它判登录会误判 |

所以「每个平台一个适配器 + 一份外置的选择器配置」是必须的,不能图省事写 if-else。

小结

回头看,这 8 个坑里有 4 个属于同一类:程序说成功,实际没成。

自动化系统里,假成功比失败危险得多:失败会重试,假成功会静默地污染数据、让用户以为事情办好了。 所以我现在给自己定了条规矩:

任何「成功」都必须有高特异性的证据;拿不到证据就如实报「未知」,绝不猜。

如果这篇对你有用,后面我会继续写「选择器热更新」和「审核状态回查」的工程实现。

相关推荐
IT_陈寒2 小时前
为什么你应该学习JavaScript?
前端·人工智能·后端
大龄秃头程序员2 小时前
iOS冷启动监控Demo
前端
光影少年2 小时前
为什么 JavaScript 中 0.1 + 0.2 !== 0.3,如何让其相等?
前端·javascript·算法
掘金酱3 小时前
[稀土掘金 × 火山引擎] AI用量周榜冲刺赛|获奖名单公示
前端·人工智能·后端
泡泡oO3 小时前
“如果你还在用Superpowers,那我不要和你说话”
前端·后端·全栈
维克兜率天3 小时前
趋势过滤:避免被“假突破“反复打脸
前端
8年区块链老兵4 小时前
一篇文章教会你:用区块高度读懂比特币链上的每一笔交易
前端
xixichensh4 小时前
前端转做AI程序员第一步——开发一个AI流式对话Demo
前端
小羊没烦恼!4 小时前
系统内部模块(子系统)之间的耦合以及模块(子系统)划分
java·开发语言·前端·数据库·算法·c#