摘要:獲取獲取上下文句柄執(zhí)行計(jì)算銷毀句柄除此之外,還可以使用意為在瀏覽器環(huán)境執(zhí)行腳本,可傳入第二個(gè)參數(shù)作為句柄,而則針對(duì)選中的一個(gè)元素執(zhí)行操作。
我們?nèi)粘J褂脼g覽器或者說(shuō)是有頭瀏覽器時(shí)的步驟為:?jiǎn)?dòng)瀏覽器、打開(kāi)一個(gè)網(wǎng)頁(yè)、進(jìn)行交互。
無(wú)頭瀏覽器指的是我們使用腳本來(lái)執(zhí)行以上過(guò)程的瀏覽器,能模擬真實(shí)的瀏覽器使用場(chǎng)景。
有了無(wú)頭瀏覽器,我們就能做包括但不限于以下事情:
對(duì)網(wǎng)頁(yè)進(jìn)行截圖保存為圖片或 pdf
抓取單頁(yè)應(yīng)用(SPA)執(zhí)行并渲染(解決傳統(tǒng) HTTP 爬蟲(chóng)抓取單頁(yè)應(yīng)用難以處理異步請(qǐng)求的問(wèn)題)
做表單的自動(dòng)提交、UI的自動(dòng)化測(cè)試、模擬鍵盤輸入等
用瀏覽器自帶的一些調(diào)試工具和性能分析工具幫助我們分析問(wèn)題
在最新的無(wú)頭瀏覽器環(huán)境里做測(cè)試、使用最新瀏覽器特性
寫爬蟲(chóng)做你想做的事情(奸笑
無(wú)頭瀏覽器很多,包括但不限于:
PhantomJS, 基于 Webkit
SlimerJS, 基于 Gecko
HtmlUnit, 基于 Rhnio
TrifleJS, 基于 Trident
Splash, 基于 Webkit
這里主要介紹 Google 提供的無(wú)頭瀏覽器(headless Chrome), 他基于 Chrome DevTools protocol 提供了不少高度封裝的接口方便我們控制瀏覽器。
簡(jiǎn)單的代碼示例啟動(dòng)/關(guān)閉瀏覽器、打開(kāi)頁(yè)面為了能使用 async/await 等新特性,需要使用 v7.6.0 或更高版本的 Node.
// 啟動(dòng)瀏覽器 const browser = await puppeteer.launch({ // 關(guān)閉無(wú)頭模式,方便我們看到這個(gè)無(wú)頭瀏覽器執(zhí)行的過(guò)程 // headless: false, timeout: 30000, // 默認(rèn)超時(shí)為30秒,設(shè)置為0則表示不設(shè)置超時(shí) }); // 打開(kāi)空白頁(yè)面 const page = await browser.newPage(); // 進(jìn)行交互 // ... // 關(guān)閉瀏覽器 // await browser.close();設(shè)置頁(yè)面視窗大小
// 設(shè)置瀏覽器視窗 page.setViewport({ width: 1376, height: 768, });輸入網(wǎng)址
// 地址欄輸入網(wǎng)頁(yè)地址 await page.goto("https://google.com/", { // 配置項(xiàng) // waitUntil: "networkidle", // 等待網(wǎng)絡(luò)狀態(tài)為空閑的時(shí)候才繼續(xù)執(zhí)行 });保存網(wǎng)頁(yè)為圖片
打開(kāi)一個(gè)網(wǎng)頁(yè),然后截圖保存到本地:
await page.screenshot({ path: "path/to/saved.png", });
完整示例代碼
保存網(wǎng)頁(yè)為 pdf打開(kāi)一個(gè)網(wǎng)頁(yè),然后保存 pdf 到本地:
await page.pdf({ path: "path/to/saved.pdf", format: "A4", // 保存尺寸 });
完整示例代碼
執(zhí)行腳本要獲取打開(kāi)的網(wǎng)頁(yè)中的宿主環(huán)境,我們可以使用 Page.evaluate 方法:
// 獲取視窗信息 const dimensions = await page.evaluate(() => { return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, deviceScaleFactor: window.devicePixelRatio }; }); console.log("視窗信息:", dimensions); // 獲取 html // 獲取上下文句柄 const htmlHandle = await page.$("html"); // 執(zhí)行計(jì)算 const html = await page.evaluate(body => body.outerHTML, htmlHandle); // 銷毀句柄 await htmlHandle.dispose(); console.log("html:", html);
Page.$ 可以理解為我們常用的 document.querySelector, 而 Page.$$ 則對(duì)應(yīng) document.querySelectorAll。
完整示例代碼
自動(dòng)提交表單打開(kāi)谷歌首頁(yè),輸入關(guān)鍵字,回車進(jìn)行搜索:
// 地址欄輸入網(wǎng)頁(yè)地址 await page.goto("https://google.com/", { waitUntil: "networkidle", // 等待網(wǎng)絡(luò)狀態(tài)為空閑的時(shí)候才繼續(xù)執(zhí)行 }); // 聚焦搜索框 // await page.click("#lst-ib"); await page.focus("#lst-ib"); // 輸入搜索關(guān)鍵字 await page.type("辣子雞", { delay: 1000, // 控制 keypress 也就是每個(gè)字母輸入的間隔 }); // 回車 await page.press("Enter");
完整示例代碼
復(fù)雜點(diǎn)的代碼示例每一個(gè)簡(jiǎn)單的動(dòng)作連接起來(lái),就是一連串復(fù)雜的交互,接下來(lái)我們看兩個(gè)更具體的示例。
抓取單頁(yè)應(yīng)用: 模擬餓了么外賣下單傳統(tǒng)的爬蟲(chóng)是基于 HTTP 協(xié)議,模擬 UserAgent 發(fā)送 http 請(qǐng)求,獲取到 html 內(nèi)容后使用正則解析出需要抓取的內(nèi)容,這種方式面對(duì)服務(wù)端渲染直出 html 的網(wǎng)頁(yè)時(shí)非常便捷。
但遇到單頁(yè)應(yīng)用(SPA)時(shí),或遇到登錄校驗(yàn)時(shí),這種爬蟲(chóng)就顯得比較無(wú)力。
而使用無(wú)頭瀏覽器,抓取網(wǎng)頁(yè)時(shí)完全使用了人機(jī)交互時(shí)的操作,所以頁(yè)面的初始化完全能使用宿主瀏覽器環(huán)境渲染完備,不再需要關(guān)心這個(gè)單頁(yè)應(yīng)用在前端初始化時(shí)需要涉及哪些 HTTP 請(qǐng)求。
無(wú)頭瀏覽器提供的各種點(diǎn)擊、輸入等指令,完全模擬人的點(diǎn)擊、輸入等指令,也就再也不用擔(dān)心正則寫不出來(lái)了啊哈哈哈
當(dāng)然,有些場(chǎng)景下,使用傳統(tǒng)的 HTTP 爬蟲(chóng)(寫正則匹配) 還是比較高效的。
在這里就不再詳細(xì)對(duì)比這些差異了,以下這個(gè)例子僅作為展示模擬一個(gè)完整的人機(jī)交互:使用移動(dòng)版餓了么點(diǎn)外賣。
先看下效果:
代碼比較長(zhǎng)就不全貼了,關(guān)鍵是幾行:
const puppeteer = require("puppeteer"); const devices = require("puppeteer/DeviceDescriptors"); const iPhone6 = devices["iPhone 6"]; console.log("啟動(dòng)瀏覽器"); const browser = await puppeteer.launch(); console.log("打開(kāi)頁(yè)面"); const page = await browser.newPage(); // 模擬移動(dòng)端設(shè)備 await page.emulate(iPhone6); console.log("地址欄輸入網(wǎng)頁(yè)地址"); await page.goto(url); console.log("等待頁(yè)面準(zhǔn)備好"); await page.waitForSelector(".search-wrapper .search"); console.log("點(diǎn)擊搜索框"); await page.tap(".search-wrapper .search"); await page.type("麥當(dāng)勞", { delay: 200, // 每個(gè)字母之間輸入的間隔 }); console.log("回車開(kāi)始搜索"); await page.tap("button"); console.log("等待搜素結(jié)果渲染出來(lái)"); await page.waitForSelector("[class^="index-container"]"); console.log("找到搜索到的第一家外賣店!"); await page.tap("[class^="index-container"]"); console.log("等待菜單渲染出來(lái)"); await page.waitForSelector("[class^="fooddetails-food-panel"]"); console.log("直接選一個(gè)菜品吧"); await page.tap("[class^="fooddetails-cart-button"]"); // console.log("===為了看清楚,傲嬌地等兩秒==="); await page.waitFor(2000); await page.tap("[class^=submit-btn-submitbutton]"); // 關(guān)閉瀏覽器 await browser.close();
關(guān)鍵步驟是:
加載頁(yè)面
等待需要點(diǎn)擊的 DOM 渲染出來(lái)后點(diǎn)擊
繼續(xù)等待下一步需要點(diǎn)擊的 DOM 渲染出來(lái)再點(diǎn)擊
關(guān)鍵的幾個(gè)指令:
page.tap(或 page.click) 為點(diǎn)擊
page.waitForSelector 意思是等待指定元素出現(xiàn)在網(wǎng)頁(yè)中,如果已經(jīng)出現(xiàn)了,則立即繼續(xù)執(zhí)行下去, 后面跟的參數(shù)為 selector 選擇器,與我們常用的 document.querySelector 接收的參數(shù)一致
page.waitFor 后面可以傳入 selector 選擇器、function 函數(shù)或 timeout 毫秒時(shí)間,如 page.waitFor(2000) 指等待2秒再繼續(xù)執(zhí)行,例子中用這個(gè)函數(shù)暫停操作主要是為了演示
以上幾個(gè)指令都可接受一個(gè) selector 選擇器作為參數(shù),這里額外介紹幾個(gè)方法:
page.$(selector) 與我們常用的 document.querySelector(selector) 一致,返回的是一個(gè) ElementHandle 元素句柄
page.$$(selector) 與我們常用的 document.querySelectorAll(selector) 一致,返回的是一個(gè)數(shù)組
在有頭瀏覽器上下文中,我們選擇一個(gè)元素的方法是:
const body = document.querySelector("body"); const bodyInnerHTML = body.innerHTML; console.log("bodyInnerHTML: ", bodyInnerHTML);
而在無(wú)頭瀏覽器里,我們首先需要獲取一個(gè)句柄,通過(guò)句柄獲取到環(huán)境中的信息后,銷毀這個(gè)句柄。
// 獲取 html // 獲取上下文句柄 const bodyHandle = await page.$("body"); // 執(zhí)行計(jì)算 const bodyInnerHTML = await page.evaluate(dom => dom.innerHTML, bodyHandle); // 銷毀句柄 await bodyHandle.dispose(); console.log("bodyInnerHTML:", bodyInnerHTML);
除此之外,還可以使用 page.$eval:
const bodyInnerHTML = await page.$eval("body", dom => dom.innerHTML); console.log("bodyInnerHTML: ", bodyInnerHTML);
page.evaluate 意為在瀏覽器環(huán)境執(zhí)行腳本,可傳入第二個(gè)參數(shù)作為句柄,而 page.$eval 則針對(duì)選中的一個(gè) DOM 元素執(zhí)行操作。
完整示例代碼
導(dǎo)出批量網(wǎng)頁(yè):下載圖靈圖書我在 圖靈社區(qū) 上買了不少電子書,以前支持推送到 mobi 格式到 kindle 或推送 pdf 格式到郵箱進(jìn)行閱讀,不過(guò)經(jīng)常會(huì)關(guān)閉這些推送渠道,只能留在網(wǎng)頁(yè)上看書。
對(duì)我來(lái)說(shuō)不是很方便,而這些書籍的在線閱讀效果是服務(wù)器渲染出來(lái)的(帶了大量標(biāo)簽,無(wú)法簡(jiǎn)單抽取出好的排版),最好的方式當(dāng)然是直接在線閱讀并保存為 pdf 或圖片了。
借助瀏覽器的無(wú)頭模式,我寫了個(gè)簡(jiǎn)單的下載已購(gòu)買書籍為 pdf 到本地的腳本,支持批量下載已購(gòu)買的書籍。
使用方法,傳入帳號(hào)密碼和保存路徑,如:
$ node ./demo/download-ituring-books.js "用戶名" "密碼" "./books"
注意:puppeteer 的 Page.pdf() 目前僅支持在無(wú)頭模式中使用,所以要想看有頭狀態(tài)的抓取過(guò)程的話,執(zhí)行到 Page.pdf() 這步會(huì)先報(bào)錯(cuò):
所以啟動(dòng)這個(gè)腳本時(shí),需要保持無(wú)頭模式:
const browser = await puppeteer.launch({ // 關(guān)閉無(wú)頭模式,方便我們看到這個(gè)無(wú)頭瀏覽器執(zhí)行的過(guò)程 // 注意若調(diào)用了 Page.pdf 即保存為 pdf,則需要保持為無(wú)頭模式 // headless: false, });
看下執(zhí)行效果:
我的書架里有20多本書,下載完后是這樣子:
完整示例代碼
無(wú)頭瀏覽器還能做什么?無(wú)頭瀏覽器說(shuō)白了就是能模擬人工在有頭瀏覽器中的各種操作,那自然很多人力活,都能使用無(wú)頭瀏覽器來(lái)做(比如上面這個(gè)下載 pdf 的過(guò)程,其實(shí)是人力打開(kāi)每一個(gè)文章頁(yè)面,然后按 ctrl+p 或 command+p 保存到本地的自動(dòng)化過(guò)程)。
那既然用自動(dòng)化工具能解決的事情,就不應(yīng)該浪費(fèi)重復(fù)的人力勞動(dòng)了,所以我們還可以做:
自動(dòng)化工具
如自動(dòng)提交表單,自動(dòng)下載
自動(dòng)化 UI 測(cè)試
如記錄下正確 DOM 結(jié)構(gòu)或截圖,然后自動(dòng)執(zhí)行指定操作后,檢查 DOM 結(jié)構(gòu)或截圖是否匹配(UI 斷言)
定時(shí)監(jiān)控工具
如定時(shí)截圖發(fā)周報(bào),或定時(shí)巡查重要業(yè)務(wù)路徑下的頁(yè)面是否處于可用狀態(tài),配合郵件告警
爬蟲(chóng)
如傳統(tǒng) HTTP 爬蟲(chóng)怕不到的地方,就可配合無(wú)頭瀏覽器渲染能力來(lái)做
etc
感謝閱讀!
(全文完)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/89179.html
摘要:抓取并生成預(yù)先呈現(xiàn)的內(nèi)容即。自動(dòng)表單提交,測(cè)試,鍵盤輸入等。創(chuàng)建一個(gè)最新的自動(dòng)化測(cè)試環(huán)境。使用最新的的和瀏覽器功能,直接在最新版本的瀏覽器中運(yùn)行測(cè)試。捕獲您網(wǎng)站的時(shí)間線跟蹤,以幫助診斷性能問(wèn)題。 木偶 Puppeteer 更友好的 Headless Chrome Node API木偶也是有心的 (=?ω?=) showImg(https://segmentfault.com/img/b...
摘要:首先介紹是一個(gè)庫(kù),他提供了一組用來(lái)操縱的默認(rèn)也就是無(wú)的,也可以配置為有有點(diǎn)類似于,但是官方團(tuán)隊(duì)進(jìn)行維護(hù)的,前景更好。使用,相當(dāng)于同時(shí)具有和的能力,應(yīng)用場(chǎng)景會(huì)非常多。 首先介紹Puppeteer Puppeteer是一個(gè)node庫(kù),他提供了一組用來(lái)操縱Chrome的API(默認(rèn)headless也就是無(wú)UI的chrome,也可以配置為有UI) 有點(diǎn)類似于PhantomJS,但Puppet...
摘要:前言年月號(hào)微信小程序正式上線,小程序不需要安裝就能使用,依托微信強(qiáng)大的生態(tài)環(huán)境,能做到很多所不能做的事情。當(dāng)然更希望的是小程序官方能給出相應(yīng)的單元測(cè)試方案吧。 前言 2017年1月9號(hào)微信小程序正式上線,小程序不需要安裝就能使用,依托微信強(qiáng)大的生態(tài)環(huán)境,能做到很多H5所不能做的事情。從微信小程序發(fā)布這段時(shí)間,陸陸續(xù)續(xù)開(kāi)發(fā)了不少小程序相關(guān)的項(xiàng)目,總結(jié)了一些通用性的組件,但是對(duì)于小程序如何...
摘要:技術(shù)縱橫調(diào)試指南協(xié)議是新加入的調(diào)試協(xié)議,通過(guò)與交互,同時(shí)基于瀏覽器的提供了圖形化的調(diào)試界面。使得多業(yè)務(wù)線在復(fù)雜架構(gòu)情況下能夠獨(dú)立開(kāi)發(fā)測(cè)試,互不干擾,并統(tǒng)一調(diào)用接口。技術(shù)周刊由小組出品,匯聚一周好文章,周刊原文。 本期推薦 寫在 2017 的前端數(shù)據(jù)層不完全指北 在前端技術(shù)的發(fā)展中,各個(gè)層面演進(jìn)出不同的技術(shù)方案,如數(shù)據(jù)類型層面的 TypeScript,F(xiàn)low,PropTypes,應(yīng)用架...
摘要:前端日?qǐng)?bào)精選無(wú)頭瀏覽器初探鼠標(biāo)無(wú)限移動(dòng)簡(jiǎn)介譯深入分析變更檢測(cè)發(fā)布前必須排查的安全如何開(kāi)發(fā)中文第期關(guān)鍵和減少阻塞渲染的的自動(dòng)化解決方案譯網(wǎng)頁(yè)設(shè)計(jì)掘金年最受歡迎的個(gè)編程挑戰(zhàn)網(wǎng)站簡(jiǎn)書系列和深入理解掘金發(fā)布后臺(tái)管理系統(tǒng),沒(méi)錯(cuò),它就是你想 2017-10-18 前端日?qǐng)?bào) 精選 無(wú)頭瀏覽器 Puppeteer 初探鼠標(biāo)無(wú)限移動(dòng) JS API Pointer Lock簡(jiǎn)介[譯] 深入分析 Angul...
閱讀 2932·2023-04-26 01:49
閱讀 2066·2021-10-13 09:39
閱讀 2278·2021-10-11 11:09
閱讀 923·2019-08-30 15:53
閱讀 2816·2019-08-30 15:44
閱讀 916·2019-08-30 11:12
閱讀 2966·2019-08-29 17:17
閱讀 2371·2019-08-29 16:57