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 ...