20

golong实现服务端浏览器截屏

 2 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzUxMTcwOTM4Mg%3D%3D&%3Bmid=2247486592&%3Bidx=1&%3Bsn=33f1eec73d8779171c9dd32cf6224d89
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

aQb2Qrj.png!web

笔者说 :近期在使用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截图的效果如下所示

niyaaq6.png!web

导出PDF

在该框架下,也可以方便地将URL指向的WEB页面导出为PDF文件(调用浏览器的“打印为PDF功能”),调用如下方法即可:

// 导出指定元素为PDF

func 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效果如下图所示

qYzU7f6.png!web

使用下述方法可以截取全屏图片

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网址为例,截取全屏图片效果如下图所示

AN3Yjmq.png!web

模拟其他设备

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)

}

}

效果如下

73IRfya.png!web

组合操作

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

写在最后

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

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

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

  • 封装便捷、通用的服务端页面 [截图/PDF导出] 工具,供其他产品项目使用。应用场景:

  • 前端开发了样式美观的图表,需要导出为图片或PDF作为邮件附件发送给用户查看;

  • 定时发送重要指标图表到用户邮箱,无需用户登陆系统查看数据

  • 开发高效的[WEB自动化测试平台],对WEB产品进行自动化测试。应用场景:

  • 线上环境升级后,自动运行测试用例,测试重点功能(登录,添加、查看测试数据,检查接口返回数据等)

奇麟大数据 ,360大数据分舵,专注于大数据领域的技术、实践、运维、产品和数据使用方面的分享和交流

想要了解 大数据 方面知识的初学者,深入技术的研发或运维大神,寻找技术解决方案的传统企业主均可食用

快来留言板交流吧

rU3YnyM.png!web

点击阅读原文,关注 奇麟大数据

i6raArj.png!web
b6bMBjv.png!web

360技术公众号

技术干货|一手资讯|精彩活动

扫码关注我们


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK