Puppeteer 学习笔记及基本用法
Puppeteer 學(xué)習(xí)筆記及基本用法
- Puppeteer 安裝
- 語法
- 基本語法
- API 分層結(jié)構(gòu)
- 加載導(dǎo)航頁面
- 等待元素、請求、響應(yīng)
- 自定義等待
- 元素定位
- 用戶模擬操作
- 請求攔截
- 獲取 WebSocket 響應(yīng)
- 植入 JavaScript 代碼
- 頁面性能分析
- 文件的上傳和下載
- 跳轉(zhuǎn)新 tab 頁處理
- 模擬不同的設(shè)備
- 模擬鍵盤
Puppeteer 安裝
-
安裝第三方庫:puppeteer
npm i puppeteer -
安裝谷歌 Chromium:若 npm 安裝失敗,需要手動下載 chromium 并解壓至相應(yīng)文件夾
語法
基本語法
API 分層結(jié)構(gòu)
- Browser: 對應(yīng)一個瀏覽器實例,一個 Browser 可以包含多個 BrowserContext
- BrowserContext: 對應(yīng)瀏覽器一個上下文會話,就像我們打開一個普通的 Chrome 之后又打開一個隱身模式的瀏覽器一樣,BrowserContext 具有獨立的 Session(cookie 和 cache 獨立不共享),一個 BrowserContext 可以包含多個 Page
- Page:表示一個 Tab 頁面,通過 browserContext.newPage()/browser.newPage() 創(chuàng)建,browser.newPage() 創(chuàng)建頁面時會使用默認的 BrowserContext,一個 Page 可以包含多個 Frame
- Frame: 一個框架,每個頁面有一個主框架(page.MainFrame()),也可以多個子框架,主要由 iframe 標簽創(chuàng)建產(chǎn)生的
- ExecutionContext: 是 javascript 的執(zhí)行環(huán)境,每一個 Frame 都一個默認的 javascript 執(zhí)行環(huán)境
- ElementHandle: 對應(yīng) DOM 的一個元素節(jié)點,通過該該實例可以實現(xiàn)對元素的點擊,填寫表單等行為,我們可以通過選擇器,xPath 等來獲取對應(yīng)的元素
- JsHandle:對應(yīng) DOM 中的 javascript 對象,ElementHandle 繼承于 JsHandle,由于我們無法直接操作 DOM 中對象,所以封裝成 JsHandle 來實現(xiàn)相關(guān)功能
- CDPSession:可以直接與原生的 CDP 進行通信,通過 session.send 函數(shù)直接發(fā)消息,通過 session.on 接收消息,可以實現(xiàn) Puppeteer API 中沒有涉及的功能
- Coverage:獲取 JavaScript 和 CSS 代碼覆蓋率
- Tracing:抓取性能數(shù)據(jù)進行分析
- Response: 頁面收到的響應(yīng)
- Request: 頁面發(fā)出的請求
puppeteer 提供了兩種方法用于創(chuàng)建一個 Browser 實例:
- puppeteer.connect :連接一個已經(jīng)存在的 Chrome 實例
- puppeteer.launch : 每次都啟動一個 Chrome 實例
puppeteer.launch() 的參數(shù)解釋:其使用字典進行配置輸入
- headless: 是否使用瀏覽器界面啟動,turn 和 false
- 默認為 turn 無頭瀏覽器,不展示瀏覽器頁面
- 設(shè)置 false 參數(shù),展示瀏覽器頁面
- **executablePath:**指定瀏覽器執(zhí)行地址
- timeout: 等待瀏覽器實例啟動的最長時間(以毫秒為單位)。默認為30000(30秒)。設(shè)置為 0 禁用超時
- slowMo: 放慢瀏覽器執(zhí)行速度,方便測試觀察
- args: 傳遞給瀏覽器實例的其余參數(shù)
- 例如:窗口最大化:['--start-maximized']
- ignoreHTTPSErrors:false 表示忽略 https 報錯
加載導(dǎo)航頁面
-
page.goto: 打開新頁面
-
page.goBack : 回退到上一個頁面
-
page.goForward : 前進到下一個頁面
-
page.reload: 重新加載頁面
-
page.waitForNavigation: 等待頁面跳轉(zhuǎn)
?Pupeeteer 中的基本上所有的操作都是異步的,以上幾個 API 都涉及到關(guān)于打開一個頁面,什么情況下才能判斷這個函數(shù)執(zhí)行完畢呢,這些函數(shù)都提供了兩個參數(shù) waitUtil 和 timeout , waitUtil 表示直到什么出現(xiàn)就算執(zhí)行完畢,timeout 表示如果超過這個時間還沒有結(jié)束就拋出異常。
等待元素、請求、響應(yīng)
-
page.waitForXPath: 等待 XPath 對應(yīng)的元素出現(xiàn),返回對應(yīng)的 ElementHandle 實例
-
page.waitForSelector : 等待選擇器對應(yīng)的元素出現(xiàn),返回對應(yīng)的 ElementHandle 實例
-
page.waitForResponse : 等待某個響應(yīng)結(jié)束,返回 Response 實例
-
page.waitForRequest: 等待某個請求出現(xiàn),返回 Request 實例
自定義等待
- page.waitForFunction: 等待在頁面中自定義函數(shù)的執(zhí)行結(jié)果,返回 JsHandle 實例
- page.waitFor: 設(shè)置等待時間,實在沒辦法的做法
元素定位
- page.$('#uniqueId'): 獲取某個選擇器對應(yīng)的第一個元素
- page.$$('div'): 獲取某個選擇器對應(yīng)的所有元素
- page.$x('//img'): 獲取某個 XPath 對應(yīng)的所有元素
- page.waitForXPath('//img'): 等待某個 XPath 對應(yīng)的元素出現(xiàn)
- page.waitForSelector('#uniqueId'): 等待某個選擇器對應(yīng)的元素出現(xiàn)
用戶模擬操作
- elementHandle.click(): 點擊某個元素
- elementHandle.tap(): 模擬手指觸摸點擊
- elementHandle.focus(): 聚焦到某個元素
- elementHandle.hover(): 鼠標 hover 到某個元素上
- elementHandle.type('hello'): 在輸入框輸入文本
請求攔截
我們可以在監(jiān)聽 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 頁面上都提供的觸發(fā)事件:
- page.on('close') 頁面關(guān)閉
- page.on('console') console API 被調(diào)用
- page.on('error') 頁面出錯
- page.on('load') 頁面加載完
- page.on('request') 收到請求
- page.on('requestfailed') 請求失敗
- page.on('requestfinished') 請求成功
- page.on('response') 收到響應(yīng)
- page.on('workercreated') 創(chuàng)建 webWorker
- page.on('workerdestroyed') 銷毀 webWorker
獲取 WebSocket 響應(yīng)
Puppeteer 目前沒有提供原生的用于處理 WebSocket 的 API 接口,但是我們可以通過更底層的 Chrome DevTool Protocol (CDP) 協(xié)議獲得
(async () => {const browser = await puppeteer.launch();const page = await browser.newPage();//創(chuàng)建 CDP 會話let cdpSession = await page.target().createCDPSession();//開啟網(wǎng)絡(luò)調(diào)試,監(jiān)聽 Chrome DevTools Protocol 中 Network 相關(guān)事件await cdpSession.send('Network.enable');//監(jiān)聽 webSocketFrameReceived 事件,獲取對應(yīng)的數(shù)據(jù)cdpSession.on('Network.webSocketFrameReceived', frame => {let payloadData = frame.response.payloadData;if(payloadData.includes('push:query')){//解析payloadData,拿到服務(wù)端推送的數(shù)據(jù)let res = JSON.parse(payloadData.match(/\{.*\}/)[0]);if(res.code !== 200){console.log(`調(diào)用websocket接口出錯:code=${res.code},message=${res.message}`);}else{console.log('獲取到websocket接口數(shù)據(jù):', 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(); })();植入 JavaScript 代碼
await page.evaluate(() => {// document.getElementById("#menus").classList.add("menus-show");// let menus = document.querySelector('#menus') as HTMLDivElement// menus.classList.add('menus-show')// console.log(menus)let button = document.querySelector('#all_menus_item') as HTMLDivElementbutton.addEventListener('click', function(){console.log("點擊【全部產(chǎn)品】")})})有哪些函數(shù)可以在瀏覽器環(huán)境中執(zhí)行代碼:
- page.evaluate(pageFunction[, ...args]): 在瀏覽器環(huán)境中執(zhí)行函數(shù)
- page.evaluateHandle(pageFunction[, ...args]): 在瀏覽器環(huán)境中執(zhí)行函數(shù),返回 JsHandle 對象
- page.$$eval(selector, pageFunction[, ...args]): 把 selector 對應(yīng)的所有元素傳入到函數(shù)并在瀏覽器環(huán)境執(zhí)行
- page.$eval(selector, pageFunction[, ...args]): 把 selector 對應(yīng)的第一個元素傳入到函數(shù)在瀏覽器環(huán)境執(zhí)行
- page.evaluateOnNewDocument(pageFunction[, ...args]): 創(chuàng)建一個新的 Document 時在瀏覽器環(huán)境中執(zhí)行,會在頁面所有腳本執(zhí)行之前執(zhí)行
- page.exposeFunction(name, puppeteerFunction): 在 window 對象上注冊一個函數(shù),這個函數(shù)在 Node 環(huán)境中執(zhí)行,有機會在瀏覽器環(huán)境中調(diào)用 Node.js 相關(guān)函數(shù)庫
頁面性能分析
Puppeteer 提供了對頁面性能分析的工具,目前功能還是比較弱的,只能獲取到一個頁面性能執(zhí)行的數(shù)據(jù)
(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(); })();文件的上傳和下載
(async () => {const browser = await puppeteer.launch();const page = await browser.newPage();//通過 CDP 會話設(shè)置下載路徑const cdp = await page.target().createCDPSession();await cdp.send('Page.setDownloadBehavior', {behavior: 'allow', //允許所有下載請求downloadPath: 'path/to/download' //設(shè)置下載路徑});//點擊按鈕觸發(fā)下載await (await page.waitForSelector('#someButton')).click();//等待文件出現(xiàn),輪訓(xùn)判斷文件是否出現(xiàn)await waitForFile('path/to/download/filename');//上傳時對應(yīng)的 inputElement 必須是<input>元素let inputElement = await page.waitForXPath('//input[@type="file"]');await inputElement.uploadFile('/path/to/file');browser.close(); })();跳轉(zhuǎn)新 tab 頁處理
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;模擬不同的設(shè)備
Puppeteer 提供了模擬不同設(shè)備的功能,其中 puppeteer.devices 對象上定義很多設(shè)備的配置信息,這些配置信息主要包含 viewport 和 userAgent,然后通過函數(shù) page.emulate 實現(xiàn)不同設(shè)備的模擬
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(); });模擬鍵盤
// 模擬鍵盤“回車”鍵 await page.keyboard.press('Enter');總結(jié)
以上是生活随笔為你收集整理的Puppeteer 学习笔记及基本用法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Seleunim 获取文本和标签属性的方
- 下一篇: Puppeteer + TypeScri