1 简析
1.1 需求与背景
有这个需求,是由于工作上,有一些监控视图,总是需要 VPN 连接才能浏览,对于非工作时间段,掌握服务状况不友好(艰辛的打工人)。
故有了,定时截图监控视图,并通过推送到企业微信,助力业务。
1.2 无头浏览器(Headless Browser)
无头浏览器指的是没有图形用户界面的浏览器。 无头浏览器在类似于流行网络浏览器的环境中提供对网页的自动控制,但是通过命令行界面或使用网络通信来执行。 (来自维基百科)
简单来说,就是可以使用程序模拟用户进行浏览器操作,能够做到别的测试方法没办法做到的 js、ajax 渲染。
1.3 效果图
数据会被定时截取并推送至企业微信。
2 实现原理
操作/模拟登陆/截图:googleChrome
+ chromeDriver
+ chromedp
推送:企业微信 WebHook
3 截图
以下操作基于 CentOS Linux release 8.3.2011 操作。 之前尝试通过 Ubuntu 进行测试,不过依赖包有兼容问题遂放弃。
3.1 安装必要的软件与环境
3.1.1 安装必要的软件
1 2 3 4
| $ wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm $ yum install google-chrome-stable_current_x86_64.rpm $ google-chrome –version
|
3.1.2 安装必要的驱动
1 2 3 4
| $ wget https://chromedriver.storage.googleapis.com/2.32/chromedriver_linux64.zip $ unzip chromedriver_linux64.zip -d /usr/bin/ $ chromedriver –version
|
3.1.3 安装必要的字体与图像驱动
1 2 3 4 5 6 7
| $ yum install xorg-x11-fonts* -y $ yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 –y $ yum install vlgothic-fonts.noarch vlgothic-p-fonts.noarch $ yum install bitmap-fonts bitmap-fonts-cjk $ yum install wqy-unibit-fonts.noarch wqy-zenhei-fonts.noarch $ yum install Xvfb
|
3.1.4 测试安装正确性
如果可以顺利的截图,且截图中的中文字符显示正确,则表示已经安装了对应的组件了。
1 2
| $ google-chrome-stable --headless --disable-gpu --screenshot https://www.baidu.com
|
3.2 开发 Go 插件
以下操作基于 Golang 操作,使用 Chrome 官方提供包 github.com/chromedp/chromedp
更多的例子,可以查询官方的示例 https://github.com/chromedp/examples
3.2.1 总览
chromedp 模拟用户操作分为 3
步:
- 构建一个特殊的自有
ctx
开始
- 然后构造一个
chromedp.Tasks{}
对象来模拟用户操作。
- 而
chromedp.Tasks{}
的参数,则是一系列的 []Action
,即你希望浏览器模拟的动作(登陆、点击、截屏、等待)等。
- 最后,完成了 Task 的构造,使用
chromedp.Run(ctx, chromedp.Task{...})
即完成一次模拟了。
以下,则是一些常用的动作实现与讲解
3.2.2 Init
需要起一个特定的上下文
1 2 3 4 5
| ctx, cancel := chromedp.NewContext( context.Background(), chromedp.WithDebugf(log.Printf), ) defer cancel()
|
3.2.3 全屏截屏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
func screenshot(ctx context.Context, url, name string) { var buf []byte
if err := chromedp.Run(ctx, fullScreenshotHandle(url, 90, &buf)); err != nil { log.Fatal(err) } if err := ioutil.WriteFile(name+"_fullScreenshot.png", buf, 0o644); err != nil { log.Fatal(err) }
log.Printf("wrote fullScreenshot.png") }
func fullScreenshotHandle(urlstr string, quality int, res *[]byte) chromedp.Tasks { return chromedp.Tasks{ chromedp.Navigate(urlstr), chromedp.FullScreenshot(res, quality), } }
|
3.2.4 模拟登陆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| func login(ctx context.Context, url, username, password string) { var res string err := chromedp.Run(ctx, submit(url, `//input[@name="account"]`, username, password, &res)) if err != nil { log.Fatal(err) } log.Printf("got: `%s`", strings.TrimSpace(res)) }
func submit(urlstr, sel, username, password string, res *string) chromedp.Tasks { return chromedp.Tasks{ chromedp.Navigate(urlstr), chromedp.WaitVisible(sel),
chromedp.SetValue(`//select[@name="loginType"]`, "2001"), chromedp.SendKeys(`//input[@name="account"]`, username), chromedp.SendKeys(`//input[@name="password"]`, password), chromedp.Submit(`//input[@name="account"]`), chromedp.WaitNotPresent(`//*[@id="remeber"]//label[contains(., '记住帐号')]`) } }
|
3.2.5 点击事件
1 2 3 4 5 6 7 8 9
| action := chromedp.Tasks{ chromedp.Navigate(url), chromedp.ActionFunc(func(ctx context.Context) error {...} }
action = append(action, chromedp.Click(`#panel-58 .dashboard-row__title`, chromedp.NodeVisible), chromedp.Click(`#panel-65 .dashboard-row__title`, chromedp.NodeVisible), )
|
4 推送
在企业微信中,新建一个群聊,并创建一个机器人,获取对应的 WebHook 地址。
由于机器人,允许发送图片,格式为图片的 base64 + md5(base64)。
4.1 图片转码
1 2 3 4 5 6 7 8 9 10 11 12
| func byte2base64(ctx context.Context, pngByte []byte) (base64Str, md5Str string) {
base64Str = base64.StdEncoding.EncodeToString(pngByte) h := md5.New() h.Write(pngByte) md5Str = strings.ToUpper(hex.EncodeToString(h.Sum(nil))) return }
|
4.2 构建一个微信推送结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| type WxPush struct { MsgType string `json:"msgtype,omitempty"` Image WxPushImage `json:"image,omitempty"` }
type WxPushImage struct { Base64 string `json:"base64,omitempty"` Md5 string `json:"md5,omitempty"` }
func createWxPushStruct (base64Str, md5Str string) (pushStruct WxPush) { pushStruct = WxPush{ MsgType: "image", Image: WxPushImage{ Base64: string(base64Str), Md5: md5Str, }, } return }
|
4.3 发起请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| func httpPostJson(postJson []byte, url string) { log.Println(string(postJson))
req, err := http.NewRequest("POST", url, bytes.NewBuffer(postJson)) if err != nil { log.Fatalf("post request err[%s]",err) }
req.Header.Set("Content-Type", "application/json")
client := &http.Client{} resp, err := client.Do(req) if err != nil { log.Fatalf("post do err[%s]",err) } defer resp.Body.Close()
statuscode := resp.StatusCode hea := resp.Header body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) fmt.Println(statuscode) fmt.Println(hea) }
|