360技术公众号曾经发布过一篇文章《服务端浏览器截屏》,文章中对基于Selenium(Python SDK)实现的服务端截屏技术进行了详细讲解,可操作性非常强。
笔者曾使用Python+Selenium实现过爬虫和简单的自动化测试功能,对上面文章中使用的技术和组件都有涉及,我认为初学者完全可以通过该文章手把手的教学,实现服务端截屏功能。
不过基于Selenium的截图实现存在如下一些缺点:
-
需要安装Selenium或是PhantomJS
-
PhantomJS已经停止维护了
-
Selenium的运行需要安装chromedriver
-
chromedriver对chrome的版本有一定要求
-
截图时需要使用html2canvas JS库
可见,整体环境的搭建(包括各个软件之间版本的匹配),还是需要花费不少时间和精力适配并测试的。因此原文章作者在上述文章的最后也提供了“集成Docker”的方案,在上述环境docker化后,可以解决环境部署的问题。
而基于golang实现的方案,可以完美解决上述问题。该方案对运行时环境的要求为仅安装chrome浏览器即可。
-
提供了更快,更简单的方式来驱动浏览器 -
提供了丰富的底层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),
}
}
// 导出指定元素为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
}),
}
}
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
}),
}
}
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)
}
}
使用golang和chromedp框架,借助chrome的CDP接口,可以在headless chrome上实现浏览器的几乎所有操作。
在2.1的例子中,我们使用不到40行代码,就实现了服务端浏览器截屏功能。同时,除了chrome浏览器、golang SDK和chromedp本身的代码之外,没有引用其他代码或工具。
在上述测试结果的基础上,我们可以继续开发:
-
封装便捷、通用的服务端页面 [截图/PDF导出] 工具,供其他产品项目使用。应用场景: -
前端开发了样式美观的图表,需要导出为图片或PDF作为邮件附件发送给用户查看; -
定时发送重要指标图表到用户邮箱,无需用户登陆系统查看数据 -
开发高效的[WEB自动化测试平台],对WEB产品进行自动化测试。应用场景: -
线上环境升级后,自动运行测试用例,测试重点功能(登录,添加、查看测试数据,检查接口返回数据等)
文章评论