Go-Rod 库中文详细笔记

1. 简介

Rod 是一个基于 DevTools 协议的高级浏览器自动化库,使用 Go 语言开发。它允许你以编程方式控制浏览器,执行各种操作如页面导航、元素查找、点击、输入文本等。

Rod 的主要特点包括:

  • 链式上下文设计,直观地处理超时或取消长时间运行的任务
  • 自动等待元素准备就绪
  • 调试友好,自动输入跟踪,远程监控无头浏览器
  • 所有操作都是线程安全的
  • 自动查找或下载浏览器
  • 高级辅助函数如 WaitStable、WaitRequestIdle、HijackRequests、WaitDownload 等
  • 两步式 WaitEvent 设计,永不错过事件
  • 正确处理嵌套 iframe 或 Shadow DOM
  • 崩溃后没有僵尸浏览器进程

2. 基本用法

2.1 安装

go get -u github.com/go-rod/rod

2.2 简单示例

以下示例展示了如何使用 Rod 打开 GitHub 网站,搜索 "git",并获取 Git 的描述:

package main

import (
    "fmt"
    "github.com/go-rod/rod"
)

func main() {
    // 创建浏览器实例
    browser := rod.New().MustConnect()
    defer browser.MustClose()

    // 创建页面
    page := browser.MustPage("https://github.com/")

    // 等待页面加载完成
    page.MustWaitLoad()

    // 查找搜索框并输入 "git"
    searchInput := page.MustElement("input[name=q]")
    searchInput.MustInput("git")
    searchInput.MustPress("Enter")

    // 等待搜索结果加载
    page.MustWaitLoad()

    // 获取 Git 描述
    description := page.MustElement(".codesearch-results p").MustText()
    fmt.Println(description)
}

3. 核心概念

3.1 浏览器对象 (Browser)

浏览器对象是与浏览器进程交互的主要接口。

// 创建新的浏览器实例
browser := rod.New()

// 连接到浏览器
browser.Connect()
// 或使用 Must 版本(出错时会 panic)
browser.MustConnect()

// 关闭浏览器
browser.Close()
// 或使用 Must 版本
browser.MustClose()

3.2 页面对象 (Page)

页面对象代表一个浏览器标签页。

// 创建新页面
page := browser.Page("https://example.com")
// 或使用 Must 版本
page := browser.MustPage("https://example.com")

// 导航到 URL
page.Navigate("https://github.com")
// 或使用 Must 版本
page.MustNavigate("https://github.com")

// 等待页面加载
page.WaitLoad()
// 或使用 Must 版本
page.MustWaitLoad()

// 关闭页面
page.Close()
// 或使用 Must 版本
page.MustClose()

3.3 元素对象 (Element)

元素对象代表页面中的 DOM 元素。

// 查找单个元素
element := page.Element("css-selector")
// 或使用 Must 版本
element := page.MustElement("css-selector")

// 查找多个元素
elements := page.Elements("css-selector")
// 或使用 Must 版本
elements := page.MustElements("css-selector")

// 获取元素文本
text := element.Text()
// 或使用 Must 版本
text := element.MustText()

// 点击元素
element.Click()
// 或使用 Must 版本
element.MustClick()

// 输入文本
element.Input("text")
// 或使用 Must 版本
element.MustInput("text")

// 按键
element.Press("Enter")
// 或使用 Must 版本
element.MustPress("Enter")

4. 上下文和超时处理

Rod 使用 Go 的 context 包来处理 IO 阻塞操作的取消,通常是超时。上下文会递归传递给所有子方法。

// 创建带有超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

// 使用上下文
page := browser.Context(ctx).MustPage("https://example.com")

// 使用超时快捷方式
page := browser.Timeout(10 * time.Second).MustPage("https://example.com")

// 使用取消快捷方式
page, cancel := browser.WithCancel().MustPage("https://example.com")
defer cancel()

5. 高级功能

5.1 浏览器自定义配置

使用 launcher 库可以进一步自定义浏览器。通常用于设置浏览器的命令行标志(开关)。

package main

import (
    "github.com/go-rod/rod"
    "github.com/go-rod/rod/lib/launcher"
)

func main() {
    url := launcher.New().
        Headless(false).       // 禁用无头模式
        UserDataDir("path").  // 设置用户数据目录
        ExecutablePath("/path/to/browser"). // 设置浏览器可执行文件路径
        Proxy("127.0.0.1:8080"). // 设置代理
        MustLaunch()

    browser := rod.New().ControlURL(url).MustConnect()
    defer browser.MustClose()

    // 使用浏览器...
}

5.2 元素查询选项自定义

可以自定义用于查询元素的重试/轮询选项。

package main

import (
    "time"

    "github.com/go-rod/rod"
    "github.com/go-rod/rod/lib/proto"
    "github.com/go-rod/rod/lib/utils"
)

func main() {
    browser := rod.New().MustConnect()
    defer browser.MustClose()

    page := browser.MustPage("https://example.com")

    // 自定义元素查询选项
    element := page.Timeout(10 * time.Second).MustElement("#slow-element")

    // 或使用自定义轮询
    element = page.Poll(utils.Poll{
        Interval: 100 * time.Millisecond,
        Timeout:  10 * time.Second,
    }).MustElement("#slow-element")
}

5.3 直接调用 CDP API

当 Rod 没有提供你需要的功能时,可以直接调用 CDP API。

package main

import (
    "github.com/go-rod/rod"
    "github.com/go-rod/rod/lib/proto"
)

func main() {
    browser := rod.New().MustConnect()
    defer browser.MustClose()

    page := browser.MustPage("https://example.com")

    // 直接调用 CDP API
    cookies, err := proto.NetworkGetCookies{}.Call(page)
    // 处理 cookies...
}

5.4 调试模式

Rod 提供了许多调试选项,可以通过 setter 方法或环境变量设置。

package main

import (
    "github.com/go-rod/rod"
    "github.com/go-rod/rod/lib/launcher"
)

func main() {
    // 禁用无头模式以便于调试
    url := launcher.New().Headless(false).MustLaunch()

    // 启用浏览器开发者工具
    browser := rod.New().
        ControlURL(url).
        SlowMotion(1 * time.Second). // 放慢操作以便观察
        MustConnect()

    defer browser.MustClose()

    // 使用浏览器...
}

5.5 事件监听

展示如何监听事件。

package main

import (
    "fmt"

    "github.com/go-rod/rod"
    "github.com/go-rod/rod/lib/proto"
)

func main() {
    browser := rod.New().MustConnect()
    defer browser.MustClose()

    page := browser.MustPage("https://example.com")

    // 监听控制台消息
    go page.EachEvent(func(e *proto.RuntimeConsoleAPICalled) {
        fmt.Println("Console:", e.Args[0].Value)
    })()

    // 监听页面加载事件
    go page.EachEvent(func(e *proto.PageLoadEventFired) {
        fmt.Println("Page loaded!")
    })()

    // 导航到页面
    page.MustNavigate("https://example.com")
    page.MustWaitLoad()

    // 等待一段时间以接收事件
    time.Sleep(5 * time.Second)
}

5.6 请求拦截和修改

展示如何拦截请求并修改请求和响应。

package main

import (
    "github.com/go-rod/rod"
    "github.com/go-rod/rod/lib/proto"
)

func main() {
    browser := rod.New().MustConnect()
    defer browser.MustClose()

    // 创建请求拦截器
    router := browser.HijackRequests()
    defer router.MustStop()

    // 拦截图片请求
    router.MustAdd("*.jpg", func(ctx *rod.Hijack) {
        // 修改请求
        ctx.Request.SetHeader("My-Header", "value")

        // 继续请求
        ctx.MustLoadResponse()

        // 修改响应
        ctx.Response.SetHeader("My-Response-Header", "value")
        ctx.Response.SetBody([]byte("替换的内容"))
    })

    // 启动拦截器
    go router.Run()

    page := browser.MustPage("https://example.com")
    page.MustWaitLoad()
}

5.7 处理多种结果

展示如何处理一个操作的多种结果。例如,当你登录页面时,结果可能是成功或密码错误。

package main

import (
    "fmt"

    "github.com/go-rod/rod"
)

func main() {
    browser := rod.New().MustConnect()
    defer browser.MustClose()

    page := browser.MustPage("https://example.com/login")

    // 填写登录表单
    page.MustElement("#username").MustInput("user")
    page.MustElement("#password").MustInput("password")
    page.MustElement("button[type=submit]").MustClick()

    // 等待可能的结果
    page.Race().Element("#dashboard", func(e *rod.Element) {
        fmt.Println("登录成功!")
    }).Element(".error-message", func(e *rod.Element) {
        fmt.Println("登录失败:", e.MustText())
    }).MustDo()
}

另一个使用 Race 的例子,等待多个可能的选择器之一出现:

// 它会一直重试,直到其中一个选择器找到匹配项
elm := page.Race().
    Element(".success-message").MustHandle(func(e *rod.Element) {
        fmt.Println("操作成功:", e.MustText())
    }).
    Element(".error-message").MustHandle(func(e *rod.Element) {
        fmt.Println("操作失败:", e.MustText())
    }).
    Element(".loading").MustHandle(func(e *rod.Element) {
        fmt.Println("正在加载...")
    })

### 5.8 搜索嵌套 iframe 或 shadow DOM 中的元素

展示如何使用 Search 获取嵌套 iframe 或 shadow DOM 中的元素。Search 方法的工作方式类似于 Chrome DevTools 中的 DOM 搜索功能。

```go
package main

import (
    "github.com/go-rod/rod"
)

func main() {
    browser := rod.New().MustConnect()
    defer browser.MustClose()

    page := browser.MustPage("https://example.com")

    // 搜索嵌套元素
    element, err := page.Search("text=搜索文本").Do()
    if err != nil {
        // 处理错误
    }

    // 使用找到的元素
    element.MustClick()

    // 使用 CSS 选择器搜索
    element, err = page.Search("#deep-button").Do()

    // 搜索带有特定文本的元素
    element, err = page.Search("text=点击我").Do()

    // 搜索带有特定 XPath 的元素
    element, err = page.Search("xpath=//*[@id='deep-button']").Do()

    // 限制搜索范围到特定元素
    container := page.MustElement("#container")
    element, err = container.Search("text=搜索文本").Do()
}
```

Search 方法的优势在于它可以穿透 Shadow DOM 和 iframe,这在处理现代 Web 应用时非常有用。它支持以下搜索语法:

- CSS 选择器:`#id`、`.class` 等
- 文本内容:`text=确切文本` 或 `text*=包含文本`
- XPath:`xpath=//div[@id='example']`

5.9 等待元素稳定

Rod 使用鼠标光标模拟点击,因此如果按钮因动画而移动,点击可能无法按预期工作。通常使用 WaitStable 确保目标不再变化。

package main

import (
    "time"

    "github.com/go-rod/rod"
)

func main() {
    browser := rod.New().MustConnect()
    defer browser.MustClose()

    page := browser.MustPage("https://example.com")

    // 等待元素稳定后再点击
    button := page.MustElement("#animated-button")
    button.MustWaitStable().MustClick()

    // 或指定自定义选项
    button.WaitStable(5*time.Second, 0.1).MustClick()
}

5.10 等待 AJAX 请求完成

当你想等待 AJAX 请求完成时,这个示例会很有用。

package main

import (
    "github.com/go-rod/rod"
    "github.com/go-rod/rod/lib/proto"
)

func main() {
    browser := rod.New().MustConnect()
    defer browser.MustClose()

    page := browser.MustPage("https://example.com")

    // 启用网络域
    wait := page.MustWaitRequestIdle()

    // 触发 AJAX 请求的操作
    page.MustElement("#load-data-button").MustClick()

    // 等待请求完成
    wait()

    // 现在可以访问加载的数据
    data := page.MustElement("#data-container").MustText()
}

5.11 高级键盘和鼠标操作

除了基本的点击和输入操作外,Rod 还提供了更高级的键盘和鼠标控制功能。

package main

import (
    "github.com/go-rod/rod"
    "github.com/go-rod/rod/lib/input"
    "github.com/go-rod/rod/lib/proto"
)

func main() {
    browser := rod.New().MustConnect()
    defer browser.MustClose()

    page := browser.MustPage("https://example.com")

    // 键盘组合键操作
    page.MustElement("#editor").MustInput("文本").MustPress(input.ControlOrMeta + "a")
    // 按下 Ctrl+C 复制
    page.Keyboard.MustPress(input.ControlOrMeta + "c")

    // 鼠标精确移动
    el := page.MustElement("#canvas")
    // 移动到元素中心
    page.Mouse.MustMove(el.MustShape().OnePointInside())
    // 按下鼠标左键
    page.Mouse.MustDown("left")
    // 移动鼠标到指定坐标
    page.Mouse.MustMoveTo(proto.Point{X: 100, Y: 200})
    // 释放鼠标左键
    page.Mouse.MustUp("left")

    // 鼠标拖拽操作
    source := page.MustElement("#draggable")
    target := page.MustElement("#droppable")
    // 拖拽元素
    source.MustDrag(target)

    // 鼠标滚轮操作
    page.Mouse.MustScroll(0, 100) // 向下滚动
}

5.12 环境变量配置

Rod 支持通过环境变量进行配置,这在不同环境中非常有用:

package main

import (
    "os"
    "github.com/go-rod/rod"
    "github.com/go-rod/rod/lib/launcher"
)

func main() {
    // 设置环境变量
    os.Setenv("ROD_SHOW_BROWSER", "true") // 等同于 Headless(false)
    os.Setenv("ROD_SLOW", "1s")         // 等同于 SlowMotion(1 * time.Second)
    os.Setenv("ROD_MONITOR", "127.0.0.1:9222") // 启用监控服务器

    // 使用默认配置创建浏览器
    browser := rod.New().MustConnect()
    defer browser.MustClose()

    // 使用浏览器...
}

常用的环境变量包括:

  • ROD_SHOW_BROWSER - 设置为 true 显示浏览器界面
  • ROD_SLOW - 设置慢动作模式的时间间隔
  • ROD_MONITOR - 启用监控服务器的地址
  • ROD_USER_DATA_DIR - 设置用户数据目录
  • ROD_PROXY - 设置代理服务器
  • ROD_DISABLE_LEAKLESS - 禁用泄漏检测
  • ROD_DEVTOOLS - 设置为 true 启用开发者工具

5.13 浏览器指纹模拟

Rod 可以用来模拟不同的浏览器指纹,这在自动化测试或网络爬虫中非常有用:

package main

import (
    "github.com/go-rod/rod"
    "github.com/go-rod/rod/lib/launcher"
    "github.com/go-rod/rod/lib/proto"
)

func main() {
    // 创建自定义启动器
    l := launcher.New().
        UserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1"). // 设置 iPhone 用户代理
        Languages("zh-CN,zh"). // 设置语言
        Platform("iPhone"). // 设置平台
        MustLaunch()

    browser := rod.New().ControlURL(l).MustConnect()
    defer browser.MustClose()

    page := browser.MustPage("https://example.com")

    // 设置设备指标模拟 iPhone X
    page.MustEmulate(proto.EmulationSetDeviceMetricsOverride{
        Width:             375,
        Height:            812,
        DeviceScaleFactor: 3,
        Mobile:            true,
    })

    // 模拟地理位置
    page.MustEval(`() => {
        navigator.geolocation.getCurrentPosition = function(cb) {
            cb({
                coords: {
                    latitude: 40.7128,
                    longitude: -74.0060
                }
            })
        }
    }`)

    // 使用页面...
}

5.14 并发控制

Rod 的所有操作都是线程安全的,可以轻松实现并发控制:

package main

import (
    "sync"
    "github.com/go-rod/rod"
)

func main() {
    browser := rod.New().MustConnect()
    defer browser.MustClose()

    // 获取所有打开的页面
    pages, err := browser.Pages()
    if err != nil {
        panic(err)
    }

    // 并发处理多个页面
    var wg sync.WaitGroup
    for _, page := range pages {
        wg.Add(1)
        go func(p *rod.Page) {
            defer wg.Done()
            // 处理页面
            p.MustNavigate("https://example.com")
            p.MustWaitLoad()
            // 其他操作...
        }(page)
    }

    // 等待所有页面处理完成
    wg.Wait()

    // 或者使用工作池模式处理大量URL
    urls := []string{"https://example1.com", "https://example2.com", /* ... */}
    workers := 5 // 同时运行5个工作线程

    jobs := make(chan string, len(urls))
    for _, url := range urls {
        jobs <- url
    }
    close(jobs)

    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for url := range jobs {
                page := browser.MustPage(url)
                page.MustWaitLoad()
                // 处理页面...
                page.MustClose()
            }
        }()
    }

    wg.Wait()
}

5.15 自定义 CDP 客户端

对于高级用户,Rod 允许自定义 CDP(Chrome DevTools Protocol)客户端:

package main

import (
    "github.com/go-rod/rod"
    "github.com/go-rod/rod/lib/cdp"
    "github.com/go-rod/rod/lib/launcher"
)

func main() {
    // 自定义 CDP 客户端选项
    cdpClient := cdp.New(launcher.MustLaunch()).Logger(rod.DefaultLogger)

    // 使用自定义客户端创建浏览器
    browser := rod.New().Client(cdpClient).MustConnect()
    defer browser.MustClose()

    // 使用浏览器...
}

5.16 性能优化技巧

在使用 Rod 进行大规模自动化时,性能优化非常重要:

package main

import (
    "github.com/go-rod/rod"
    "github.com/go-rod/rod/lib/launcher"
    "github.com/go-rod/rod/lib/proto"
)

func main() {
    // 1. 禁用不必要的浏览器功能
    url := launcher.New().
        Headless(true).
        DisableDefaultArgs().
        Args(
            "--disable-web-security",
            "--disable-setuid-sandbox",
            "--no-sandbox",
            "--disable-dev-shm-usage",
            "--disable-accelerated-2d-canvas",
            "--disable-gpu",
            "--window-size=1920,1080",
        ).
        MustLaunch()

    browser := rod.New().ControlURL(url).MustConnect()
    defer browser.MustClose()

    // 2. 禁用图片加载以提高速度
    router := browser.HijackRequests()
    router.MustAdd("*.(jpg|jpeg|png|webp|svg|gif)", func(ctx *rod.Hijack) {
        ctx.Response.Fail(proto.NetworkErrorReasonBlockedByClient)
    })
    go router.Run()

    // 3. 重用浏览器实例
    for i := 0; i < 10; i++ {
        page := browser.MustPage("https://example.com")
        // 处理页面...
        page.MustClose() // 关闭页面但保持浏览器运行
    }

    // 4. 使用 MustElementR 减少等待时间
    page := browser.MustPage("https://example.com")
    // 使用正则表达式匹配文本内容
    element := page.MustElementR("button", "提交|确认")
    element.MustClick()

    // 5. 使用 page.Race() 处理不确定性
    page.Race().Element("#success", func(e *rod.Element) {
        // 处理成功情况
    }).Element("#error", func(e *rod.Element) {
        // 处理错误情况
    }).MustDo()
}

5.17 实际应用场景

以下是一些 Rod 的实际应用场景示例:

网站监控和截图

package main

import (
    "fmt"
    "time"
    "github.com/go-rod/rod"
)

func main() {
    browser := rod.New().MustConnect()
    defer browser.MustClose()

    // 监控网站并截图
    page := browser.MustPage("https://example.com")
    page.MustWaitLoad()

    // 截取全页面截图
    screenshot, err := page.Screenshot(true)
    if err != nil {
        panic(err)
    }

    // 保存截图
    filename := fmt.Sprintf("screenshot_%s.png", time.Now().Format("20060102_150405"))
    err = os.WriteFile(filename, screenshot, 0644)
    if err != nil {
        panic(err)
    }

    // 检查页面是否包含特定元素
    hasError, err := page.Has(".error-message")
    if err != nil {
        panic(err)
    }

    if hasError {
        fmt.Println("网站出现错误!")
        // 发送告警...
    }
}

自动化表单填写和提交

package main

import (
    "github.com/go-rod/rod"
)

func main() {
    browser := rod.New().MustConnect()
    defer browser.MustClose()

    page := browser.MustPage("https://example.com/form")
    page.MustWaitLoad()

    // 填写表单
    page.MustElement("#name").MustInput("张三")
    page.MustElement("#email").MustInput("[email protected]")

    // 选择下拉菜单
    page.MustElement("select[name=country]").MustSelect("中国")

    // 勾选复选框
    page.MustElement("input[type=checkbox][name=agree]").MustClick()

    // 选择单选按钮
    page.MustElement("input[type=radio][value=male]").MustClick()

    // 上传文件
    page.MustElement("input[type=file]").MustSetFiles("/path/to/file.pdf")

    // 提交表单
    page.MustElement("button[type=submit]").MustClick()

    // 等待提交完成
    page.MustWaitLoad()

    // 验证提交结果
    successMessage := page.MustElement(".success-message").MustText()
    fmt.Println("提交结果:", successMessage)
}

// ... existing code ...

每日更新-免费小火箭账号
不要错过任何机会,探索最新的应用和游戏,就在我们的平台。
立即访问
最后修改:2025 年 04 月 27 日
如果觉得我的文章对你有用,请随意赞赏