從Node.js中以程式化方式控制Chrome。
Puppeteer是一個我們可以使用的Node庫,用於控制一個無界面Chrome實例。我們基本上是使用Chrome,但以JavaScript進行程式化操作。
使用它,我們可以:
-
爬取網頁
-
自動化表單提交
-
執行任何種類的瀏覽器自動化
-
追蹤頁面加載性能
-
創建使用服務器端渲染的單頁應用程序的版本
-
生成網頁的截圖
-
創建自動化測試
-
從網頁生成PDF文件
它是由Google開發的。它本身並不會解鎖任何新功能,但它將我們需要處理的許多細節抽象出來,而不使用它。
簡而言之,它讓事情變得非常簡單。
由於它在初始化時啟動一個新的Chrome實例,它可能不是最高效的。然而,它是使用實際瀏覽器(在幕後)進行自動測試的最準確的方法。
準確地說,它使用的是Chromium,也就是Chrome的開源部分。這意味著您沒有Google許可的專有編解碼器(MP3,AAC,H.264等),也沒有與Google服務(如崩潰報告,Google更新等)的集成,但從編程的角度來看,它應該與Chrome完全相似(除了媒體播放,如前所述)。
安裝Puppeteer
首先要使用以下命令安裝:
npm install puppeteer
這將下載並打包最新版本的Chromium。
您也可以選擇安裝puppeteer-core
,讓puppeteer運行您已安裝的本地Chrome。在某些特殊情況下這很有用(參見[puppeteer vs puppeteer-core](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteer-vs-puppeteer-core))。通常,您只需選擇puppeteer
。
使用Puppeteer
在一個Node.js文件中,引入它:
const puppeteer = require('puppeteer');
然後,我們可以使用launch()
方法創建一個瀏覽器實例:
(async () => {
const browser = await puppeteer.launch()
})()
我們也可以這樣寫:
puppeteer.launch().then(async browser => {
//...
})
您可以將選項對像傳遞給puppeteer.launch()
。最常見的選項是:
puppeteer.launch({ headless:false })
以在Puppeteer執行操作時顯示Chrome。這樣可以方便地查看正在發生的事情並進行調試。
我們使用await
,所以我們必須將此方法調用包裹在一個異步函數中,我們會立即調用它(/ javascript-iife /)。
接下來,我們可以使用browser
對像上的newPage()
方法獲取page
對象:
(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
})()
隨後,我們調用page
對象上的goto()
方法來加載該頁面:
(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://website.com')
})()
我們也可以使用promise而不是async / await,但使用後者會使代碼讀起來更加清晰:
(() => {
puppeteer.launch().then(browser => {
browser.newPage().then(page => {
page.goto('https://website.com').then(() => {
//...
})
})
})
})()
獲取頁面內容
在頁面加載了URL後,我們可以使用page
的evaluate()
方法來獲取頁面的內容:
(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://website.com')
const result = await page.evaluate(() => {
//...
})
})()
此方法接受一個回調函數,我們可以在其中添加所需的代碼來檢索頁面上需要的元素。然後,我們返回一個新對象,這將成為我們的evaluate()
方法調用的結果。
我們可以使用page.$()
方法來訪問文檔上的選擇器API方法querySelector()
,並使用page.$$()
作為querySelectorAll()
的別名。
完成計算後,我們在browser
上調用close()
方法:
browser.close()
頁面方法
我們在上面看到了從調用browser.newPage()
獲得的page
對象,並在其中調用了goto()
和evaluate()
方法。
所有方法都返回一個promise,因此它們通常以await
闕字開頭。
讓我們查看我們將調用的一些最常見的方法。您可以在Puppeteer文檔中查看完整列表。
page.$()
可以在頁面上使用選擇器API方法querySelector()
page.$$()
可以在頁面上使用選擇器API方法querySelectorAll()
page.$eval()
接受2個或多個參數。第一個是選擇器,第二個是函數。如果有更多參數,則將其作為附加參數傳遞給函數。
它在頁面上運行querySelectorAll()
,使用第一個參數作為選擇器,然後將該參數用作函數的第一個參數。
const innerTextOfButton = await page.$eval('button#submit', el => el.innerText)
click()
對參數中傳遞的元素執行鼠標單擊事件
await page.click('button#submit')
我們可以傳遞一個帶有選項對象的附加參數:
-
button
可以設置為left
(默認值),right
或middle
-
clickCount
是一個數字,默認為1,設置了元素應該被點擊的次數 -
delay
是點擊之間的毫秒數。默認是0
content()
獲取頁面的HTML源代碼
const source = await page.content()
emulate()
模擬設備。它將用戶代理設置為特定設備,並相應地設置視圖。
支持的設備列表可在此文件中找到: DeviceDescriptors.js。
這是如何模擬iPhone X的方法:
iPhone X
const puppeteer = require('puppeteer');
const device = require('puppeteer/DeviceDescriptors')['iPhone X'];
puppeteer.launch().then(async browser => {
const page = await browser.newPage()
await page.emulate(device)
//do stuff
await browser.close()
})
evaluate()
在頁面上下文中評估一個函數。在此函數內部,我們可以訪問document
對象,因此可以調用任何DOM API:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://flaviocopes.com')
const result = await page.evaluate(() => {
return document.querySelectorAll('.footer-tags a').length
})
console.log(result)
})()
我們在此處調用的內容是在頁面上下文中執行的,因此如果我們運行console.log()
,我們將無法在Node.js上下文中看到結果,因為它是在無界面瀏覽器中執行的。
我們可以在這裡計算值並返回JavaScript對象,但如果我們想從evaluate()返回DOM元素並在Node.js上下文中訪問它,則必須使用不同的方法,evaluateHandle()
。如果我們從evaluate()返回DOM元素,我們只會得到一個空對象。
evaluateHandle()
與evaluate()類似,但如果我們返回DOM元素,則會得到正確的對象,而不是空對象:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://flaviocopes.com')
const result = await page.evaluateHandle(() => {
return document.querySelectorAll('.footer-tags a')
})
console.log(result)
})()
exposeFunction()
此方法允許您在瀏覽器上下文中添加一個新的函數,該函數在Node.js上下文中執行。
這意味著我們可以在瀏覽器中添加一個函數,在其中運行Node.js代碼。
此示例在瀏覽器上下文中添加一個名為test()的函數,該函數從文件系統中讀取與腳本相對路徑的“app.js”文件:
const puppeteer = require('puppeteer');
const fs = require('fs');
(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://flaviocopes.com')
await page.exposeFunction('test', () => {
const loadData = (path) => {
try {
return fs.readFileSync(path, 'utf8')
} catch (err) {
console.error(err)
return false
}
}
return loadData('app.js')
})
const result = await page.evaluate(() => {
return test()
})
console.log(result)
})()
focus()
將焦點放在作為參數傳遞的選擇器上
await page.focus('input#name')
goBack()
在頁面導航歷史記錄中返回
await page.goBack()
goForward()
在頁面導航歷史記錄中前進
await page.goForward()
goto()
打開一個新頁面。
await page.goto('https://flaviocopes.com')
您可以傳遞一個對象作為第二個參數,帶有選項。如果傳遞了waitUntil
選項,並將其設置為networkidle2
值,則將等待到導航完成:
await page.goto('https://flaviocopes.com', {waitUntil: 'networkidle2'})
hover()
在作為參數傳遞的選擇器上進行鼠標懸停操作
await page.hover('input#name')
pdf()
從頁面生成PDF。您可以:
await page.pdf({ path: 'file.pdf })
您可以將許多選項傳遞給此方法,以設置所生成PDF的詳細信息。查看官方文檔。
reload()
重新加載頁面
await page.reload()
screenshot()
對頁面進行PNG截圖,並將其保存到使用path
選中的文件名中。
await page.screenshot({path: 'screenshot.png'})
select()
選擇由參數標識的DOM元素
await page.select('input#name')
setContent()
您可以設置頁面的內容,而不是打開現有的網頁。
可用於使用現有的HTML程序生成PDF或截圖:
const html = '<h1>Hello!</h1>'
await page.setContent(html)
await page.pdf({path: 'hello.pdf'})
await page.screenshot({path: 'screenshot.png'})
setViewPort()
默認情況下,視圖口的大小為800x600像素。如果您想要不同的視圖口,也許是為了截圖,則調用setViewport
,並傳遞一個具有width
和height
屬性的對象。
await page.setViewport({ width: 1280, height: 800 })
title()
獲取頁面標題
await page.title()
type()
在標識表單元素的選擇器中輸入文字
await page.type('input#name', 'Flavio')
delay
選項允許模擬現實世界中的打字,每個字符之間添加延遲:
await page.type('input#name', 'Flavio', {delay: 100})
url()
獲取頁面URL
await page.url()
viewport()
獲取頁面視圖口
await page.viewport()
waitFor()
等待某些特定的事情發生。有以下快捷函數:
waitForFunction
waitForNavigation
waitForRequest
waitForResponse
waitForSelector
waitForXPath
示例:
await page.waitFor(waitForNameToBeFilled)
const waitForNameToBeFilled = () => page.$('input#name').value != ''
頁面命名空間
一個頁面對象提供了訪問多個不同對象的方式:
這些中的每一個都提供了許多新的功能。
keyboard
和mouse
在嘗試自動化事務時最有可能使用。
例如,如何觸發對元素的輸入(應事先選擇):
await page.keyboard.type('hello!')
鍵盤的其他方法是
-
keyboard.down()
發送keydown事件 -
keyboard.press()
發送keydown和keyup(模擬正常鍵輸入)。主要用於修飾鍵(Shift,Ctrl,Cmd) -
keyboard.sendCharacter()
發送keypress事件 -
keyboard.type()
發送keydown,keypress和keyup事件 -
keyboard.up()
發送keyup事件
所有這些都使用鍵盤鍵碼,該碼在美國鍵盤布局文件中定義為: https://github.com/GoogleChrome/puppeteer/blob/master/lib/USKeyboardLayout.js。常規字符和數字保持原樣,而特殊鍵有一個特殊的代碼來定義它們。
mouse
提供4種方法:
-
mouse.click()
模擬單擊:mousedown和mouseup事件 -
mouse.down()
模擬mousedown事件 -
mouse.move()
移動到不同的坐標 -
mouse.up()
模擬mouseup事件