用接口而不是 DOM 做浏览器自动化:一次真实的重写记录

举报
yd_224601470 发表于 2026/11/07 21:40:49 2026/11/07
【摘要】 浏览器自动化脚本容易坏,坏在绑定了会变的东西。本文用一个真实的 Chrome 扩展任务模块重写案例,讲怎么从「点 DOM 按钮」换成「打页面内 JSON 接口」,以及踩到的几个反直觉的参数坑。

问题:脚本为什么总是坏

我给浏览器做了一套任务模块,最初所有操作都是「找元素、点按钮」:

document.querySelector('.task-center-view .cardItem')
  .querySelector('button').click()

能用。但页面一升级就断——现在是 data-v-08ee82f2 这种编译期哈希,类名全变。

更麻烦的是拿不到数据。页面上只显示「已完成」「+30 积分」,任务周期、跳转地址、完成判据全在 DOM 里没有。想看「这个任务是每日还是每周」,只能靠标题里有没有「每日」两个字猜。

方向:接口比 DOM 稳

打开开发者工具看请求,页面自己调的是这么一组接口:

GET  .../growth/bonus-beans
GET  .../growth/sign
POST .../growth/sign                 {account_name}
POST .../growth/mission/query-mission-list-by-condition
POST .../growth/prize

关键发现:query-mission-list-by-condition 传 category_key: "DAILY_MISSIONS",返回的却是全部任务,不只是每日的。每项都带 rule_list[].mission_cycle_type(DAILY / WEEKLY)、is_finish、url、guide。

这比在 DOM 里凑信息强一个量级。原来是「标题里有每日就算每日」,现在是接口直接告诉你。

坑一:CSRF 会过期

写操作要一个请求头 csrf。我第一版从 cookie 里读 csrf-eco:

const csrf = document.cookie
  .split(';')
  .map(c => c.trim())
  .find(c => c.startsWith('csrf-eco='))
  .split('=').slice(1).join('=');

第一次跑通了。过了一会儿再跑,全部 403 CSRF unequal。

重开页面也不管用。查下来:cookie 里那份是旧的,新值要从 GET /api/get-ainfo 的响应头里取。

const res = await fetch('/api/get-ainfo');
const csrf = res.headers.get('csrf');

这个坑很阴:第一次能跑通,所以不会怀疑它;报错信息(CSRF unequal)指向「值不对」,真正的原因是「值过期」。

坑二:字段名报错和实际不一致

签到接口要一个账号名。我先按常见的驼峰写法:

{ accountName: 'xxx' }

返回:

400 parameter is error; accountName不能为空

报错说 accountName 不能为空,那就再传一遍 accountName——还是同样的错。

实际字段是 account_name,下划线。报错文案写的是驼峰,接口要的是下划线。照着报错改会一直绕不出来。

教训:报错文案不能当接口契约。抓一次真实的请求体比猜十次有用。

坑三:页面卡死,加超时没用

页面开久了之后,读状态从 1 秒变成 30 秒超时。不是某个接口慢,是fetch整体不响应。

第一反应是把超时从 30 秒调到 60 秒。没用,因为它不是慢,是不回。

真正有效的是重新导航一次:

try {
  return await readState();
} catch {
  await navigate(url);
  await sleep(9000);
  return await readState();
}

边界:哪些动作不该自动化

这套东西我划了两条线。

一、对外不可撤销的动作。比如给开源仓库点 Star。技术上能做,但点错了撤不回,而且这类动作往往还绑着另一套登录态。脚本改成先探登录态,能用就打开页面,不能用就如实报 auth-expired——而不是硬点然后猜结果。

二、需要人做判断的动作。比如「花积分兑换代金券」。弹窗里有实名认证要求、每日/每年封顶、违约条款。把这些替人点掉,等于替人同意条款。

所以脚本只做「算」:多少分、能换什么、每积分值多少钱、还差多少。至于换不换,留给人。

写法上的几条经验

  1. 默认只读。不带 --apply 就只采集,不写。误操作的成本远高于多打一个参数。
  2. 失败要有名字。auth-expired / no-csrf / cannot-reply 这种 reason,比一个 ok: false 有用得多。
  3. 「0 条」是结论,不是失败。搜到 0 个结果、可抄的评论不足两条——这些是有效信息,不该和「请求挂了」混在一个布尔里。
  4. 别把超时时间当解药。慢和卡是两回事,后者要重连。

结果

重写之后,读一次状态从「点开页面、展开任务、逐个解析卡片」变成一次接口调用,拿到 14 项任务的完整信息(周期、积分、完成状态、跳转地址)。原来只能猜的东西现在都是字段。

脚本本身也短了:不依赖任何 class 名,页面改版不用动。

小结

浏览器自动化最容易犯的错,是把「会变的东西」当成契约。DOM 结构会变,class 名会变,报错文案和实际参数甚至可能不一致。相对稳的是接口——它有明确的入参出参,有错误码,能抓、能复现。

换成接口之后,脚本从一个「点击器」变成了一个「客户端」。这两者的维护成本差得很远。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。