ZetCode

Go chromedp

最后修改时间 2024 年 4 月 11 日

在本文中,我们将展示如何在 Golang 中使用 chromedp 自动化浏览器。

chromedp 是一个 Go 库,它提供了一个高级 API,用于通过 DevTools Protocol 控制 Chromium。它允许在无头模式(默认模式)下使用浏览器,这种模式无需用户界面即可运行。这对于编写脚本非常有用。

获取外层 HTML

chromedp.OuterHTML 会检索与选择器匹配的第一个元素节点的外部 HTML。

html.go
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)
}

该示例检索 webcode.me 的主页。

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

chromedp.NewContext 从父上下文创建一个 chromedp 上下文。返回的取消函数必须被调用以终止 chromedp 上下文;该函数等待资源清理完毕,并返回在此过程中遇到的任何错误。

if err := chromedp.Run(ctx,

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

Run 函数针对上下文执行操作。我们导航到 URL 并检索 html 标签的 HTML 数据。

获取标题

使用 chromedp.Title,我们可以获取文档的标题。

title.go
package main

import (
    "context"
    "fmt"
    "io"
    "log"
    "net/http"
    "net/http/httptest"
    "strings"

    "github.com/chromedp/chromedp"
)

func writeHTML(content string) http.Handler {

    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

        w.Header().Set("Content-Type", "text/html")
        io.WriteString(w, strings.TrimSpace(content))
    })
}

func main() {

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

    ts := httptest.NewServer(writeHTML(`
<head>
    <title>Home page</title>
</head>
<body>
    <p>Hello there!</a>
</body>
    `))

    defer ts.Close()

    var title string

    if err := chromedp.Run(ctx,

        chromedp.Navigate(ts.URL),
        chromedp.Title(&title),
    ); err != nil {

        log.Fatal(err)
    }

    fmt.Println(title)
}

在此示例中,我们创建了一个内置的网络服务器,它发送一个简单的网页。然后我们获取其标题。

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

    w.Header().Set("Content-Type", "text/html")
    io.WriteString(w, strings.TrimSpace(content))
})

处理函数将 text/html 内容发送回客户端。

ts := httptest.NewServer(writeHTML(`
<head>
    <title>Home page</title>
</head>
<body>
    <p>Hello there!</a>
</body>
    `))

使用 httptest.NewServer 创建了一个测试服务器。

defer ts.Close()

在程序结束时,测试服务器会被关闭。

if err := chromedp.Run(ctx,

    chromedp.Navigate(ts.URL),
    chromedp.Title(&title),
); err != nil {

    log.Fatal(err)
}

我们导航到测试服务器的 URL,并使用 chromedp.Title 获取文档标题。

$ go run title.go 
Home page

设置超时

我们可能会遇到任务死锁。为了防止这种情况,我们可以设置超时。

timeout.go
package main

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

    "github.com/chromedp/chromedp"
)

func main() {

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

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

    url := "http://webcode.me"

    var res string

    err := chromedp.Run(ctx,

        chromedp.Navigate(url),
        chromedp.Text("body", &res, chromedp.NodeVisible),
    )

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

    fmt.Println(strings.TrimSpace(res))
}

在此示例中,我们获取 body 标签的可见文本。并设置了超时。

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

我们将超时设置为 5 秒。

err := chromedp.Run(ctx,

    chromedp.Navigate(url),
    chromedp.Text("body", &res, chromedp.NodeVisible),
)

我们运行一个任务列表。我们使用 chromedp.Text 获取 body 的文本。

点击操作

使用 chromedp.Click 执行点击查询操作。

click.go
package main

import (
    "context"
    "log"
    "time"

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

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

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

    url := "http://webcode.me/click.html"

    var ua string

    err := chromedp.Run(ctx,

        chromedp.Emulate(device.IPhone11),
        chromedp.Navigate(url),
        chromedp.Click("button", chromedp.NodeVisible),
        chromedp.Text("#output", &ua),
    )

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

    log.Printf("User agent: %s\n", ua)
}

在此示例中,我们点击了一个网页上的按钮。该网页在输出 div 中显示了客户端的用户代理。

err := chromedp.Run(ctx,

    chromedp.Emulate(device.IPhone11),
    chromedp.Navigate(url),
    chromedp.Click("button", chromedp.NodeVisible),
    chromedp.Text("#output", &ua),
)

在任务列表中,我们导航到 URL,点击按钮,然后检索输出文本。我们获取了用户代理。我们使用 chromedp.Emulate 模拟了 IPhone11 设备。

创建屏幕截图

我们可以使用 chromedp.Screenshot 创建元素的屏幕截图。chromedp.FullScreenshot 则会截取整个浏览器视口的屏幕截图。

screenshot.go
package main

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

    "github.com/chromedp/chromedp"
)

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

    url := "http://webcode.me"

    var buf []byte
    if err := chromedp.Run(ctx, ElementScreenshot(url, "body", &buf)); err != nil {
        log.Fatal(err)
    }

    if err := ioutil.WriteFile("body.png", buf, 0o644); err != nil {
        log.Fatal(err)
    }

    if err := chromedp.Run(ctx, FullScreenshot(url, 90, &buf)); err != nil {
        log.Fatal(err)
    }

    if err := ioutil.WriteFile("full.png", buf, 0o644); err != nil {
        log.Fatal(err)
    }

    fmt.Println("screenshots created")
}

func ElementScreenshot(url, sel string, res *[]byte) chromedp.Tasks {

    return chromedp.Tasks{

        chromedp.Navigate(url),
        chromedp.Screenshot(sel, res, chromedp.NodeVisible),
    }
}

func FullScreenshot(url string, quality int, res *[]byte) chromedp.Tasks {

    return chromedp.Tasks{

        chromedp.Navigate(url),
        chromedp.FullScreenshot(res, quality),
    }
}

我们创建了两个屏幕截图。图像被写入磁盘,使用 ioutil.WriteFile

提交表单

chromedp.SendKeys 用于填充输入字段。表单可以使用 chromedp.Clickchromedp.Submit 进行提交。

submit.go
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)
}

该示例填写了一个表单并接收了一条消息。

chromedp.SendKeys("input[name=name]", "Lucia"),
chromedp.SendKeys("input[name=message]", "Hello!"),

我们将两个字符串设置到指定的输入标签中。

// chromedp.Click("button", chromedp.NodeVisible),
chromedp.Submit("input[name=name]"),

我们可以使用 chromedp.Clickchromedp.Submit 来提交表单。在后一种情况下,该函数会提交与选择器匹配的第一个元素节点的父表单。

来源

Go chromedp - Github 页面

在本文中,我们已经使用 chromedp 在 Go 中自动化了浏览器。

作者

我的名字是 Jan Bodnar,我是一名充满激情的程序员,拥有丰富的编程经验。我从 2007 年就开始撰写编程文章。迄今为止,我已发表了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Go 教程