“木偶”浏览器

2019 年 9 月 24 日

“木偶”浏览器

之前在团队内做了一次关于 Headless Browser 的分享,趁着周末,梳理成文字版本,主要内容涉及到 Selenium、PhantomJS、Puppeteer、Headeless Chrome。


“木偶”浏览器


受 Puppeteer 启发,我在这里造了一个词,“木偶”浏览器,指通过调用 API 来操控浏览器行为,我们可以在此基础上构建一套自动化系统,以此解放开发人员的双手,目前主要应用在下面几个场景中:


  • 页面自动化测试,产品上线前,进行一些重要路径的自动化测试

  • Javascript 库自动化自测,提供 JS 运行环境

  • 网页截图,在一些常见的前端监控系统中,网页出错了,通过这个功能进行网页截图记录

  • 爬虫,面对一些反爬虫系统,无头浏览器可以模拟用户访问进行爬去数据


实现木偶浏览器,目前有两种方案,一种是 Selenium,一种是以 PhantomJS 为代表的 Headles Browser(无头浏览器),两个方案,在使用方式和应用的方向都有所区别,Selenium 多应用在产品自动化测试,而无头浏览器则更多被用于应对反爬虫系统和网页截图。


两种木偶


Selenium


Jason Huggins 在 2014 年开发了 Selenium,作为 ThoughtWorks 的内部工具,之后 Paul Hammant 加入开发团队,领导开发了新一版操作逻辑,即经典的的 “Selenium Remote Control”(Selenium-RC),并选择在当年开源。


Selenium 出现前,市面上流行着 Mercury 公司的自动化功能测试软件 QTP,Selenium 的命名源于 Huggins 在邮件里开的一个玩笑,“you can cure mercury poisoning by taking selenium supplements”,你可以通过服用硒(selenium)补充剂来治疗汞(mercury)中毒。


2007 年 Huggins 加入 Google,继续 Selenium-RC 的开发维护,与此同时,还在 ThoughtWorks 的 Simon Stewart 开发出了大名鼎鼎的 WebDriver,接着就到了 2009 Selenium 与 WebDriver 的合并,开启 Selenium 2.0 时代,合并之后,Selenium-RC 和 WebDriver 共存,直到 2016 年,跳票 3 年之久的 Selenium 3.0 发布,Selenium-RC 被抛弃,项目完全投向 WebDriver API。


还有一个重要的时间点,2018 年 Philippe Hanrigou 开发了 “Selenium Grid”,使 Selenium 支持分布式执行测试,可以控制多台机器多个浏览器执行测试用例。


以 Selenium 2.0 为例,完整的组成结构应该是


Selenium = Selenium IDE + Selenium WebDriver + Selenium Remote Control + Selenium Grid


这里简要解释下这几个名词,配合下图(途中略去 RC 部分)食用效果更佳:



elenium IDE


Selenium IDE 是 firefox/Chrome 浏览器的插件,提供简单的脚本录制、编辑与回放功能。


Selenium Client API


Selenese 是 Selenium 的指令集,除了使用 Selenese,Selenium 还开放了编程语言调用接口,通过调用 Selenium Client API 中的方法与 WebDriver 进行通信,目前支持 JAVA、C#、Javascript、Python。


2.0 版本之后,引入了新的 Client API(以 WebDriver 为中心组件),不过仍向下兼容。


Selenium Grid


Grid 上文已经介绍了,用于对测试脚本进行分布式处理,目前已经集成到 Selenium Server 中。


WebDriver & Selenium-RC


RC 通过 Javascript 驱动网页,这使得整个过程与网页的内容高度耦合,得益于此,Selenium 也是第一批支持 Ajax 和一些高动态网页的自动化测试工具之一,同时,一个绕不过的问题,自动化代码运行在 Javascript 沙箱内,这就要求 Selenium-RC 服务必须跟对应网页保持同源。


而 WebDriver 则是通过浏览器原生接口协议驱动浏览器,并且开放了不同语言对应的 API,虽然浏览器、不同编程语言的适配会耗费很长的人力、时间成本,但带来的是 RC 无可替代的使用体验。


WebDriver 和 Selenium 合并,WebDriver 解决了 RC 绕不过 JS 沙箱问题,带来了更友好的 API,同时,WebDriver 也得支持更多的浏览器。


Headles Browser


关于无头浏览器,这里分别介绍下 PhantomJS 和 Headless Chrome。


PhantomJS


2011 年 1 月 23 号,Ariya Hidayat 发布了 PhantomJS,这是真正意义上第一个无头浏览器。


PhantomJS 基于 webkit 内核打造,并且提供一系列的 Javascript API 供开发者操控浏览器行为,项目发布后,一石激起千层浪,到目前为止,已经被上千家组织或公司使用,并在原有的基础上衍生出了 CasperJS 和 Yslow 两个项目


这样的场景直到 2017 年被打破,Chrome 59 宣布支持在 headless 环境下运行 Chrome,同时因为长期缺乏代码提交,2018 年 5 月 4 号,Ariya 宣布了暂停项目的开发维护,版本号最终停在 2.1.1。


Headless Chrome


上面也提到,2017 年 Chrome 开始支持 headless 环境,紧接着,用于控制 Chrome 的 Node 库开源项目 Chromeless 出现,不过随着 Chrome 团队发布了官方库 Puppeteer,Chromeless 作者宣布项目停止开发,并建议用户迁移 Puppeteer。


读到这里,关于这两种无头浏览器的关系,你可能有些疑惑,主要的区别是 PhantomJS 集成了 浏览器(webkit)和 API,而 Google 的做法则是将浏览器(Chrome)和 Node API(pupteer-core) 独立,可以用下面这个等式表示:


Chrome + Puppeteer-core/Chromeless = PhantomJS


Puppeteer、Puppeteer-core、Chrome 和 PhantomJS 的关系如下:


Puppeteer = Puppeteer-core + Chromium = PhantomJS


提线木偶


PhantomJS


举个例子,下面是使用 PhantomJS 访问掘金首页并截图的代码:


 1  // 为了方便演示,我们引入 npm phantom 包 2  const phantom = require('phantom') 3  const log = console.log 4  const imgPath = './capture.jpg' 5  const targetUrl = 'https://juejin.im/' 6  const picSet = { 7    format: 'jpg', 8    qualiity: '80' 9  }10  const getTime = () => {11    return (new Date()).getTime()12  }13  const genPic = async () => {14    let start = getTime()15    const instance = await phantom.create()16    const page = await instance.createPage()17    await page.on('onResourceRequested', function(requestData) {18      log('Requesting =>> ', requestData.url)19    })20    const status = await page.open(targetUrl)21    log('page download: ', (getTime() - start))22    page.property('viewportSize', {width: 375*2, height: 627*2})23    page.property('zoomFactor', 2)24    log(status)25    // const content = await page.property('content')26    // console.log(content)27    await page.render(imgPath, picSet)28    log('pic rendered: ', (getTime() - start))29    await instance.exit()30  }31  genPic()
复制代码


Puppeteer


Puppeteer 基于 DevTools Protocol 控制 Chrome 或 Chromium,安装时,默认下载最新版本的 Chromium,不过从 1.7 版本开始,官方提供了 puppeteer-core,不会下载 Chromium 包的版本。


网页截图 Puppeteer 版本:


 1  const puppeteer = require('puppeteer') 2  const shotSet = { 3    path: './screenshot.png', 4    fullPage: true 5  } 6  const viewPortSet = { 7    width: 375, 8    height: 627, 9    isMobile: true,10    deviceScaleFactor: 111  }12  const genPic = async () => {13    const browser = await puppeteer.launch()14    const page = await browser.newPage()15    await page.setViewport(viewPortSet)16    await page.goto('https://juejin.im/')17    console.log(await page.content())18    await page.screenshot(shotSet)19    await browser.close()20  }21  genPic()
复制代码


Puppeteer 操控 GUI Chrome 访问 juejin.im


 1  const search = async () => { 2    const browser = await puppeteer.launch({ 3      headless: false, 4      timeout: 30000 5    }) 6    const page = await browser.newPage() 7    // viewPortSet 参见上一段截图代码 8    await page.setViewport(viewPortSet) 9    await page.goto('https://juejin.im/')10    let menu = '.main-nav-list'11    await page.click(menu)12    let pin = '.pin'13    await page.waitForSelector(pin)14    await page.click(pin)15    let login = '.nav-item.auth'16    await page.waitForSelector(login)17    await page.click(login)18    await page.type('[name="loginPhoneOrEmail"]', 'xxxxxxxxxx@xx.com')19    await page.type('[name="loginPassword"]', 'xxxxxxxxxx')20    let loginBtn = '.panel .btn'21    await page.waitForSelector(loginBtn)22    await page.click(loginBtn)23    await browser.close()24  }25  search()
复制代码


因为脚本控制,速度很快,我在关键操作后增加了延时,录了个 gif,效果如下,有兴趣的同学可以直接拷贝代码,安装依赖之后即可执行:



Selenium WebDriver VS Puppeteer


上文已经对 WebDriver、Puppeteer 的概念做了阐述,那么两者的区别在哪?


实际上,WebDriver 是 Selenium 根据不同的浏览器(Chrome、Safari、IE、Firefox)的接口定制的规范统称,面对不同浏览器,使用的 Driver 不同,官网目前提供了 IE Driver、Safari Driver、Chrome Driver、Firefox Driver,可以通过下图加深理解,以 Chrome 为例,Puppeteer-core 和 ChromeDriver 都是通过 devtools-protocol 控制浏览器,区别是 Puppeteer-core 是开发者直接可用的 Node 库,ChromeDriver 则需要用户通过 Selenium Client API 进行调用。



作者介绍:


作者阿苏勒(企业代号名),目前负责贝壳找房增长策略方向的相关工作。


本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。


原文链接:


https://mp.weixin.qq.com/s/55IrHW1V79sC98BD8wtNlA


2019 年 9 月 24 日 12:01270

评论

发布
暂无评论
发现更多内容

简述 CAP 原理

不在调上

week6 总结

不在调上

redis系列之——一致性hash算法

诸葛小猿

redis 一致性hash redis集群

数据加工

阡陌r

图解SQL Server 2008 R2安装

JackWangGeek

sql

当我们在谈架构时,我们谈的是什么?

Winfield

架构 企业架构 系统架构

因为 Django ORM update,我今天差点「从删库到跑路」

AlwaysBeta

数据库 django 编程 程序员

Mybatis执行流程浅析(附深度文章推荐&面试题集锦)

Kerwin

Java mybatis

没内鬼,来点干货!SQL优化和诊断

Kerwin

MySQL

没内鬼,来点干货!volatile和synchronized

Kerwin

Java volatile synchronized

疫情年逆风翻盘

Kerwin

程序员

架构师训练营 - 第六周 - 学习总结

韩挺

程序员的时间管理

Kerwin

程序员

6 个珍藏已久 IDEA 小技巧,这一波全部分享给你!

楼下小黑哥

Java IDEA

架构师训练营 - 第六周 - 作业

韩挺

ARTS - Week 5

Khirye

ARTS 打卡计划

【计算机网络】你需要知道的链路层知识

烫烫烫个喵啊

计算机网络 链路层 交换机

SpringBoot代码生成器

Kerwin

Java 开源

有朋自远方来 不亦说乎,请多关照哈

InfoQ_353bc6b96230

SharePoint 部署架构

JackWangGeek

架构 BI SharePoint

nginx在重定向时端口出错的问题

烫烫烫个喵啊

nginx

Hello!GitHub 好用好玩值得收藏的开源项目集合~

Kerwin

开源

week6

Geek_2e7dd7

Elasticsearch从入门到放弃:再聊搜索

Jackey

elasticsearch

图解SharePoint Server 2010安装

JackWangGeek

SharePoint

关于如何判断一个list是否为空的思考

Leetao

Python Python基础知识 列表

第六周作业

赵龙

week6 学习总结

Geek_2e7dd7

设计模式总篇:从为什么需要原则到实际落地(附知识图谱)

Kerwin

Java 设计模式

日记一则

progyoung

图解Windows Server 2008 R2安装

JackWangGeek

windows-server

“木偶”浏览器-InfoQ