用 chromedp 做浏览器自动化:几个让流程更稳的实践

这是我的第一篇博客。最近做了一些浏览器自动化的工作,用的是 Go 生态里的 chromedp,踩了不少坑也攒了一些经验,挑几个对"流程稳定性"影响最大的记录下来,希望能帮到同样在做自动化的同学。

一、等页面"真的"就绪,别用固定 sleep

新手最容易写的就是 chromedp.Sleep(3*time.Second) 然后开始操作。问题是网络有快有慢,固定等待要么不够、要么浪费时间。更稳的做法是等待一个明确的就绪信号,再配合少量重试:

err := chromedp.Run(ctx,
    chromedp.WaitReady("body", chromedp.ByQuery),
    chromedp.ActionFunc(func(ctx context.Context) error {
        // 关键数据可能由前端异步拉取,必要时轮询几次
        for i := 0; i < 10; i++ {
            if ok := tryReadState(ctx); ok {
                return nil
            }
            _ = chromedp.Sleep(time.Second).Do(ctx)
        }
        return errors.New("页面状态未就绪")
    }),
)

二、给受控表单注入值,要触发框架的事件

现在的后台大多是 Angular / React 这类框架,输入框的值由框架的数据模型托管。如果你直接 el.value = x,框架的模型不会更新,点"提交"时拿到的还是空值。正确做法是用原生 setter 写值,再派发 input 事件让框架感知:

const proto = el.tagName === 'TEXTAREA'
    ? HTMLTextAreaElement.prototype
    : HTMLInputElement.prototype;
Object.getOwnPropertyDescriptor(proto, 'value').set.call(el, val);
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('blur',  { bubbles: true }));

注入后可以观察元素的 class,框架通常会把 ng-pristine 切换成 ng-dirty ng-valid,这就是模型已经同步的信号。

三、能复用会话直接调接口,就别硬怼 DOM

如果目标站点是个 SPA、背后有干净的接口,与其模拟点来点去,不如在已登录的页面上下文里直接 fetch 调它自己的接口——cookie、CSRF token 都是现成的:

const r = await fetch('/api/user', { credentials: 'include' });
const data = await r.json();

要特别注意同源问题:从 a.example.com 去 fetch('https://b.example.com/...') 属于跨域,可能拿不到数据;优先选同源接口会省很多事。

四、读取响应体注意时机

用 CDP 监听 EventResponseReceived 后立刻 GetResponseBody,有时会报 No data found for resource (-32000)——因为响应体还没在浏览器侧落地,或者页面已经导航走了。稳妥的做法是收到事件后稍等一下再取,并对失败做容错,不要让它中断主流程。

小结

这几条说白了就是:等明确信号而非定时、尊重前端框架的数据流、能用接口就别硬点、读数据注意时机。浏览器自动化本质上是在和"时序"打交道,把这几点处理好,流程的稳定性会有质的提升。

第一篇,写得比较朴素,后面会继续记录一些踩坑与心得。

posted on 2026-06-24 15:32  老王123333  阅读(20)  评论(0)    收藏  举报