(摘) golang库 -- chromedp

声明:内容源自网络,版权归原作者所有。若有侵权请在网页聊天中联系我

chromedp是谷歌官方推出的无头浏览器。chromedp 包是一种更快、更简单的方式来驱动在 Go 中支持 Chrome DevTools 协议的浏览器,而无需外部依赖。

chromedp github

示例https://github.com/chromedp/examples

Chrome DevTools Protocol

看起来就像Python那个什么库,实现浏览器的模拟操作。之前还设计通过Python代替人工实现自动操作。或许这个库也可以。
它要求必须使用安装版的chrome来调用。大致分为前台模式(显示浏览器,更多用于调试)和无头模式(后台自动完成)。
实际使用有头模式(前台模式)时,它会显示“”Chrome正受到自动测试软件的控制 。 chromedp.Flag(“headless”, true), // 有头无头,关键在此

这里有一个网友示例可以看看。

演示了如何使用选择器单击元素

package main

import (
	"context"
	"log"
	"time"

	"github.com/chromedp/chromedp"
)

func main() {
	// 创建chrome实例
	ctx, cancel := chromedp.NewContext(
		context.Background(),
		// chromedp.WithDebugf(log.Printf),   // 是否显示调试信息
	)
	defer cancel()

	// 创建超时
	ctx, cancel = context.WithTimeout(ctx, 15*time.Second)
	defer cancel()

	// 导航到页面,等待元素,单击
	var example string
	err := chromedp.Run(ctx,
		chromedp.Navigate(`https://pkg.go.dev/time`),
		// 等待页脚元素可见(即页面已加载)
		chromedp.WaitVisible(`body > footer`),
		// 查找并单击“示例”链接
		chromedp.Click(`#example-After`, chromedp.NodeVisible),
		// 检索textarea的文本
		chromedp.Value(`#example-After textarea`, &example),
	)
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("获取到示例数据:\n%s", example)
}

常用功能

chromedp.NewContext() 初始化chromedp的上下文,后续这个页面都使用这个上下文进行操作
chromedp.Run() 运行一个chrome的一系列操作
chromedp.Navigate() 将浏览器导航到某个页面
chromedp.WaitVisible() 等候某个元素可见,再继续执行。
chromedp.Click() 模拟鼠标点击某个元素
chromedp.Value() 获取某个元素的value值
chromedp.ActionFunc() 再当前页面执行某些自定义函数
chromedp.Text() 读取某个元素的text值
chromedp.Evaluate() 执行某个js,相当于控制台输入js
network.SetExtraHTTPHeaders() 截取请求,额外增加header头
chromedp.SendKeys() 模拟键盘操作,输入字符
chromedp.Nodes() 根据xpath获取某些元素,并存储进入数组
chromedp.NewRemoteAllocator
chromedp.OuterHTML() 获取元素的outer html
chromedp.Screenshot() 根据某个元素截图
page.CaptureScreenshot() 截取整个页面的元素
chromedp.Submit() 提交某个表单
chromedp.WaitNotPresent() 等候某个元素不存在,比如“正在搜索。。。”
chromedp.Tasks{} 一系列Action组成的任务

另一个简单实例

package main

import (
	"context"
	"log"
	"time"

	"github.com/chromedp/chromedp"
)

var loginURL = "https://i.scwy.net"

func main() {
	// chromdp依赖context上限传递参数
	ctx, _ := chromedp.NewExecAllocator(
		context.Background(),

		// 以默认配置的数组为基础,覆写headless参数
		// 当然也可以根据自己的需要进行修改,这个flag是浏览器的设置
		append(
			chromedp.DefaultExecAllocatorOptions[:],
			chromedp.Flag("headless", false),
		)...,
	)
	ctx, _ = context.WithTimeout(ctx, 30*time.Second)
	ctx, _ = chromedp.NewContext(
		ctx,
		// 设置日志方法
		chromedp.WithLogf(log.Printf),
	)
	// 执行任务
	if err := chromedp.Run(ctx, myTasks()); err != nil {
		log.Fatal(err)
		return
	}
}

// 自定义任务
func myTasks() chromedp.Tasks {
	return chromedp.Tasks{
		chromedp.Navigate(loginURL),
	}
}

模拟特定设备,例如iPhone。这里也演示了如何截取网页为图片

package main

import (
	"context"
	"io/ioutil"
	"log"

	"github.com/chromedp/chromedp"
	"github.com/chromedp/chromedp/device"
)

func main() {
	// create context
	ctx, cancel := chromedp.NewContext(context.Background())
	defer cancel()

	// run
	var b1, b2 []byte
	if err := chromedp.Run(ctx,
		// 模拟iPhone 7环境
		chromedp.Emulate(device.IPhone7landscape),
		chromedp.Navigate(`https://i.scwy.net/`),
		chromedp.CaptureScreenshot(&b1),

		// 重置
		chromedp.Emulate(device.Reset),

		// 设置非常大的视点
		chromedp.EmulateViewport(1920, 2000),
		chromedp.Navigate(`https://i.scwy.net/`),
		chromedp.CaptureScreenshot(&b2),
	); err != nil {
		log.Fatal(err)
	}

	if err := ioutil.WriteFile("screenshot1.png", b1, 0o644); err != nil {
		log.Fatal(err)
	}
	if err := ioutil.WriteFile("screenshot2.png", b2, 0o644); err != nil {
		log.Fatal(err)
	}
	log.Printf("wrote screenshot1.png and screenshot2.png")
}

运行脚本

func main() {
	// create context
	ctx, cancel := chromedp.NewContext(context.Background())
	defer cancel()

	// run task list
	var res []string
	err := chromedp.Run(ctx,
		chromedp.Navigate(`https://www.google.com/`),
		chromedp.Evaluate(`Object.keys(window);`, &res),
	)
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("window object keys: %v", res)
}

又是一个网页截图,但我看的是它的配置部份

package main

import (
	"context"
	"io/ioutil"
	"log"
	"math"
	"time"

	"github.com/chromedp/cdproto/emulation"
	"github.com/chromedp/cdproto/page"
	"github.com/chromedp/chromedp"
)

func main() {

	// 禁用chrome headless
	opts := append(
		chromedp.DefaultExecAllocatorOptions[:],
		chromedp.NoDefaultBrowserCheck, //不检查默认浏览器
		chromedp.Flag("headless", true),   // 有头无头,关键在此
		chromedp.Flag("blink-settings", "imagesEnabled=true"), //开启图像界面,重点是开启这个
		chromedp.Flag("ignore-certificate-errors", true),      //忽略错误
		chromedp.Flag("disable-web-security", true),           //禁用网络安全标志
		chromedp.Flag("disable-extensions", true),             //开启插件支持
		chromedp.Flag("disable-default-apps", true),
		chromedp.WindowSize(1920, 1080),    // 设置浏览器分辨率(窗口大小)
		chromedp.Flag("disable-gpu", true), // 开启gpu渲染
		chromedp.Flag("hide-scrollbars", true),
		chromedp.Flag("mute-audio", true),
		chromedp.Flag("no-sandbox", true),
		chromedp.Flag("no-default-browser-check", true),
		chromedp.NoFirstRun, //设置网站不是首次运行
		chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36"), //设置UserAgent
	)
	allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
	defer cancel()

	// 创建上下文实例
	ctx, cancel := chromedp.NewContext(
		allocCtx,
		chromedp.WithLogf(log.Printf),
	)
	defer cancel()

	// 创建超时上下文
	ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
	defer cancel()

	//导航到目标页面,等待一个元素,捕捉元素的截图
	var buf []byte
	if err := chromedp.Run(ctx, fullScreenshot(`https://i.scwy.net/`, 80, &buf)); err != nil {
		log.Fatal(err)
	}
	if err := ioutil.WriteFile("./Screenshot.png", buf, 0644); err != nil {
		log.Fatal(err)
	}
	log.Println("图片写入完成")

}

// 获取整个浏览器窗口的截图(全屏)
// 这将模拟浏览器操作设置。
func fullScreenshot(urlstr string, quality int64, res *[]byte) chromedp.Tasks {
	return chromedp.Tasks{
		chromedp.Navigate(urlstr),
		//chromedp.WaitVisible("style"),
		chromedp.Sleep(10 * time.Second),
		//chromedp.OuterHTML(`document.querySelector("body")`, &htmlContent, chromedp.ByJSPath),
		chromedp.ActionFunc(func(ctx context.Context) error {
			// 得到布局页面
			_, _, _, _, _, contentSize, err := page.GetLayoutMetrics().Do(ctx)
			if err != nil {
				return err
			}

			width, height := int64(math.Ceil(contentSize.Width)), int64(math.Ceil(contentSize.Height))

			// 浏览器视窗设置模拟
			err = emulation.SetDeviceMetricsOverride(width, height, 1, false).
				WithScreenOrientation(&emulation.ScreenOrientation{
					Type:  emulation.OrientationTypePortraitPrimary,
					Angle: 0,
				}).
				Do(ctx)
			if err != nil {
				return err
			}

			// 捕捉屏幕截图
			*res, err = page.CaptureScreenshot().
				WithQuality(quality).
				WithClip(&page.Viewport{
					X:      contentSize.X,
					Y:      contentSize.Y,
					Width:  contentSize.Width,
					Height: contentSize.Height,
					Scale:  1,
				}).Do(ctx)
			if err != nil {
				return err
			}
			return nil
		}),
	}
}

关于Cookies自动登陆:https://zhuanlan.zhihu.com/p/515734676

以下示例是用于获取网页内容

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/chromedp/chromedp"
)

func main() {

	ctx, cancel := chromedp.NewContext(context.Background())
	defer cancel()

	url := "http://webcode.me"
	var data string

	if err := chromedp.Run(ctx,
		chromedp.Navigate(url),
		chromedp.OuterHTML("html", &data, chromedp.ByQuery),
	); err != nil {
		log.Fatal(err)
	}

	fmt.Println(data)
}

表单提交及模拟键盘输入

package main

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

    "github.com/chromedp/chromedp"
)

func main() {

    ctx, cancel := chromedp.NewContext(
        context.Background(),
    )
    defer cancel()

    ctx, cancel = context.WithTimeout(ctx, 6*time.Second)
    defer cancel()

    url := "http://webcode.me/submit/"
    var res string

    err := chromedp.Run(ctx,

        chromedp.Navigate(url),
        chromedp.SendKeys("input[name=name]", "Lucia"),
        chromedp.SendKeys("input[name=message]", "Hello!"),
        // chromedp.Click("button", chromedp.NodeVisible),
        chromedp.Submit("input[name=name]"),
        chromedp.Text("*", &res),
    )

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(res)
}

如何使用远程WebSocket URL连接到现有的Chrome DevTools实例。

// 运行 cmd /c "C:\Users\Administrator\AppData\Local\Google\Chrome\Application\chrome.exe"  --remote-debugging-port=9222
// 调用 go run main.go --devtools-ws-url http://127.0.0.1:9222

package main

import (
	"context"
	"flag"
	"log"

	"github.com/chromedp/chromedp"
)

func main() {
	devtoolsWsURL := flag.String("devtools-ws-url", "", "DevTools WebSsocket URL")
	flag.Parse()
	if *devtoolsWsURL == "" {
		log.Fatal("must specify -devtools-ws-url")
	}

	// create allocator context for use with creating a browser context later
	allocatorContext, cancel := chromedp.NewRemoteAllocator(context.Background(), *devtoolsWsURL)
	defer cancel()

	// create context
	ctx, cancel := chromedp.NewContext(allocatorContext)
	defer cancel()

	// run task list
	var body string
	if err := chromedp.Run(ctx,
		chromedp.Navigate("https://i.scwy.net"),
		chromedp.WaitVisible(".page-item"),
		chromedp.OuterHTML("html", &body),
	); err != nil {
		log.Fatalf("Failed getting body of duckduckgo.com: %v", err)
	}

	log.Println("获取到一些内容:")
	log.Println(body[0:1000])
}

解决 exec: “google-chrome”: executable file not found in %PATH% 错误

这是没有发现chrome浏览器的症状,简单安装一下即可。但我比较环保,那就安装一个,复制目录到当前,然后再卸载。
修改chromedp库中的allocate.go文件,findExecPath函数中添加例如:.\Google\Chrome\Application\chrome.exe

网上提到的如下修改并没用:

	ctx, cancel := chromedp.NewExecAllocator(
		context.Background(),
		append(chromedp.DefaultExecAllocatorOptions[:],
			chromedp.Flag("headless", false),
			chromedp.ExecPath("./Google/Chrome/Application/chrome.exe"),
		)...,
	)

以下修改正确,不需要折腾chrome路径:

	options := []chromedp.ExecAllocatorOption{
		chromedp.Flag("headless", false),                       // debug使用
		chromedp.Flag("blink-settings", "imagesEnabled=false"), // 禁用图片加载
		chromedp.UserAgent(`Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36`),
                chromedp.ExecPath("./Chrome/Application/chrome.exe"),
	}
	options = append(chromedp.DefaultExecAllocatorOptions[:], options...)

	// Chrome初始化代码如下:
	c, _ := chromedp.NewExecAllocator(context.Background(), options...)

	// 创建实例
	ctx, cancel := chromedp.NewContext(c) //chromedp.WithDebugf(log.Printf),
	defer cancel()