golang实现Chrome调试协议类型安全绑定的插件库cdp的使用

Golang实现Chrome调试协议类型安全绑定的插件库cdp的使用

简介

cdp包为Chrome DevTools协议(CDP)提供了类型安全的Go语言绑定。这些绑定是从最新的协议定义生成的,主要用于Google Chrome或Chromium,但也可以用于任何实现该协议的调试目标(如Node.js、Edge DevTools协议、Safari等)。

特性

  • 可发现的Chrome DevTools协议API(GoDoc,自动完成友好)
  • 上下文作为一等公民(用于超时和取消)
  • 简单同步的事件处理(无回调)
  • 并发安全
  • 无静默或隐藏错误
  • 尽可能匹配CDP类型到Go类型
  • 关注点分离(避免混合CDP和RPC)

安装

$ go get -u github.com/mafredri/cdp

使用示例

下面是一个完整的示例,展示如何使用cdp库进行网页导航、获取DOM内容和截图:

package main

import (
	"bufio"
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"time"

	"github.com/mafredri/cdp"
	"github.com/mafredri/cdp/devtool"
	"github.com/mafredri/cdp/protocol/dom"
	"github.com/mafredri/cdp/protocol/page"
	"github.com/mafredri/cdp/rpcc"
)

func main() {
	err := run(5 * time.Second)
	if err != nil {
		log.Fatal(err)
	}
}

func run(timeout time.Duration) error {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	// 使用DevTools HTTP/JSON API管理目标(如页面、webworkers)
	devt := devtool.New("http://127.0.0.1:9222")
	pt, err := devt.Get(ctx, devtool.Page)
	if err != nil {
		pt, err = devt.Create(ctx)
		if err != nil {
			return err
		}
	}

	// 初始化到Chrome DevTools协议目标的新RPC连接
	conn, err := rpcc.DialContext(ctx, pt.WebSocketDebuggerURL)
	if err != nil {
		return err
	}
	defer conn.Close() // 不关闭连接会导致内存泄漏

	c := cdp.NewClient(conn)

	// 打开DOMContentEventFired客户端来缓冲此事件
	domContent, err := c.Page.DOMContentEventFired(ctx)
	if err != nil {
		return err
	}
	defer domContent.Close()

	// 在Page域上启用事件,通常在启用事件前创建事件客户端更好,这样不会错过任何事件
	if err = c.Page.Enable(ctx); err != nil {
		return err
	}

	// 创建带有可选Referrer字段的Navigate参数
	navArgs := page.NewNavigateArgs("https://www.google.com").
		SetReferrer("https://duckduckgo.com")
	nav, err := c.Page.Navigate(ctx, navArgs)
	if err != nil {
		return err
	}

	// 等待直到我们收到DOMContentEventFired事件
	if _, err = domContent.Recv(); err != nil {
		return err
	}

	fmt.Printf("页面加载完成,框架ID: %s\n", nav.FrameID)

	// 获取文档根节点。我们可以在这里传递nil,因为此方法只接受可选参数
	doc, err := c.DOM.GetDocument(ctx, nil)
	if err != nil {
		return err
	}

	// 获取页面的外部HTML
	result, err := c.DOM.GetOuterHTML(ctx, &dom.GetOuterHTMLArgs{
		NodeID: &doc.Root.NodeID,
	})
	if err != nil {
		return err
	}

	fmt.Printf("HTML: %s\n", result.OuterHTML)

	// 捕获当前页面的截图
	screenshotName := "screenshot.jpg"
	screenshotArgs := page.NewCaptureScreenshotArgs().
		SetFormat("jpeg").
		SetQuality(80)
	screenshot, err := c.Page.CaptureScreenshot(ctx, screenshotArgs)
	if err != nil {
		return err
	}
	if err = ioutil.WriteFile(screenshotName, screenshot.Data, 0o644); err != nil {
		return err
	}

	fmt.Printf("截图已保存: %s\n", screenshotName)

	// 将页面保存为PDF
	pdfName := "page.pdf"
	f, err := os.Create(pdfName)
	if err != nil {
		return err
	}

	pdfArgs := page.NewPrintToPDFArgs().
		SetTransferMode("ReturnAsStream") // 请求流
	pdfData, err := c.Page.PrintToPDF(ctx, pdfArgs)
	if err != nil {
		return err
	}

	sr := c.NewIOStreamReader(ctx, *pdfData.Stream)
	r := bufio.NewReader(sr)

	// 以~r.Size()块写入文件
	_, err = r.WriteTo(f)
	if err != nil {
		return err
	}

	err = f.Close()
	if err != nil {
		return err
	}

	fmt.Printf("PDF已保存: %s\n", pdfName)

	return nil
}

主要功能说明

  1. 连接管理:通过devtool包获取WebSocket调试URL,然后使用rpcc包建立连接
  2. 事件处理:通过DOMContentEventFired等事件监听页面状态变化
  3. 页面导航:使用Page.Navigate方法导航到指定URL
  4. DOM操作:获取文档根节点和外部HTML
  5. 截图功能:捕获页面截图并保存为JPEG文件
  6. PDF导出:将页面导出为PDF文件

这个示例展示了cdp库的主要功能,包括页面控制、DOM操作和内容导出等常见用例。


更多关于golang实现Chrome调试协议类型安全绑定的插件库cdp的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang实现Chrome调试协议类型安全绑定的插件库cdp的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang中使用cdp库实现Chrome调试协议绑定

cdp是一个用于Golang的类型安全的Chrome DevTools Protocol (CDP)客户端库,它允许你通过代码控制Chrome或Chromium浏览器。下面我将介绍如何使用cdp库与Chrome浏览器交互。

安装cdp库

首先安装cdp库:

go get github.com/mafredri/cdp
go get github.com/mafredri/cdp/devtool
go get github.com/mafredri/cdp/protocol
go get github.com/mafredri/cdp/rpcc

基本使用示例

1. 启动Chrome浏览器

首先确保Chrome浏览器以远程调试模式运行:

google-chrome --remote-debugging-port=9222

2. 连接浏览器示例

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/mafredri/cdp"
	"github.com/mafredri/cdp/devtool"
	"github.com/mafredri/cdp/protocol/page"
	"github.com/mafredri/cdp/rpcc"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// 连接到浏览器
	devt := devtool.New("http://localhost:9222")
	pt, err := devt.Get(ctx, devtool.Page)
	if err != nil {
		pt, err = devt.Create(ctx)
		if err != nil {
			log.Fatal(err)
		}
	}

	// 建立RPC连接
	conn, err := rpcc.DialContext(ctx, pt.WebSocketDebuggerURL)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	// 创建CDP客户端
	client := cdp.NewClient(conn)

	// 启用必要的域
	if err := client.Page.Enable(ctx); err != nil {
		log.Fatal(err)
	}

	// 导航到页面
	navArgs := page.NewNavigateArgs("https://example.com")
	_, err = client.Page.Navigate(ctx, navArgs)
	if err != nil {
		log.Fatal(err)
	}

	// 等待页面加载完成
	loadEventFired, err := client.Page.LoadEventFired(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer loadEventFired.Close()

	_, err = loadEventFired.Recv()
	if err != nil {
		log.Fatal(err)
	}

	// 获取页面标题
	title, err := client.Page.GetTitle(ctx)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Page title: %s\n", title.Title)
}

高级功能示例

1. 截取屏幕截图

func takeScreenshot(client *cdp.Client, ctx context.Context) error {
	// 获取视口尺寸
	metrics, err := client.Page.GetLayoutMetrics(ctx)
	if err != nil {
		return err
	}

	// 设置截图参数
	screenshotArgs := page.NewCaptureScreenshotArgs().
		SetFormat("png").
		SetQuality(90).
		SetClip(&page.Viewport{
			X:      metrics.LayoutViewport.ClientX,
			Y:      metrics.LayoutViewport.ClientY,
			Width:  metrics.LayoutViewport.ClientWidth,
			Height: metrics.LayoutViewport.ClientHeight,
			Scale:  1,
		})

	// 截取屏幕
	screenshot, err := client.Page.CaptureScreenshot(ctx, screenshotArgs)
	if err != nil {
		return err
	}

	// 保存截图
	err = os.WriteFile("screenshot.png", screenshot.Data, 0644)
	if err != nil {
		return err
	}

	fmt.Println("Screenshot saved as screenshot.png")
	return nil
}

2. 执行JavaScript代码

func evaluateJS(client *cdp.Client, ctx context.Context, js string) (string, error) {
	evalArgs := page.NewEvaluateArgs(js)
	result, err := client.Page.Evaluate(ctx, evalArgs)
	if err != nil {
		return "", err
	}
	
	if result.Result.Value != nil {
		return fmt.Sprintf("%v", result.Result.Value), nil
	}
	return "", nil
}

// 使用示例
// result, err := evaluateJS(client, ctx, "document.querySelector('h1').textContent")

3. 监听控制台日志

func listenConsoleLogs(client *cdp.Client, ctx context.Context) error {
	// 启用Console域
	if err := client.Console.Enable(ctx); err != nil {
		return err
	}

	// 监听控制台消息
	consoleEvents, err := client.Console.MessageAdded(ctx)
	if err != nil {
		return err
	}

	go func() {
		defer consoleEvents.Close()
		for {
			event, err := consoleEvents.Recv()
			if err != nil {
				return
			}
			fmt.Printf("Console %s: %s\n", event.Message.Level, event.Message.Text)
		}
	}()

	return nil
}

最佳实践

  1. 上下文管理:始终使用context来控制超时和取消操作
  2. 资源清理:确保关闭所有连接和监听器
  3. 错误处理:妥善处理所有可能的错误
  4. 并发安全:cdp客户端不是并发安全的,需要自己管理同步

总结

cdp库为Golang提供了类型安全的Chrome DevTools Protocol绑定,使得自动化浏览器操作变得简单可靠。通过这个库,你可以实现页面导航、DOM操作、JavaScript执行、网络监控等各种浏览器自动化功能。

更多高级用法可以参考cdp官方文档Chrome DevTools Protocol文档

回到顶部