以前在團隊內作了一次關於 Headless Browser 的分享,趁着週末,梳理成文字版本,主要內容涉及到 Selenium、PhantomJS、Puppeteer、Headeless Chrome。html
內容有誤的地方,歡迎指正前端
受 Puppeteer 啓發,我在這裏造了一個詞,「木偶」瀏覽器,指經過調用 API 來操控瀏覽器行爲,咱們能夠在此基礎上構建一套自動化系統,以此解放開發人員的雙手,目前主要應用在下面幾個場景中:git
實現木偶瀏覽器,目前有兩種方案,一種是 Selenium,一種是以 PhantomJS 爲表明的 Headles Browser(無頭瀏覽器),兩個方案,在使用方式和應用的方向都有所區別,Selenium 多應用在產品自動化測試,而無頭瀏覽器則更多被用於應對反爬蟲系統和網頁截圖。github
Jason Huggins 在 2014 年開發了 Selenium,做爲 ThoughtWorks 的內部工具,以後 Paul Hammant 加入開發團隊,領導開發了新一版操做邏輯,即經典的的 "Selenium Remote Control"(Selenium-RC),並選擇在當年開源。web
Selenium 出現前,市面上流行着 Mercury 公司的自動化功能測試軟件 QTP,Selenium 的命名源於 Huggins 在郵件裏開的一個玩笑,「you can cure mercury poisoning by taking selenium supplements」,你能夠經過服用硒(selenium)補充劑來治療汞(mercury)中毒。chrome
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。npm
還有一個重要的時間點,2018 年 Philippe Hanrigou 開發了 「Selenium Grid」,使 Selenium 支持分佈式執行測試,能夠控制多臺機器多個瀏覽器執行測試用例。編程
以 Selenium 2.0 爲例,完整的組成結構應該是瀏覽器
Selenium = Selenium IDE + Selenium WebDriver + Selenium Remote Control + Selenium Grid
less
這裏簡要解釋下這幾個名詞,配合下圖(途中略去 RC 部分)食用效果更佳:
Selenium IDE 是 firefox/Chrome 瀏覽器的插件,提供簡單的腳本錄製、編輯與回放功能。
Selenese 是 Selenium 的指令集,除了使用 Selenese,Selenium 還開放了編程語言調用接口,經過調用 Selenium Client API 中的方法與 WebDriver 進行通訊,目前支持 JAVA
、C#
、Javascript
、Python
。
2.0 版本以後,引入了新的 Client API(以 WebDriver 爲中心組件),不過仍向下兼容。
Grid 上文已經介紹了,用於對測試腳本進行分佈式處理,目前已經集成到 Selenium Server 中。
RC 經過 Javascript 驅動網頁,這使得整個過程與網頁的內容高度耦合,得益於此,Selenium 也是第一批支持 Ajax 和一些高動態網頁的自動化測試工具之一,同時,一個繞不過的問題,自動化代碼運行在 Javascript 沙箱內,這就要求 Selenium-RC 服務必須跟對應網頁保持同源。
而 WebDriver 則是經過瀏覽器原生接口協議驅動瀏覽器,而且開放了不一樣語言對應的 API,雖然瀏覽器、不一樣編程語言的適配會耗費很長的人力、時間成本,但帶來的是 RC 無可替代的使用體驗。
WebDriver 和 Selenium 合併,WebDriver 解決了 RC 繞不過 JS 沙箱問題,帶來了更友好的 API,同時,WebDriver 也得支持更多的瀏覽器。
關於無頭瀏覽器,這裏分別介紹下 PhantomJS 和 Headless Chrome。
2011 年 1 月 23 號,Ariya Hidayat 發佈了 PhantomJS,這是真正意義上第一個無頭瀏覽器。
PhantomJS 基於 webkit 內核打造,而且提供一系列的 Javascript API 供開發者操控瀏覽器行爲,項目發佈後,一石激起千層浪,到目前爲止,已經被上千家組織或公司使用,並在原有的基礎上衍生出了 CasperJS 和 Yslow 兩個項目
這樣的場景直到 2017 年被打破,Chrome 59 宣佈支持在 headless 環境下運行 Chrome,同時由於長期缺少代碼提交,2018 年 5 月 4 號,Ariya 宣佈了暫停項目的開發維護,版本號最終停在 2.1.1
。
上面也提到,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 訪問掘金首頁並截圖的代碼:
// 爲了方便演示,咱們引入 npm phantom 包
const phantom = require('phantom')
const log = console.log
const imgPath = './capture.jpg'
const targetUrl = 'https://juejin.im/'
const picSet = {
format: 'jpg',
qualiity: '80'
}
const getTime = () => {
return (new Date()).getTime()
}
const genPic = async () => {
let start = getTime()
const instance = await phantom.create()
const page = await instance.createPage()
await page.on('onResourceRequested', function(requestData) {
log('Requesting =>> ', requestData.url)
})
const status = await page.open(targetUrl)
log('page download: ', (getTime() - start))
page.property('viewportSize', {width: 375*2, height: 627*2})
page.property('zoomFactor', 2)
log(status)
// const content = await page.property('content')
// console.log(content)
await page.render(imgPath, picSet)
log('pic rendered: ', (getTime() - start))
await instance.exit()
}
genPic()
複製代碼
Puppeteer 基於 DevTools Protocol 控制 Chrome 或 Chromium,安裝時,默認下載最新版本的 Chromium,不過從 1.7
版本開始,官方提供了 puppeteer-core
,不會下載 Chromium 包的版本。
網頁截圖 Puppeteer 版本:
const puppeteer = require('puppeteer')
const shotSet = {
path: './screenshot.png',
fullPage: true
}
const viewPortSet = {
width: 375,
height: 627,
isMobile: true,
deviceScaleFactor: 1
}
const genPic = async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.setViewport(viewPortSet)
await page.goto('https://juejin.im/')
console.log(await page.content())
await page.screenshot(shotSet)
await browser.close()
}
genPic()
複製代碼
Puppeteer 操控 GUI Chrome 訪問 juejin.im
:
const search = async () => {
const browser = await puppeteer.launch({
headless: false,
timeout: 30000
})
const page = await browser.newPage()
// viewPortSet 參見上一段截圖代碼
await page.setViewport(viewPortSet)
await page.goto('https://juejin.im/')
let menu = '.main-nav-list'
await page.click(menu)
let pin = '.pin'
await page.waitForSelector(pin)
await page.click(pin)
let login = '.nav-item.auth'
await page.waitForSelector(login)
await page.click(login)
await page.type('[name="loginPhoneOrEmail"]', 'xxxxxxxxxx@xx.com')
await page.type('[name="loginPassword"]', 'xxxxxxxxxx')
let loginBtn = '.panel .btn'
await page.waitForSelector(loginBtn)
await page.click(loginBtn)
await browser.close()
}
search()
複製代碼
由於腳本控制,速度很快,我在關鍵操做後增長了延時,錄了個 gif,效果以下,有興趣的同窗能夠直接拷貝代碼,安裝依賴以後便可執行:
上文已經對 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 進行調用
原文地址:「木偶」瀏覽器-Leeon
參考: