從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後,我們可以使用pageevaluate()方法來獲取頁面的內容

(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(默認值),rightmiddle

  • 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,並傳遞一個具有widthheight屬性的對象。

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 != ''

頁面命名空間

一個頁面對象提供了訪問多個不同對象的方式:

這些中的每一個都提供了許多新的功能。

keyboardmouse在嘗試自動化事務時最有可能使用。

例如,如何觸發對元素的輸入(應事先選擇):

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事件