使用 chromedp 操作 chrome
承蒙大家厚爱,我的《Go语言之路》的纸质版图书已经上架京东,有需要的朋友请点击 此链接 购买。
chromedp 是一个基于 Go 语言开发的 Chrome/Chromium 浏览器自动化工具,通过 DevTools Protocol 实现高效页面控制。
chromedp
chromedp 介绍
chromedp 是一个用于 Chrome 浏览器的自动化测试工具,基于 Go 语言开发,专门用于控制和操作 Chrome 浏览器实例。
对比 Selenium、Puppeteer
工具 | 语言 | 支持浏览器 | 特点 |
---|---|---|---|
chromedp | Go | 仅 Chrome/Chromium | Go 生态工具,适合高性能并发场景,依赖 Chrome DevTools Protocol (CDP) |
Selenium | 多语言(Python/Java/C#等) | 多浏览器(Chrome/Firefox/Safari/Edge等) | 成熟生态,跨浏览器支持,依赖 WebDriver 协议 |
Puppeteer | JavaScript (Node.js) | 仅 Chrome/Chromium | 由 Chrome 团队开发,深度集成 CDP,提供高级 API(如截图、PDF 生成等) |
CDP 协议
CDP 协议 全称是 Chrome DevTools Protocol,允许工具对基于 Chromium、Chrome 和其他 Blink 引擎的浏览器进行检测、审查、调试和分析。目前许多现有项目都在使用该协议。Chrome 开发者工具(DevTools)本身也基于该协议构建,相关API由Chrome团队负责维护。我们在 Chrome 浏览器中 F12 打开的就是开发者工具,它就是通过 CDP 协议与浏览器内核进行通讯。
WebDriver 协议
WebDriver 协议是一套开源的 跨浏览器自动化协议,定义了客户端(如 Selenium)与浏览器驱动(如 ChromeDriver)之间的通信规范。其核心目标是实现"同一套代码控制不同浏览器"(如 Chrome、Firefox、Safari)。
ChromeDriver 是 Google 官方开发的浏览器驱动工具,专门用于控制 Chrome/Chromium 内核浏览器(如 Chrome、Edge、Opera)。ChromeDriver作为协议的实现者,将 WebDriver 指令翻译为 CDP 命令与浏览器交互。
如果我们使用 Selenium
操作 Chrome 则需要安装 ChromeDriver
,如果使用 chromedp 这种原生支持 CDP 协议的工具则不需要安装 ChromeDriver
。
chromedp 使用
安装
在项目目录下执行以下命令安装依赖。
go get -u github.com/chromedp/chromedp
引入依赖
import "github.com/chromedp/chromedp"
初始化配置
chromedp 是一个强大的 Go 语言库,用于控制 headless Chrome 或完整浏览器实例。它提供简洁的 API 实现页面导航、元素操作、截图、PDF 生成等常见浏览器自动化任务。
使用默认的浏览器打开 tab
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
连接本地浏览器
// 指定 Chrome 路径
opts := append(chromedp.DefaultExecAllocatorOptions[:],
//chromedp.ExecPath("C:/Program Files/Google/Chrome/Application/chrome.exe"), // Windows
chromedp.ExecPath("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"), // MacOS
chromedp.Flag("headless", false),
)
allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancel()
ctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()
连接远程 Chrome
allocatorContext, _ := chromedp.NewRemoteAllocator(context.Background(), "ws://10.20.30.1:9222/")
defer cancel()
常用方法
页面导航
chromedp.Run(ctx,
chromedp.Navigate("https://www.example.com"),
)
设备模拟
chromedp.Run(ctx,
chromedp.Emulate(device.IPhone15ProMax),
chromedp.Navigate("https://m.example.com"),
)
执行 JavaScript
// 同步执行示例
var title string
chromedp.Run(ctx,
chromedp.Evaluate(`document.title`, &title),
)
// 异步 Promise 处理
chromedp.Run(ctx,
chromedp.Evaluate(
`someReturnPromiseFunc()`,
&result,
func(p *runtime.EvaluateParams) *runtime.EvaluateParams {
return p.WithAwaitPromise(true) // 等待 Promise 完成
}),
)
元素操作
// 获取元素的 text
var res string
chromedp.Run(ctx,
chromedp.Navigate(`https://pkg.go.dev/time`),
chromedp.Text(`.Documentation-overview`, &res, chromedp.NodeVisible),
)
click
// 打开一个页面, 等待一个元素渲染完成, 点击
var example string
chromedp.Run(ctx,
chromedp.Navigate(`https://pkg.go.dev/time`),
// 等待页面的 footer 元素可见 (即,页面已经加载完成后)
chromedp.WaitVisible(`body > footer`),
// 找到并点击页面上的 "Example" 连接
chromedp.Click(`#example-After`, chromedp.NodeVisible),
// 获取 textarea 区域的文本
chromedp.Value(`#example-After textarea`, &example),
)
使用示例
Submit
func doGithubSearch() {
// create context
ctx, cancel := chromedp.NewContext(context.Background(), chromedp.WithDebugf(log.Printf))
defer cancel()
// run task list
var res string
err := chromedp.Run(ctx, submit(`https://github.com/search`, `//input[@name="q"]`, `chromedp`, &res))
if err != nil {
log.Fatal(err)
}
log.Printf("got: `%s`", strings.TrimSpace(res))
}
func submit(urlStr, sel, q string, res *string) chromedp.Tasks {
return chromedp.Tasks{
chromedp.Navigate(urlStr),
chromedp.WaitVisible(sel),
chromedp.SendKeys(sel, q),
chromedp.Submit(sel),
chromedp.WaitVisible(`//*[contains(., 'repository results')]`),
chromedp.Text(`(//*//ul[contains(@class, "repo-list")]/li[1]//p)[1]`, res),
}
}
URL 截图
输入一个 URL,通过 chromedp 打开页面并进行截图。
package main
import (
"context"
"log"
"os"
"github.com/chromedp/chromedp"
)
func main() {
// 新建 context
ctx, cancel := chromedp.NewContext(
context.Background(),
chromedp.WithDebugf(log.Printf), // 打印 debug 日志
)
defer cancel()
// 捕获网页的一个元素进行屏幕截图
var buf []byte
if err := chromedp.Run(ctx, elementScreenshot(`https://pkg.go.dev/`, `img.Homepage-logo`, &buf)); err != nil {
log.Fatal(err)
}
if err := os.WriteFile("elementScreenshot.png", buf, 0o644); err != nil {
log.Fatal(err)
}
// 捕获整个浏览器视口,返回质量为 90 的 png
if err := chromedp.Run(ctx, fullScreenshot(`https://brank.as/`, 90, &buf)); err != nil {
log.Fatal(err)
}
if err := os.WriteFile("fullScreenshot.png", buf, 0o644); err != nil {
log.Fatal(err)
}
log.Printf("wrote elementScreenshot.png and fullScreenshot.png")
}
// elementScreenshot 截取特定元素的屏幕截图。
func elementScreenshot(urlStr, sel string, res *[]byte) chromedp.Tasks {
return chromedp.Tasks{
chromedp.Navigate(urlStr),
chromedp.Screenshot(sel, res, chromedp.NodeVisible),
}
}
// fullScreenshot 会截取整个浏览器视口的屏幕截图。
//
// 注意: chromedp.FullScreenshot 会覆盖设备的emulation 设置。 Use
// 使用 device.Reset 重置 emulation 和 viewport 设置。
func fullScreenshot(urlstr string, quality int, res *[]byte) chromedp.Tasks {
return chromedp.Tasks{
chromedp.Navigate(urlstr),
chromedp.FullScreenshot(res, quality),
}
}
URL 转 pdf
输入一个 URL,通过 chromedp 打开页面并转存为 PDF。
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/chromedp/cdproto/page"
"github.com/chromedp/chromedp"
)
func main() {
// 新建 context
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
// 抓取屏幕为 PDF
var buf []byte
if err := chromedp.Run(ctx, printToPDF(`https://www.baidu.com/`, &buf)); err != nil {
log.Fatal(err)
}
// 保存为本地的 sample.pdf 文件
if err := os.WriteFile("sample.pdf", buf, 0o644); err != nil {
log.Fatal(err)
}
fmt.Println("wrote sample.pdf")
}
// 打印特定的 pdf 页面.
func printToPDF(urlStr string, res *[]byte) chromedp.Tasks {
return chromedp.Tasks{
chromedp.Navigate(urlStr),
chromedp.ActionFunc(func(ctx context.Context) error {
buf, _, err := page.PrintToPDF().WithPrintBackground(false).Do(ctx)
if err != nil {
return err
}
*res = buf
return nil
}),
}
}
chromedp 支持诸如页面点击、表单提交等很多浏览器操作,更多官方使用示例,详见 examples。
chrome headless 模式
Chrome Headless 模式是 Chrome 浏览器的无界面形态,可以在不打开浏览器的前提下使用所有 Chrome 支持的特性运行程序。可以像在其他现代浏览器里一样渲染目标网页,并能进行获取 HTML 内容、获取cookie、网页截图等操作。Chrome Headless 模式特别适合部署在服务器上运行,可以用来跑自动化测试或者部署类似截图或转 PDF 的服务。
根据 Chrome 官方博客的说明,从 Chrome 112 开始提供全新的无头模式(-headless=new
),旧版无头实现现已作为独立的 chrome-headless-shell
二进制文件提供。这些新的 chrome-headless-shell
二进制文件是针对每个面向用户的 Chrome 版本生成的,可供通过 Chrome for Testing Infrastructure 下载(从 Chrome 120 开始)。