部署 chromeheadlessshell以及使用chromedp 实现自动化操作

部署 chromeheadlessshell以及使用chromedp 实现自动化操作

部署 chrome-headless-shel l 以及使用 chromedp 实现自动化操作

一、使用 chromedp 操作本地 谷歌浏览器

第三方库:https://github.com/chromedp

代码实现:

package main

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/chromedp/cdproto/dom"
	"github.com/chromedp/chromedp"
)

func main() {
	var url = "https://www.doc88.com/p-99929140776272.html?s=rel&id=9"
	var timeOut = time.Second * 60 * 5
	//var remoteAllocatorUrl = "ws://0.0.0.0:9222" // 操作远程的 chrome 浏览器
	var remoteAllocatorUrl = "" // 空则表示操作本地的谷歌浏览器
	var err error
	// 创建上下文和取消函数
	ctx, cancel := chromedp.NewContext(context.Background())
	defer func() {
		cancel()
	}()

	// 设置超时时间
	ctx, cancel = context.WithTimeout(ctx, timeOut)
	defer func() {
		cancel()
	}()
	var execCtx context.Context
	var cancelExec context.CancelFunc
	// 创建chrome配置
	opts := append(chromedp.DefaultExecAllocatorOptions[:],
		chromedp.Flag("headless", false), // 使用不展示浏览器,
		chromedp.WindowSize(1200, 1200),  // 打开的浏览器的窗口大小
	)
	// 创建chrome执行器
	if remoteAllocatorUrl != "" {
		execCtx, cancelExec = chromedp.NewRemoteAllocator(ctx, remoteAllocatorUrl, []chromedp.RemoteAllocatorOption{}...)
	} else {
		execCtx, cancelExec = chromedp.NewExecAllocator(ctx, opts...)
	}

	defer func() {
		cancelExec()
	}()
	// 创建chromedp任务上下文
	taskCtx, cancelTask := chromedp.NewContext(execCtx)
	defer cancelTask()
	defer cancelTask()
	fmt.Println("chromedp.Navigate:", url)
	var buf []byte
	runAction := []chromedp.Action{
		chromedp.Navigate(url),
	}
	if remoteAllocatorUrl != "" {
		runAction = append(runAction, chromedp.EmulateViewport(1920, 1080, chromedp.EmulateScale(1)))

	}
	err = chromedp.Run(taskCtx,
		runAction...,
	)
	if err != nil {
		fmt.Printf("Run %s err:%v\n", url, err)
		return
	}
	// 持续点击id='continueButton',直到页面不存在为止
	fmt.Println("chromedp.continueButton:")

	for i := 1; i <= 10; i++ {

		err = chromedp.Run(taskCtx,
			chromedp.Evaluate(`document.querySelector("#continueButton").click()`, nil),
			chromedp.Sleep(time.Second*1),
		)
		fmt.Println("click ontinueButton:", i)
	}
	// 获取第一个div的位置并滚动到该位置
	var divPos *dom.Rect
	err = chromedp.Run(taskCtx,
		chromedp.Evaluate(`document.querySelector('.outer_page').getBoundingClientRect()`, &divPos),
	)
	if err != nil {
		return
	}
	time.Sleep(time.Second * 1)
	// 创建一个空的结果变量,用于存储获取到的div数量
	var divCount int

	// 获取div的数量
	err = chromedp.Run(taskCtx,
		chromedp.EvaluateAsDevTools(`document.querySelectorAll('.outer_page').length`, &divCount),
		chromedp.Sleep(time.Second*1),
		chromedp.ScrollIntoView("#readEndDiv"),
		chromedp.Sleep(time.Second*3),
		chromedp.ScrollIntoView(".doctopic"),
		chromedp.Sleep(time.Second*1),
		chromedp.CaptureScreenshot(&buf), // 截图
	)
	if err != nil {
		return
	}
	fmt.Println("chromedp.get div count:", divCount)
	time.Sleep(time.Second * 1)

	// 循环点击id='nextPageButton'并下载图片
	for i := 1; i <= divCount; i++ {
		err = chromedp.Run(taskCtx,
			chromedp.Click("#nextPageButton"),
			chromedp.WaitVisible(".outer_page"),
		)
		err = chromedp.Run(taskCtx,
			chromedp.WaitVisible(fmt.Sprintf("#pagepb_%d", i), chromedp.ByID),
			chromedp.ActionFunc(func(ctx context.Context) error {
				var pageContent = "内容加载中......"
				for pageContent != "" {
					err = chromedp.TextContent(fmt.Sprintf("#pagepb_%d", i), &pageContent).Do(ctx)
					if err != nil {
						break
					}
					time.Sleep(time.Second * 1)
					fmt.Printf("cur page %d load pageContent: %s\n", i, pageContent)
				}

				return nil
			}),
			chromedp.ActionFunc(func(ctx context.Context) error {
				jsCode := fmt.Sprintf(`document.querySelector('#page_%d').toDataURL('image/jpeg', 0.8)`, i)
				var result string
				err = chromedp.Evaluate(jsCode, &result).Do(ctx)
				if err != nil {
					return err
				}
				fmt.Println("download result:", fmt.Sprintf("%d.jpg", i))

				return nil
			}),
			// chromedp.Sleep(time.Second*1),
		)
		if err != nil {
			return
		}
		fmt.Println("nextPageButton download ", i)
		// 添加延迟
		//time.Sleep(1 * time.Second)
	}

	err = os.WriteFile("screenshot.png", buf, 0644)
	if err != nil {
		return
	}
	fmt.Println("chromedp.Navigate:", url)
	return
}

二、 操作远程的 Chrome 浏览器

1. 在服务器部署 chrome-headless-shell

参考资料:

https://github.com/chromedp/docker-headless-shell

examples/remote/main.go at master · chromedp/examples · GitHub

https://juejin.cn/post/6959511879936901133

1). 使用 docker 部署

# 启动容器
docker run -d -p 9222:9222 --rm --name headless-shell --shm-size 2G chromedp/headless-shell
# 默认是没有中文字体的,所以需要下载中文字体
docker exec -it headless-shell apt-get update
docker exec -it headless-shell apt-get install -y fonts-wqy-zenhei
docker exec -it headless-shell apt install xfonts-intl-chinese ttf-wqy-microhei xfonts-wqy
# 重启容器	
docker restart headless-shell

2). 使用 k8s 部署

只需要编写 YAML ,然后 kubectl -f xxx.yaml

参考:/init/chromeheadlessshell/chromeheadlessshell.yaml

2. 实现代码

和步骤一的代码一直,只需要设置:

var remoteAllocatorUrl = "ws://0.0.0.0:9222" // 操作远程的 Chrome 浏览器