Puppeteer 初探

咱們平常使用瀏覽器或者說是有頭瀏覽器時的步驟爲:啓動瀏覽器、打開一個網頁、進行交互。html

無頭瀏覽器指的是咱們使用腳原本執行以上過程的瀏覽器,能模擬真實的瀏覽器使用場景。前端

有了無頭瀏覽器,咱們就能作包括但不限於如下事情:node

  • 對網頁進行截圖保存爲圖片或 pdf
  • 抓取單頁應用(SPA)執行並渲染(解決傳統 HTTP 爬蟲抓取單頁應用難以處理異步請求的問題)
  • 作表單的自動提交、UI的自動化測試、模擬鍵盤輸入等
  • 用瀏覽器自帶的一些調試工具和性能分析工具幫助咱們分析問題
  • 在最新的無頭瀏覽器環境裏作測試、使用最新瀏覽器特性
  • 寫爬蟲作你想作的事情(奸笑

無頭瀏覽器不少,包括但不限於:git

  • PhantomJS, 基於 Webkit
  • SlimerJS, 基於 Gecko
  • HtmlUnit, 基於 Rhnio
  • TrifleJS, 基於 Trident
  • Splash, 基於 Webkit

這裏主要介紹 Google 提供的無頭瀏覽器(headless Chrome), 他基於 Chrome DevTools protocol 提供了很多高度封裝的接口方便咱們控制瀏覽器。github

簡單的代碼示例

爲了能使用 async/await 等新特性,須要使用 v7.6.0 或更高版本的 Node.chrome

啓動/關閉瀏覽器、打開頁面

// 啓動瀏覽器
    const browser = await puppeteer.launch({
        // 關閉無頭模式,方便咱們看到這個無頭瀏覽器執行的過程
        // headless: false,
        timeout: 30000, // 默認超時爲30秒,設置爲0則表示不設置超時
    });

    // 打開空白頁面
    const page = await browser.newPage();

    // 進行交互
    // ...

    // 關閉瀏覽器
    // await browser.close();

設置頁面視窗大小

// 設置瀏覽器視窗
    page.setViewport({
        width: 1376,
        height: 768,
    });

輸入網址

// 地址欄輸入網頁地址
    await page.goto('https://google.com/', {
        // 配置項
        // waitUntil: 'networkidle', // 等待網絡狀態爲空閒的時候才繼續執行
    });

保存網頁爲圖片

打開一個網頁,而後截圖保存到本地:數組

await page.screenshot({
    path: 'path/to/saved.png',
});

完整示例代碼瀏覽器

保存網頁爲 pdf

打開一個網頁,而後保存 pdf 到本地:服務器

await page.pdf({
     path: 'path/to/saved.pdf',
    format: 'A4', // 保存尺寸
});

完整示例代碼網絡

執行腳本

要獲取打開的網頁中的宿主環境,咱們可使用 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');

// 執行計算
const html = await page.evaluate(body => body.outerHTML, htmlHandle);

// 銷燬句柄
await htmlHandle.dispose();

console.log('html:', html);

Page.$ 能夠理解爲咱們經常使用的 document.querySelector, 而 Page.$$ 則對應 document.querySelectorAll

完整示例代碼

自動提交表單

打開谷歌首頁,輸入關鍵字,回車進行搜索:

// 地址欄輸入網頁地址
await page.goto('https://google.com/', {
    waitUntil: 'networkidle', // 等待網絡狀態爲空閒的時候才繼續執行
});

// 聚焦搜索框
// await page.click('#lst-ib');
await page.focus('#lst-ib');

// 輸入搜索關鍵字
await page.type('辣子雞', {
   delay: 1000, // 控制 keypress 也就是每一個字母輸入的間隔
});

// 回車
await page.press('Enter');

完整示例代碼

複雜點的代碼示例

每個簡單的動做鏈接起來,就是一連串複雜的交互,接下來咱們看兩個更具體的示例。

抓取單頁應用: 模擬餓了麼外賣下單

傳統的爬蟲是基於 HTTP 協議,模擬 UserAgent 發送 http 請求,獲取到 html 內容後使用正則解析出須要抓取的內容,這種方式面對服務端渲染直出 html 的網頁時很是便捷。

但遇到單頁應用(SPA)時,或遇到登陸校驗時,這種爬蟲就顯得比較無力。

而使用無頭瀏覽器,抓取網頁時徹底使用了人機交互時的操做,因此頁面的初始化徹底能使用宿主瀏覽器環境渲染完備,再也不須要關心這個單頁應用在前端初始化時須要涉及哪些 HTTP 請求。

無頭瀏覽器提供的各類點擊、輸入等指令,徹底模擬人的點擊、輸入等指令,也就不再用擔憂正則寫不出來了啊哈哈哈

固然,有些場景下,使用傳統的 HTTP 爬蟲(寫正則匹配) 仍是比較高效的。

在這裏就再也不詳細對比這些差別了,如下這個例子僅做爲展現模擬一個完整的人機交互:使用移動版餓了麼點外賣。

先看下效果:

代碼比較長就不全貼了,關鍵是幾行:

const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');
const iPhone6 = devices['iPhone 6'];

console.log('啓動瀏覽器');
const browser = await puppeteer.launch();

console.log('打開頁面');
const page = await browser.newPage();

// 模擬移動端設備
await page.emulate(iPhone6);

console.log('地址欄輸入網頁地址');
await page.goto(url);

console.log('等待頁面準備好');
await page.waitForSelector('.search-wrapper .search');

console.log('點擊搜索框');
await page.tap('.search-wrapper .search');

await page.type('麥當勞', {
    delay: 200, // 每一個字母之間輸入的間隔
});

console.log('回車開始搜索');
await page.tap('button');

console.log('等待搜素結果渲染出來');
await page.waitForSelector('[class^="index-container"]');

console.log('找到搜索到的第一家外賣店!');
await page.tap('[class^="index-container"]');


console.log('等待菜單渲染出來');
await page.waitForSelector('[class^="fooddetails-food-panel"]');


console.log('直接選一個菜品吧');
await page.tap('[class^="fooddetails-cart-button"]');

// console.log('===爲了看清楚,傲嬌地等兩秒===');
await page.waitFor(2000);
await page.tap('[class^=submit-btn-submitbutton]');

// 關閉瀏覽器
await browser.close();

關鍵步驟是:

  • 加載頁面
  • 等待須要點擊的 DOM 渲染出來後點擊
  • 繼續等待下一步須要點擊的 DOM 渲染出來再點擊

關鍵的幾個指令:

  • page.tap(或 page.click) 爲點擊
  • page.waitForSelector 意思是等待指定元素出如今網頁中,若是已經出現了,則當即繼續執行下去, 後面跟的參數爲 selector 選擇器,與咱們經常使用的 document.querySelector 接收的參數一致
  • page.waitFor 後面能夠傳入 selector 選擇器、function 函數或 timeout 毫秒時間,如 page.waitFor(2000) 指等待2秒再繼續執行,例子中用這個函數暫停操做主要是爲了演示

以上幾個指令均可接受一個 selector 選擇器做爲參數,這裏額外介紹幾個方法:

  • page.$(selector) 與咱們經常使用的 document.querySelector(selector) 一致,返回的是一個 ElementHandle 元素句柄
  • page.$$(selector) 與咱們經常使用的 document.querySelectorAll(selector) 一致,返回的是一個數組

在有頭瀏覽器上下文中,咱們選擇一個元素的方法是:

const body = document.querySelector('body');
const bodyInnerHTML = body.innerHTML;
console.log('bodyInnerHTML: ', bodyInnerHTML);

而在無頭瀏覽器裏,咱們首先須要獲取一個句柄,經過句柄獲取到環境中的信息後,銷燬這個句柄。

// 獲取 html
// 獲取上下文句柄
const bodyHandle = await page.$('body');
// 執行計算
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 意爲在瀏覽器環境執行腳本,可傳入第二個參數做爲句柄,而 page.$eval 則針對選中的一個 DOM 元素執行操做。

完整示例代碼

導出批量網頁:下載圖靈圖書

我在 圖靈社區 上買了很多電子書,之前支持推送到 mobi 格式到 kindle 或推送 pdf 格式到郵箱進行閱讀,不過常常會關閉這些推送渠道,只能留在網頁上看書。

對我來講不是很方便,而這些書籍的在線閱讀效果是服務器渲染出來的(帶了大量標籤,沒法簡單抽取出好的排版),最好的方式固然是直接在線閱讀並保存爲 pdf 或圖片了。

藉助瀏覽器的無頭模式,我寫了個簡單的下載已購買書籍爲 pdf 到本地的腳本,支持批量下載已購買的書籍。

使用方法,傳入賬號密碼和保存路徑,如:

$ node ./demo/download-ituring-books.js '用戶名' '密碼' './books'

注意:puppeteerPage.pdf() 目前僅支持在無頭模式中使用,因此要想看有頭狀態的抓取過程的話,執行到 Page.pdf() 這步會先報錯:

因此啓動這個腳本時,須要保持無頭模式:

const browser = await puppeteer.launch({
    // 關閉無頭模式,方便咱們看到這個無頭瀏覽器執行的過程
    // 注意若調用了 Page.pdf 即保存爲 pdf,則須要保持爲無頭模式
    // headless: false,
});

看下執行效果:

個人書架裏有20多本書,下載完後是這樣子:

完整示例代碼

無頭瀏覽器還能作什麼?

無頭瀏覽器說白了就是能模擬人工在有頭瀏覽器中的各類操做,那天然不少人力活,都能使用無頭瀏覽器來作(好比上面這個下載 pdf 的過程,實際上是人力打開每個文章頁面,而後按 ctrl+pcommand+p 保存到本地的自動化過程)。

那既然用自動化工具能解決的事情,就不該該浪費重複的人力勞動了,因此咱們還能夠作:

  • 自動化工具
    如自動提交表單,自動下載
  • 自動化 UI 測試
    如記錄下正確 DOM 結構或截圖,而後自動執行指定操做後,檢查 DOM 結構或截圖是否匹配(UI 斷言)
  • 定時監控工具
    如定時截圖發週報,或定時巡查重要業務路徑下的頁面是否處於可用狀態,配合郵件告警
  • 爬蟲
    如傳統 HTTP 爬蟲怕不到的地方,就可配合無頭瀏覽器渲染能力來作
  • etc

感謝閱讀!(全文完)

相關文章
相關標籤/搜索