chromedp 是一个基于 Go 语言开发的 Chrome/Chromium 浏览器自动化工具,通过 DevTools Protocol 实现高效页面控制。

chromedp

chromedp 介绍

chromedp 是一个用于 Chrome 浏览器的自动化测试工具,基于 Go 语言开发,专门用于控制和操作 Chrome 浏览器实例。

对比 Selenium、Puppeteer

工具语言支持浏览器特点
chromedpGo仅 Chrome/ChromiumGo 生态工具,适合高性能并发场景,依赖 Chrome DevTools Protocol (CDP)
Selenium多语言(Python/Java/C#等)多浏览器(Chrome/Firefox/Safari/Edge等)成熟生态,跨浏览器支持,依赖 WebDriver 协议
PuppeteerJavaScript (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 开始)。


扫码关注微信公众号