chromedp
package main
import (
"context"
"fmt"
"github.com/chromedp/chromedp"
"log"
"time"
)
var timeOutError = errors.New("获取指定path的的页面元素超时")
func main() {
dpCtx := newChromeDpCtx()
defer func() {
if err := chromedp.Cancel(dpCtx); err != nil {
log.Println(err)
}
}()
/** 调试时可以加上,避免主动关闭进程但是浏览器还在执行
go func() {
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt, os.Kill)
<-quit
_ = chromedp.Cancel(dpCtx)
os.Exit(1)
}()
*/
if err := chromedp.Run(dpCtx, newTask()); err != nil {
log.Println("执行失败:", err)
return
}
}
//获得一个chromdp的context
func newChromeDpCtx() context.Context {
options := []chromedp.ExecAllocatorOption{
chromedp.Flag("headless", false), // debug使用false 正式使用用true
chromedp.WindowSize(1280, 1024), // 调整浏览器大小
}
options = append(chromedp.DefaultExecAllocatorOptions[:], options...)
ctx, _ := chromedp.NewExecAllocator(context.Background(), options...)
ctx, _ = chromedp.NewContext(ctx, chromedp.WithLogf(log.Printf)) // 会打开浏览器并且新建一个标签页进行操作
ctx, _ = chromedp.NewRemoteAllocator(ctx, "ws://127.0.0.1:9222/devtools/page/${targetId}") //使用远程调试,可以结合下面的容器使用
ctx, _ = chromedp.NewContext(ctx, chromedp.WithLogf(log.Printf),chromedp.WithTargetID(${targetId}))// WithTargetID可以指定一个标签页进行操作
return ctx
}
//返回一个任务的列队来执行
//这里示例登录网站后对一个div截图
func newTask() chromedp.Tasks {
return chromedp.Tasks{
toUrl("打开登录页面",`http://***.***.***.***:*****/#/login`),
setValue("填写用户名", "//*[@id=\"app\"]/div/div/div[2]/form/div[1]/input", "******"),
setValue("填写密码", "//*[@id=\"app\"]/div/div/div[2]/form/div[2]/input", "******"),
click("点击登录按钮", "//*[@id=\"app\"]/div/div/div[2]/form/button"),
toUrl("跳转至页面", "http://***.***.***.***:*****/#/project/1/dashboard/579"),
chromedp.Sleep(2 * time.Second),
screenShot("指定div截图","//*[@id=\"app\"]/div/div/div/div[2]/div/div/div/div/div[2]/div[1]/div"),
}
}
func setValue(name, path string, value string) chromedp.ActionFunc {
return func(ctx context.Context) (err error) {
defer handleActionError(name,&err)
timeout, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
if chromedp.WaitVisible(path).Do(timeout) != nil {
return timeOutError
}
return chromedp.SetValue(path, value).Do(timeout)
}
}
func click(name, path string) chromedp.ActionFunc {
return func(ctx context.Context) (err error) {
defer handleActionError(name,&err)
timeout, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
if chromedp.WaitVisible(path).Do(timeout) != nil {
return timeOutError
}
return chromedp.Click(path).Do(timeout)
}
}
func toUrl(name, url string) chromedp.ActionFunc {
return func(ctx context.Context) (err error) {
defer handleActionError(name,&err)
chromedp.Sleep(1*time.Second).Do(ctx)
return chromedp.Navigate(url).Do(ctx)
}
}
func screenShot(name string,path string) chromedp.ActionFunc {
return func(ctx context.Context) (err error) {
defer handleActionError(name,&err)
timeout, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
if chromedp.WaitVisible(path).Do(timeout) != nil {
return timeOutError
}
var code []byte
if err = chromedp.Screenshot(path, &code).Do(timeout); err != nil {
return
}
return ioutil.WriteFile("shot.png", code, 0755)
}
}
func handleActionError(name string,err *error){
if err != nil {
*err = fmt.Errorf("【%s】失败=>%w", name, *err)
}
}
headless-shell
Chrome无头浏览器的容器,用于自动化/驱动web
https://registry.hub.docker.com/r/chromedp/headless-shell
$ docker run -d -p 9222:9222 --rm --name headless-shell chromedp/headless-shell
# if headless-shell is crashing with a BUS_ADRERR error, pass a larger shm-size:
$ docker run -d -p 9222:9222 --rm --name headless-shell --shm-size 2G chromedp/headless-shell
会缺少中文字体,进入容器,执行:
apt-get update && apt install xfonts-intl-chinese ttf-wqy-microhei xfonts-wqy
通过 http://ip:9222/json 来获取 调试模式下的浏览器tab id,可以通过 chromedp.NewRemoteAllocator来指定webSocketDebuggerUrl,就可以远程驱动谷歌浏览器进行操作了
windows下 谷歌浏览器启动调试模式可以在快捷方式右键属性的目标后加入--remote-debugging-port 命令来完成,如:
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
本地调试远程浏览器
http://localhost:9222/devtools/inspector.html?ws=${ip}:${port}/devtools/page/${targetId}