golang实现服务端浏览器截屏

2020年3月31日 345点热度 0人点赞 0条评论
 每天,探索一点点。


笔者近期在使用golang进行开发工作时,受360技术公众号曾发布过文章的启发,想到是否可以使用golang操作无屏浏览器(headless browser)实现诸如爬虫、截屏、自动化测试等功能。

经过调研和测试,发现golang实现也是很好的一种选择。


背景

360技术公众号曾经发布过一篇文章《服务端浏览器截屏》,文章中对基于Selenium(Python SDK)实现的服务端截屏技术进行了详细讲解,可操作性非常强。


笔者曾使用Python+Selenium实现过爬虫和简单的自动化测试功能,对上面文章中使用的技术和组件都有涉及,我认为初学者完全可以通过该文章手把手的教学,实现服务端截屏功能。


不过基于Selenium的截图实现存在如下一些缺点:

  • 需要安装Selenium或是PhantomJS

  • PhantomJS已经停止维护了

  • Selenium的运行需要安装chromedriver

  • chromedriver对chrome的版本有一定要求

  • 截图时需要使用html2canvas JS库


可见,整体环境的搭建(包括各个软件之间版本的匹配),还是需要花费不少时间和精力适配并测试的。因此原文章作者在上述文章的最后也提供了“集成Docker”的方案,在上述环境docker化后,可以解决环境部署的问题。


而基于golang实现的方案,可以完美解决上述问题。该方案对运行时环境的要求为仅安装chrome浏览器即可。

无屏浏览器
Headless browser是浏览器的无界面形态,可以在不打开浏览器的前提下,使用所有浏览器支持的特性,例如:获取HTML,执行Javascript,渲染目标网页,获取cookie等。
本文主要使用的是headless chrome,下文提到的“浏览器”均代指headless chrome。

主要框架
为了方便地在golang程序里使用headless chrome,需要借助一些开源框架。实现headless chrome交互的库有很多,笔者经过对比后选择了chromedp。

chromedp的主要特点有:

  • 提供了更快,更简单的方式来驱动浏览器
  • 提供了丰富的底层API接口(基于CDP协议——Chrome Debugging Protocol)
  • 提供了灵活的上层API接口(Actions & Tasks),类似Selenium的WebElement actions
  • 除了浏览器本身,没有任何外部依赖 (如Selenium, PhantomJS等)
  • “一次编译,随处拷贝,到处运行”(基于golang的特性)

实现
安装
go get -u github.com/chromedp/chromedp
截图实现代码
package main
import ( "context" "io/ioutil" "log"
cdp "github.com/chromedp/chromedp")
func main() { // 创建新的cdp上下文 ctx, cancel := cdp.NewContext(context.Background()) defer cancel()
// 此处以360搜索首页为例 urlstr := `https://www.so.com/` var buf []byte // 需要截图的元素,支持CSS selector以及XPath query selector := `#main` if err := cdp.Run(ctx, elementScreenshot(urlstr, selector, &buf)); err != nil { log.Fatal(err) } // 写入文件 if err := ioutil.WriteFile("360_so.png", buf, 0644); err != nil { log.Fatal(err) }}
// 截图方法func elementScreenshot(urlstr, sel string, res *[]byte) cdp.Tasks { return cdp.Tasks{ // 打开url指向的页面 cdp.Navigate(urlstr),
// 等待待截图的元素渲染完成 cdp.WaitVisible(sel, cdp.ByID), // 也可以等待一定的时间 //cdp.Sleep(time.Duration(3) * time.Second),
// 执行截图 cdp.Screenshot(sel, res, cdp.NodeVisible, cdp.ByID), }}
编译执行后,代码中URL截图的效果如下所示

图片

导出PDF
在该框架下,也可以方便地将URL指向的WEB页面导出为PDF文件(调用浏览器的“打印为PDF功能”),调用如下方法即可:
// 导出指定元素为PDFfunc elementPDFPrint(urlstr, sel string, res *[]byte) cdp.Tasks {  var err error  return cdp.Tasks{    cdp.Navigate(urlstr),    cdp.Sleep(time.Duration(5) * time.Second),    cdp.ActionFunc(func(ctx context.Context) error {      // 获取pdf数据      *res, _, err = page.PrintToPDF().Do(ctx)      if err != nil {        return err      }      return nil    }),  }}

以360.cn网址为例,导出的PDF效果如下图所示

图片

使用下述方法可以截取全屏图片
func main() {  // 创建新的cdp上下文  ctx, cancel := cdp.NewContext(context.Background())  defer cancel()
// 此处以360官网首页为例 urlstr := `https://www.360.cn/` var buf []byte // 获取 png, quality=90 if err := cdp.Run(ctx, fullScreenshot(urlstr, 90, &buf)); err != nil { log.Fatal(err) } if err := ioutil.WriteFile("360_cn_full.png", buf, 0644); err != nil { log.Fatal(err) }}
func fullScreenshot(urlstr string, quality int64, res *[]byte) cdp.Tasks { return cdp.Tasks{ cdp.Navigate(urlstr), cdp.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 }), }}
以360.cn网址为例,截取全屏图片效果如下图所示

图片

模拟其他设备
chromedp支持对多种设备的模拟(通过修改浏览器User-Agent的方式),如下代码实现了模拟iPhone7的请求:
func main() {  // 创建新的cdp上下文  ctx, cancel := cdp.NewContext(context.Background())  defer cancel()  // run  var b []byte  if err := cdp.Run(ctx,    // 模拟 iPhone 7    cdp.Emulate(device.IPhone7landscape),    cdp.Navigate(`https://www.whatsmyua.info/`),    cdp.CaptureScreenshot(&b),  ); err != nil {    log.Fatal(err)  }  if err := ioutil.WriteFile("iphone7_ua.png", b, 0644); err != nil {    log.Fatal(err)  }}
效果如下

图片


组合操作
chromedp本身提供了Actions和Tasks数据结构,供用户把任意WEB操作动作组合在一起之后执行,具体可以查看github主页examples下面的例子,这里就不再赘述了。

写在最后

使用golang和chromedp框架,借助chrome的CDP接口,可以在headless chrome上实现浏览器的几乎所有操作。


在2.1的例子中,我们使用不到40行代码,就实现了服务端浏览器截屏功能。同时,除了chrome浏览器、golang SDK和chromedp本身的代码之外,没有引用其他代码或工具。

在上述测试结果的基础上,我们可以继续开发:

  • 封装便捷、通用的服务端页面 [截图/PDF导出] 工具,供其他产品项目使用。应用场景:
  • 前端开发了样式美观的图表,需要导出为图片或PDF作为邮件附件发送给用户查看;
  • 定时发送重要指标图表到用户邮箱,无需用户登陆系统查看数据
  • 开发高效的[WEB自动化测试平台],对WEB产品进行自动化测试。应用场景:
  • 线上环境升级后,自动运行测试用例,测试重点功能(登录,添加、查看测试数据,检查接口返回数据等)
10430golang实现服务端浏览器截屏

root

这个人很懒,什么都没留下

文章评论