結合項目來談談 Puppeteer

Puppeteer 是 Chrome 開發團隊在 2017 年發佈的一個 Node.js 包,用來模擬 Chrome 瀏覽器的運行。咱們團隊從 Puppeteer 剛發佈出來就開始成爲忠實用戶了(主要是由於 PhantomJs 坑太多了),本文主要在介紹 Puppeteer 的同時,結合咱們平時的實踐作一個分享。javascript

學習 Puppeteer 以前咱們先來了解一下 Chrome DevTool Protocolcss

什麼是 Chrome DevTool Protocol

  • CDP 基於 WebSocket,利用 WebSocket 實現與瀏覽器內核的快速數據通道
  • CDP 分爲多個域(DOM,Debugger,Network,Profiler,Console...),每一個域中都定義了相關的命令和事件(Commands and Events)
  • 咱們能夠基於 CDP 封裝一些工具對 Chrome 瀏覽器進行調試及分析,好比咱們經常使用的 「Chrome 開發者工具」 就是基於 CDP 實現的
  • 若是你以 remote-debugging-port 參數啓動 Chrome,那麼就能夠看到全部 Tab 頁面的開發者調試前端頁面,還會在同一端口上還提供了 http 服務,主要提供如下幾個接口:
GET /json/version                     # 獲取瀏覽器的一些元信息
GET /json or /json/list               # 當前瀏覽器上打開的一些頁面信息
GET /json/protocol                    # 獲取當前 CDP 的協議信息 
GET /json/new?{url}                   # 開啓一共新的 Tab 頁面
GET /json/activate/{targetId}         # 激活某個頁面成爲當前顯示的頁面
GET /json/close/{targetId}            # 關閉某個頁面
GET /devtools/inspector.html          # 打開當前頁面的開發者調試工具
WebSocket /devtools/page/{targetId}   # 獲取某個頁面的 websocket 地址
複製代碼

什麼是 Headless Chrome

  • 在無界面的環境中運行 Chrome
  • 經過命令行或者程序語言操做 Chrome
  • 無需人的干預,運行更穩定
  • 沒有界面,少了真實瀏覽器加載 css/js 以及渲染頁面的工做,無頭測試要比真實瀏覽器更快
  • 在啓動 Chrome 時添加參數 --headless,即可以 headless 模式啓動 Chrome
alias chrome="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome"  # Mac OS X 命令別名
chrome --headless --remote-debugging-port=9222 --disable-gpu                   # 開啓遠程調試
chrome --headless --disable-gpu --dump-dom https://www.baidu.com               # 獲取頁面 DOM
chrome --headless --disable-gpu --screenshot https://www.baidu.com             # 截圖
複製代碼
  • chrome 啓動時能夠加一些什麼參數,你們能夠點擊這裏查看

Puppeteer 是什麼

  • Puppeteer 是 Node.js 工具引擎
  • Puppeteer 提供了一系列 API,經過 Chrome DevTools Protocol 協議控制 Chromium/Chrome 瀏覽器的行爲
  • Puppeteer 默認狀況下是以 headless 啓動 Chrome 的,也能夠經過參數控制啓動有界面的 Chrome
  • Puppeteer 默認綁定最新的 Chromium 版本,也能夠本身設置不一樣版本的綁定
  • Puppeteer 讓咱們不須要了解太多的底層 CDP 協議實現與瀏覽器的通訊

Puppeteer 能作什麼

官方稱:「Most things that you can do manually in the browser can be done using Puppeteer」,那麼具體能夠作些什麼呢?html

  • 網頁截圖或者生成 PDF
  • 爬取 SPA 或 SSR 網站
  • UI 自動化測試,模擬表單提交,鍵盤輸入,點擊等行爲
  • 捕獲網站的時間線,幫助診斷性能問題
  • 建立一個最新的自動化測試環境,使用最新的 js 和最新的 Chrome 瀏覽器運行測試用例
  • 測試 Chrome 擴展程序
  • ...

Puppeteer API 分層結構

Puppeteer 中的 API 分層結構基本和瀏覽器保持一致,下面對常使用到的幾個類介紹一下:前端

image

  • Browser: 對應一個瀏覽器實例,一個 Browser 能夠包含多個 BrowserContext
  • BrowserContext: 對應瀏覽器一個上下文會話,就像咱們打開一個普通的 Chrome 以後又打開一個隱身模式的瀏覽器同樣,BrowserContext 具備獨立的 Session(cookie 和 cache 獨立不共享),一個 BrowserContext 能夠包含多個 Page
  • Page:表示一個 Tab 頁面,經過 browserContext.newPage()/browser.newPage() 建立,browser.newPage() 建立頁面時會使用默認的 BrowserContext,一個 Page 能夠包含多個 Frame
  • Frame: 一個框架,每一個頁面有一個主框架(page.MainFrame()),也能夠多個子框架,主要由 iframe 標籤建立產生的
  • ExecutionContext: 是 javascript 的執行環境,每個 Frame 都一個默認的 javascript 執行環境
  • ElementHandle: 對應 DOM 的一個元素節點,經過該該實例能夠實現對元素的點擊,填寫表單等行爲,咱們能夠經過選擇器,xPath 等來獲取對應的元素
  • JsHandle:對應 DOM 中的 javascript 對象,ElementHandle 繼承於 JsHandle,因爲咱們沒法直接操做 DOM 中對象,因此封裝成 JsHandle 來實現相關功能
  • CDPSession:能夠直接與原生的 CDP 進行通訊,經過 session.send 函數直接發消息,經過 session.on 接收消息,能夠實現 Puppeteer API 中沒有涉及的功能
  • Coverage:獲取 JavaScript 和 CSS 代碼覆蓋率
  • Tracing:抓取性能數據進行分析
  • Response: 頁面收到的響應
  • Request: 頁面發出的請求

如何建立一個 Browser 實例

puppeteer 提供了兩種方法用於建立一個 Browser 實例:java

  • puppeteer.connect: 鏈接一個已經存在的 Chrome 實例
  • puppeteer.launch: 每次都啓動一個 Chrome 實例
const puppeteer = require('puppeteer');
let request = require('request-promise-native');

//使用 puppeteer.launch 啓動 Chrome
(async () => {
    const browser = await puppeteer.launch({
        headless: false,   //有瀏覽器界面啓動
        slowMo: 100,       //放慢瀏覽器執行速度,方便測試觀察
        args: [            //啓動 Chrome 的參數,詳見上文中的介紹
            '–no-sandbox',
            '--window-size=1280,960'
        ],
    });
    const page = await browser.newPage();
    await page.goto('https://www.baidu.com');
    await page.close();
    await browser.close();
})();

//使用 puppeteer.connect 鏈接一個已經存在的 Chrome 實例
(async () => {
    //經過 9222 端口的 http 接口獲取對應的 websocketUrl
    let version = await request({
        uri:  "http://127.0.0.1:9222/json/version",
        json: true
    });
    //直接鏈接已經存在的 Chrome
    let browser = await puppeteer.connect({
        browserWSEndpoint: version.webSocketDebuggerUrl
    });
    const page = await browser.newPage();
    await page.goto('https://www.baidu.com');
    await page.close();
    await browser.disconnect();
})();

複製代碼

這兩種方式的對比:git

  • puppeteer.launch 每次都要從新啓動一個 Chrome 進程,啓動平均耗時 100 到 150 ms,性能欠佳
  • puppeteer.connect 能夠實現對於同一個 Chrome 實例的共用,減小啓動關閉瀏覽器的時間消耗
  • puppeteer.launch 啓動時參數能夠動態修改
  • 經過 puppeteer.connect 咱們能夠遠程鏈接一個 Chrome 實例,部署在不一樣的機器上
  • puppeteer.connect 多個頁面共用一個 chrome 實例,偶爾會出現 Page Crash 現象,須要進行併發控制,並定時重啓 Chrome 實例

如何等待加載?

在實踐中咱們常常會遇到如何判斷一個頁面加載完成了,什麼時機去截圖,什麼時機去點擊某個按鈕等問題,那咱們到底如何去等待加載呢?github

下面咱們把等待加載的 API 分爲三類進行介紹:web

加載導航頁面
  • page.goto:打開新頁面
  • page.goBack :回退到上一個頁面
  • page.goForward :前進到下一個頁面
  • page.reload :從新加載頁面
  • page.waitForNavigation:等待頁面跳轉

Pupeeteer 中的基本上全部的操做都是異步的,以上幾個 API 都涉及到關於打開一個頁面,什麼狀況下才能判斷這個函數執行完畢呢,這些函數都提供了兩個參數 waitUtil 和 timeout,waitUtil 表示直到什麼出現就算執行完畢,timeout 表示若是超過這個時間尚未結束就拋出異常。chrome

await page.goto('https://www.baidu.com', {
   timeout: 30 * 1000,
   waitUntil: [
       'load',              //等待 「load」 事件觸發
       'domcontentloaded',  //等待 「domcontentloaded」 事件觸發
       'networkidle0',      //在 500ms 內沒有任何網絡鏈接
       'networkidle2'       //在 500ms 內網絡鏈接個數不超過 2 個
   ]
});
複製代碼

以上 waitUtil 有四個事件,業務能夠根據需求來設置其中一個或者多個觸發才覺得結束,networkidle0 和 networkidle2 中的 500ms 對時間性能要求高的用戶來講,仍是有點長的docker

等待元素、請求、響應
  • page.waitForXPath:等待 xPath 對應的元素出現,返回對應的 ElementHandle 實例
  • page.waitForSelector :等待選擇器對應的元素出現,返回對應的 ElementHandle 實例
  • page.waitForResponse :等待某個響應結束,返回 Response 實例
  • page.waitForRequest:等待某個請求出現,返回 Request 實例
await page.waitForXPath('//img');
await page.waitForSelector('#uniqueId');
await page.waitForResponse('https://d.youdata.netease.com/api/dash/hello');
await page.waitForRequest('https://d.youdata.netease.com/api/dash/hello');
複製代碼
自定義等待

若是上面提供的等待方式都不能知足咱們的需求,puppeteer 還提供咱們提供兩個函數:

  • page.waitForFunction:等待在頁面中自定義函數的執行結果,返回 JsHandle 實例
  • page.waitFor:設置等待時間,實在沒辦法的作法
await page.goto(url, { 
    timeout: 120000, 
    waitUntil: 'networkidle2' 
});
//咱們能夠在頁面中定義本身認爲加載完的事件,在合適的時間點咱們將該事件設置爲 true
//如下是咱們項目在觸發截圖時的判斷邏輯,若是 renderdone 出現且爲 true 那麼就截圖,若是是 Object,說明頁面加載出錯了,咱們能夠捕獲該異常進行提示
let renderdoneHandle = await page.waitForFunction('window.renderdone', {
    polling: 120
});
const renderdone = await renderdoneHandle.jsonValue();
if (typeof renderdone === 'object') {
    console.log(`加載頁面失敗:報表${renderdone.componentId}出錯 -- ${renderdone.message}`);
}else{
    console.log('頁面加載成功');
}
複製代碼

兩個獨立的環境

在使用 Puppeteer 時咱們幾乎必定會遇到在這兩個環境之間交換數據:運行 Puppeteer 的 Node.js 環境和 Puppeteer 操做的頁面 Page DOM,理解這兩個環境很重要

  • 首先 Puppeteer 提供了不少有用的函數去 Page DOM Environment 中執行代碼,這個後面會介紹到
  • 其次 Puppeteer 提供了 ElementHandle 和 JsHandle 將 Page DOM Environment 中元素和對象封裝成對應的 Node.js 對象,這樣能夠直接這些對象的封裝函數進行操做 Page DOM

image

10 個用例告訴你如何使用 puppeteer

下面介紹 10 個關於使用 Puppeteer 的用例,並在介紹用例的時候會穿插的講解一些 API,告訴你們如何使用 Puppeteer:

Case1:截圖

咱們使用 Puppeteer 既能夠對某個頁面進行截圖,也能夠對頁面中的某個元素進行截圖:

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    //設置可視區域大小
    await page.setViewport({width: 1920, height: 800});
    await page.goto('https://youdata.163.com');
    //對整個頁面截圖
    await page.screenshot({
        path: './files/capture.png',  //圖片保存路徑
        type: 'png',
        fullPage: true //邊滾動邊截圖
        // clip: {x: 0, y: 0, width: 1920, height: 800}
    });
    //對頁面某個元素截圖
    let [element] = await page.$x('/html/body/section[4]/div/div[2]');
    await element.screenshot({
        path: './files/element.png'
    });
    await page.close();
    await browser.close();
})();
複製代碼

咱們怎麼去獲取頁面中的某個元素呢?

  • page.$('#uniqueId'):獲取某個選擇器對應的第一個元素
  • page.$$('div'):獲取某個選擇器對應的全部元素
  • page.$x('//img'):獲取某個 xPath 對應的全部元素
  • page.waitForXPath('//img'):等待某個 xPath 對應的元素出現
  • page.waitForSelector('#uniqueId'):等待某個選擇器對應的元素出現
case2: 模擬用戶登陸
(async () => {
    const browser = await puppeteer.launch({
        slowMo: 100,    //放慢速度
        headless: false,
        defaultViewport: {width: 1440, height: 780},
        ignoreHTTPSErrors: false, //忽略 https 報錯
        args: ['--start-fullscreen'] //全屏打開頁面
    });
    const page = await browser.newPage();
    await page.goto('https://demo.youdata.com');
    //輸入帳號密碼
    const uniqueIdElement = await page.$('#uniqueId');
    await uniqueIdElement.type('admin@admin.com', {delay: 20});
    const passwordElement = await page.$('#password', {delay: 20});
    await passwordElement.type('123456');
    //點擊肯定按鈕進行登陸
    let okButtonElement = await page.$('#btn-ok');
    //等待頁面跳轉完成,通常點擊某個按鈕須要跳轉時,都須要等待 page.waitForNavigation() 執行完畢才表示跳轉成功
    await Promise.all([
        okButtonElement.click(),
        page.waitForNavigation()  
    ]);
    console.log('admin 登陸成功');
    await page.close();
    await browser.close();
})();
複製代碼

那麼 ElementHandle 都提供了哪些操做元素的函數呢?

  • elementHandle.click():點擊某個元素
  • elementHandle.tap():模擬手指觸摸點擊
  • elementHandle.focus():聚焦到某個元素
  • elementHandle.hover():鼠標 hover 到某個元素上
  • elementHandle.type('hello'):在輸入框輸入文本
case3:請求攔截

請求在有些場景下頗有必要,攔截一下不必的請求提升性能,咱們能夠在監聽 Page 的 request 事件,並進行請求攔截,前提是要開啓請求攔截 page.setRequestInterception(true)。

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    const blockTypes = new Set(['image', 'media', 'font']);
    await page.setRequestInterception(true); //開啓請求攔截
    page.on('request', request => {
        const type = request.resourceType();
        const shouldBlock = blockTypes.has(type);
        if(shouldBlock){
            //直接阻止請求
            return request.abort();
        }else{
            //對請求重寫
            return request.continue({
                //能夠對 url,method,postData,headers 進行覆蓋
                headers: Object.assign({}, request.headers(), {
                    'puppeteer-test': 'true'
                })
            });
        }
    });
    await page.goto('https://demo.youdata.com');
    await page.close();
    await browser.close();
})();
複製代碼

那 page 頁面上都提供了哪些事件呢?

  • page.on('close') 頁面關閉
  • page.on('console') console API 被調用
  • page.on('error') 頁面出錯
  • page.on('load') 頁面加載完
  • page.on('request') 收到請求
  • page.on('requestfailed') 請求失敗
  • page.on('requestfinished') 請求成功
  • page.on('response') 收到響應
  • page.on('workercreated') 建立 webWorker
  • page.on('workerdestroyed') 銷燬 webWorker
case4:獲取 WebSocket 響應

Puppeteer 目前沒有提供原生的用於處理 WebSocket 的 API 接口,可是咱們能夠經過更底層的 Chrome DevTool Protocol (CDP) 協議得到

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    //建立 CDP 會話
    let cdpSession = await page.target().createCDPSession();
    //開啓網絡調試,監聽 Chrome DevTools Protocol 中 Network 相關事件
    await cdpSession.send('Network.enable');
    //監聽 webSocketFrameReceived 事件,獲取對應的數據
    cdpSession.on('Network.webSocketFrameReceived', frame => {
        let payloadData = frame.response.payloadData;
        if(payloadData.includes('push:query')){
            //解析payloadData,拿到服務端推送的數據
            let res = JSON.parse(payloadData.match(/\{.*\}/)[0]);
            if(res.code !== 200){
                console.log(`調用websocket接口出錯:code=${res.code},message=${res.message}`);
            }else{
                console.log('獲取到websocket接口數據:', res.result);
            }
        }
    });
    await page.goto('https://netease.youdata.163.com/dash/142161/reportExport?pid=700209493');
    await page.waitForFunction('window.renderdone', {polling: 20});
    await page.close();
    await browser.close();
})();       
複製代碼
case5:植入 javascript 代碼

Puppeteer 最強大的功能是,你能夠在瀏覽器裏執行任何你想要運行的 javascript 代碼,下面是我在爬 188 郵箱的收件箱用戶列表時,發現每次打開收件箱再關掉都會多處一個 iframe 來,隨着打開收件箱的增多,iframe 增多到瀏覽器卡到沒法運行,因此我在爬蟲代碼里加了刪除無用 iframe 的腳本:

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto('https://webmail.vip.188.com');
    //註冊一個 Node.js 函數,在瀏覽器裏運行
    await page.exposeFunction('md5', text =>
        crypto.createHash('md5').update(text).digest('hex')
    );
    //經過 page.evaluate 在瀏覽器裏執行刪除無用的 iframe 代碼
    await page.evaluate(async () =>  {
        let iframes = document.getElementsByTagName('iframe');
        for(let i = 3; i <  iframes.length - 1; i++){
            let iframe = iframes[i];
            if(iframe.name.includes("frameBody")){
                iframe.src = 'about:blank';
                try{
                    iframe.contentWindow.document.write('');
                    iframe.contentWindow.document.clear();
                }catch(e){}
                //把iframe從頁面移除
                iframe.parentNode.removeChild(iframe);
            }
        }
        //在頁面中調用 Node.js 環境中的函數
        const myHash = await window.md5('PUPPETEER');
        console.log(`md5 of ${myString} is ${myHash}`);
    });
    await page.close();
    await browser.close();
})();
複製代碼

有哪些函數能夠在瀏覽器環境中執行代碼呢?

  • page.evaluate(pageFunction[, ...args]):在瀏覽器環境中執行函數
  • page.evaluateHandle(pageFunction[, ...args]):在瀏覽器環境中執行函數,返回 JsHandle 對象
  • page.$$eval(selector, pageFunction[, ...args]):把 selector 對應的全部元素傳入到函數並在瀏覽器環境執行
  • page.$eval(selector, pageFunction[, ...args]):把 selector 對應的第一個元素傳入到函數在瀏覽器環境執行
  • page.evaluateOnNewDocument(pageFunction[, ...args]):建立一個新的 Document 時在瀏覽器環境中執行,會在頁面全部腳本執行以前執行
  • page.exposeFunction(name, puppeteerFunction):在 window 對象上註冊一個函數,這個函數在 Node 環境中執行,有機會在瀏覽器環境中調用 Node.js 相關函數庫
case6: 如何抓取 iframe 中的元素

一個 Frame 包含了一個執行上下文(Execution Context),咱們不能跨 Frame 執行函數,一個頁面中能夠有多個 Frame,主要是經過 iframe 標籤嵌入的生成的。其中在頁面上的大部分函數實際上是 page.mainFrame().xx 的一個簡寫,Frame 是樹狀結構,咱們能夠經過 frame.childFrames() 遍歷到全部的 Frame,若是想在其它 Frame 中執行函數必須獲取到對應的 Frame 才能進行相應的處理

如下是在登陸 188 郵箱時,其登陸窗口實際上是嵌入的一個 iframe,如下代碼時咱們在獲取 iframe 並進行登陸

(async () => {
    const browser = await puppeteer.launch({headless: false, slowMo: 50});
    const page = await browser.newPage();
    await page.goto('https://www.188.com');
    //點擊使用密碼登陸
    let passwordLogin = await page.waitForXPath('//*[@id="qcode"]/div/div[2]/a');
    await passwordLogin.click();
    for (const frame of page.mainFrame().childFrames()){
        //根據 url 找到登陸頁面對應的 iframe
        if (frame.url().includes('passport.188.com')){
            await frame.type('.dlemail', 'admin@admin.com');
            await frame.type('.dlpwd', '123456');
            await Promise.all([
                frame.click('#dologin'),
                page.waitForNavigation()
            ]);
            break;
        }
    }
    await page.close();
    await browser.close();
})();
複製代碼
case7: 頁面性能分析

Puppeteer 提供了對頁面性能分析的工具,目前功能仍是比較弱的,只能獲取到一個頁面性能執行的數據,如何分析須要咱們本身根據數據進行分析,聽說在 2.0 版本會作大的改版:

  • 一個瀏覽器同一時間只能 trace 一次
  • 在 devTools 的 Performance 能夠上傳對應的 json 文件並查看分析結果
  • 咱們能夠寫腳原本解析 trace.json 中的數據作自動化分析
  • 經過 tracing 咱們獲取頁面加載速度以及腳本的執行性能
(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.tracing.start({path: './files/trace.json'});
    await page.goto('https://www.google.com');
    await page.tracing.stop();
    /* continue analysis from 'trace.json' */
    browser.close();
})();

複製代碼
case8: 文件的上傳和下載

在自動化測試中,常常會遇到對於文件的上傳和下載的需求,那麼在 Puppeteer 中如何實現呢?

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    //經過 CDP 會話設置下載路徑
    await page.target().createCDPSession().send('Page.setDownloadBehavior', {
        behavior: 'allow', //容許全部下載請求
        downloadPath: 'path/to/download'  //設置下載路徑
    });
    //點擊按鈕觸發下載
    await (await page.waitForSelector('#someButton')).click();
    //等待文件出現,輪訓判斷文件是否出現
    await waitForFile('path/to/download/filename');
    
    //上傳時對應的 inputElement 必須是<input>元素
    let inputElement = await page.waitForXPath('//input[@type="file"]');
    await inputElement.uploadFile('/path/to/file');
    browser.close();
})();
複製代碼

case9:跳轉新 tab 頁處理

在點擊一個按鈕跳轉到新的 Tab 頁時會新開一個頁面,這個時候咱們如何獲取改頁面對應的 Page 實例呢?能夠經過監聽 Browser 上的 targetcreated 事件來實現,表示有新的頁面建立:

let page = await browser.newPage();
await page.goto(url);
let btn = await page.waitForSelector('#btn');
//在點擊按鈕以前,事先定義一個 Promise,用於返回新 tab 的 Page 對象
const newPagePromise = new Promise(res => 
  browser.once('targetcreated', 
    target => res(target.page())
  )
);
await btn.click();
//點擊按鈕後,等待新tab對象
let newPage = await newPagePromise;
複製代碼

case10: 模擬不一樣的設備

Puppeteer 提供了模擬不一樣設備的功能,其中 puppeteer.devices 對象上定義不少設備的配置信息,這些配置信息主要包含 viewport 和 userAgent,而後經過函數 page.emulate 實現不一樣設備的模擬

const puppeteer = require('puppeteer');
const iPhone = puppeteer.devices['iPhone 6'];
puppeteer.launch().then(async browser => {
  const page = await browser.newPage();
  await page.emulate(iPhone);
  await page.goto('https://www.google.com');
  await browser.close();
});
複製代碼

Puppeteer vs Phantomjs

  • 徹底真實的瀏覽器操做,支持全部 Chrome 特性
  • 能夠提供不一樣版本的 Chrome 瀏覽器環境
  • Chrome 團隊維護,擁有更好的兼容性和前景
  • headless 參數動態配置,調試更爲方便,經過 –remote-debugging-port=9222,能夠進入調試界面調試
  • 支持最新的 JS 語法,好比 async/await 等
  • Phantomjs 環境安裝複雜,API 調用不友好
  • 二者的主要不一樣在於 Phantomjs 使用了一個較老版本的 WebKit 做爲它的渲染引擎
  • 比 Phantomjs 有更快更好的性能,如下是其餘人對於 Puppeteer 和 Phantomjs 性能對比結果:

Headless Chrome vs PhantomJS Benchmark

image

Puppeteer 在咱們團隊的應用場景

image

性能和優化

  • 關於共享內存:
Chrome 默認使用 /dev/shm 共享內存,可是 docker 默認/dev/shm 只有64MB,顯然是不夠使用的,提供兩種方式來解決:
- 啓動 docker 時添加參數 --shm-size=1gb 來增大 /dev/shm 共享內存,可是 swarm 目前不支持 shm-size 參數
- 啓動 Chrome 添加參數 - disable-dev-shm-usage,禁止使用 /dev/shm 共享內存
複製代碼
  • 儘可能使用同一個瀏覽器實例,這樣能夠實現緩存共用
  • 經過請求攔截不必加載的資源
  • 像咱們本身打開 Chrome 同樣,tab 頁多必然會卡,因此必須有效控制 tab 頁個數
  • 一個 Chrome 實例啓動時間長了不免會出現內存泄漏,頁面奔潰等現象,因此定時重啓 Chrome 實例是有必要的
  • 爲了加快性能,關閉不必的配置,好比:-no-sandbox(沙箱功能),--disable-extensions(擴展程序)等
  • 儘可能避免使用 page.waifFor(1000),讓程序本身決定效果會更好
  • 由於和 Chrome 實例鏈接時使用的 Websocket,會存在 Websocket sticky session 問題,這個須要特別注意

參考文獻

相關文章
相關標籤/搜索