導讀
這篇文章,主要用於收集整理常用的Puppeteer
的一些常用API操作,自動化操作,爬蟲測試,基礎使用等等。當然至於是什麼是Puppeteer
呢,我們來看下官方介紹:Puppeteer
是谷歌官方出品的一個通過DevTools
協議控制headless Chrome
的Node
庫。可以通過Puppeteer的提供的api直接控制Chrome模擬大部分用戶操作來進行UI Test
或者作爲爬蟲訪問頁面來收集數據。
首先備註好中文API文檔地址:點擊打開Puppeteer中文文檔API查看訪問URL地址
官方API文檔地址:https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md
比較不錯的新手文檔資料參考:小一輩無產階級碼農的《Puppeteer 入門教程》
環境和安裝
cnpm i puppeteer -S
安裝的時候需要注意deshi ,Puppeteer
安裝時自帶一個最新版本的Chromium,可以通過設置環境變量或者npm config中的PUPPETEER_SKIP_CHROMIUM_DOWNLOAD
跳過下載。如果不下載的話,啓動時可以通過puppeteer.launch([options])配置項中的executablePath
指定Chromium的位置。
基本使用
我們可以通過操作Browser
實例來操作瀏覽器作出對應的事情,比如生成頁面,窗口截圖、生成pdf文件(文字可複製),獲取頁面數據等等,這些都是可以做到的。
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://rennaiqian.com');
await page.screenshot({path: 'example.png'});
await page.pdf({path: 'example.pdf', format: 'A4'});
await browser.close();
})();
當然,我們也可以新增一個headless:true
,不打開瀏覽器就能執行我們的各種操作,默認後臺運行,修改如下:
const browser = await puppeteer.launch({headless:false})
new browser.newPage()
: 這個方法可以打開一個新選項卡並返回選項卡的實例page,通過page上的各種方法可以對頁面進行常用操作。上述代碼就進行了截屏和打印pdf的操作。
當然我們最常用還是,動態的植入JS腳本,操作頁面上的元素,並且獲取一些信息,那我們該如何來實現呢?我們可以使用page.evaluate(pageFunction, ...args)
來實現這一目的,向頁面注入我們的自定義函數:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://rennaiqian.com');
// Get the "viewport" of the page, as reported by the page.
const dimensions = await page.evaluate(() => {
// 在這裏可以進行DOM操作
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
deviceScaleFactor: window.devicePixelRatio
};
});
console.log('Dimensions:', dimensions);
await browser.close();
})();
爬蟲相關實現
當然,這個puppeteer
這個框架,用的最多的地方就是在數據爬蟲,頁面模擬點擊操作,用戶名密碼自動輸入,表單提交,cookie無密碼登錄等等這些操作;
1. 模擬切換設備
這些操作呢,基本上都需要識別當前的設備,所以我們如何來模擬我們的當前設備呢?我們可以通過page.emulate(mobile)
來模擬我們所需要的設備,如下所示:
const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors'); // puppeteer內置的一些常見設備的模擬參數
const iPhone = devices['iPhone 6'];
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
await page.emulate(iPhone);
await page.goto('https://www.example.com');
// other actions...
await browser.close();
});
2. 模擬點擊輸入
使用puppeteer
的童鞋大多數都是奔着,自動化測試,使用腳本自動操作頁面內容來着,爬取動態加載網頁;所以呢,模擬點擊輸入整個功能是非常重要,且十分方便的,非常類似於selenium
框架,但是比這個框架配置要簡單得多,直接安裝就可以使用了,好,接下來我們就來看看這個點擊輸入又是怎麼完成的呢?
首先我們需要對點擊輸入分一下類別,分成 鍵盤和鼠標操作,那我們分別來看下有哪些鍵盤和鼠標操作?
2-1. 鍵盤操作
首先我們列出常用的一些API,再羅列出常用的一些小案例:
- keyboard.down(key[, options]) :觸發 keydown 事件
- keyboard.press(key[, options]) :按下某個鍵,key 表示鍵的名稱,比如 ‘ArrowLeft’ 向左鍵,詳細的鍵名映射* 請戳這裏
- keyboard.sendCharacter(char) :輸入一個字符
- keyboard.type(text, options) :輸入一個字符串
- keyboard.up(key) :觸發 keyup 事件
接下來展示一些常用到的一些小案例:
page.keyboard.press("Shift"); //按下 Shift 鍵
page.keyboard.sendCharacter('嗨'); // 輸入一個字符
page.keyboard.type('Hello'); // 一次輸入完成
page.keyboard.type('World', {delay: 100}); // 像用戶一樣慢慢輸入
2-2. 鼠標操作
- mouse.click(x, y, [options]) :移動鼠標指針到指定的位置,然後按下鼠標,這個其實 mouse.move 和mouse.down 或 mouse.up 的快捷操作
- mouse.down([options]) :觸發 mousedown 事件,options 可配置:
- options.button 按下了哪個鍵,可選值爲 [left, right, middle], 默認是 left, 表示鼠標左鍵
- options.clickCount 按下的次數,單擊,雙擊或者其他次數
- delay 按鍵延時時間
- mouse.move(x, y, [options]): 移動鼠標到指定位置, options.steps 表示移動的步長
- mouse.up([options]) :觸發 mouseup 事件
3. 修改瀏覽器運行配置
在我們爬蟲的過程中,經常會遇到各式各樣請求頭的問題,比較經常遇到的就是user-agent
等等這些參數,所以如果我們可以直接修改這些配置的話,就可以很好的進行爬蟲工作了;而且呢,這個puppeteer
同時給我們提供了一些 API
可以讓我們修改瀏覽器終端的配置:
- Page.setViewport() :修改瀏覽器視窗大小
- Page.setUserAgent() :設置瀏覽器的 UserAgent 信息
- Page.emulateMedia() :更改頁面的CSS媒體類型,用於進行模擬媒體仿真。 可選值爲 “screen”, “print”, “null”, 如果設置爲 null 則表示禁用媒體仿真。
- Page.emulate() :模擬設備,參數設備對象,比如 iPhone, Mac, Android 等,爬H5頁面,親測很好用
再接着,我們來看一下一些常用的小案例:
page.setViewport({width:1920, height:1080}); //設置視窗大小爲 1920x1080
page.setUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36');
page.emulateMedia('print'); //設置打印機媒體樣式
除此之外我們還可以模擬非 PC 機設備, 比如下面這段代碼模擬 iPhone 6
訪問百度:
const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');
const iPhone = devices['iPhone 6'];
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
await page.emulate(iPhone);
await page.goto('https://www.baidu.com');
// other actions...
await browser.close();
});
4. 獲取頁面元素
這裏呢,我們來探討下,如何使用pupeteer
來獲取頁面的元素,以及元素裏的內容及屬性;因爲這也是使用非常非常頻繁的操作,但是呢,估計大家都比較熟悉,所以呢,把它放在後面進行講解;
4-1. 獲取頁面的元素節點
(1) Page.$(selector)
獲取單個元素,底層是調用的是 document.querySelector()
, 所以選擇器的 selector
格式遵循 css
選擇器規範
let inputElement = await page.$("#search", input => input);
//下面寫法等價
let inputElement = await page.$('#search');
(2) Page.$$(selector)
獲取一組元素,底層調用的是 document.querySelectorAll()
. 返回 Promise(Array(ElemetHandle))
元素數組.
const links = await page.$$("a");
//下面寫法等價
const links = await page.$$("a", links => links);
4-1. 獲取頁面的元素屬性
Puppeteer
獲取元素屬性跟我們平時寫前段的js的邏輯有點不一樣,按照通常的邏輯,應該是現獲取元素,然後在獲取元素的屬性。但是上面我們知道 獲取元素的 API 最終返回的都是 ElemetHandle
對象,而你去查看 ElemetHandle 的 API 你會發現,它並沒有獲取元素屬性的 API.
而 Puppeteer
專門提供了一套獲取屬性的 API, Page.$eval()
和 Page.$$eval()
const value = await page.$eval('input[name=search]', input => input.value);
const href = await page.$eval('#a", ele => ele.href);
const content = await page.$eval('.content', ele => ele.outerHTML);
5. 執行自定義的 JS 腳本
執行自定義腳本也是目前來說爬蟲中,比較常用的一些功能,通過自定義腳本去拿到接口的簽名等等,都是比較一種非常不錯的爬蟲方式,好,那我們就一起來看看如何向頁面動態植入自定義腳本呢?
Puppeteer
的 Page
對象提供了一系列 evaluate 方法,你可以通過他們來執行一些自定義的 js 代碼,主要提供了下面三個 API
5-1. page.evaluate(pageFunction, …args)
這個page.evaluate
返回一個可序列化的普通對象,pageFunction 表示要在頁面執行的函數, args
表示傳入給 pageFunction
的參數, 下面的 pageFunction 和 args 表示同樣的意思。
const result = await page.evaluate(() => {
return Promise.resolve(8 * 7);
});
console.log(result); // 56
5-2. Page.evaluateHandle(pageFunction, …args)
這個evaluateHandle
在 Page 上下文執行一個 pageFunction, 返回 JSHandle 實體
const aWindowHandle = await page.evaluateHandle(() => Promise.resolve(window));
aWindowHandle; // Handle for the window object.
const aHandle = await page.evaluateHandle('document'); // Handle for the 'document'.
下面這段代碼實現獲取頁面的動態(包括js動態插入的元素) HTML 代碼:
const aHandle = await page.evaluateHandle(() => document.body);
const resultHandle = await page.evaluateHandle(body => body.innerHTML, aHandle);
console.log(await resultHandle.jsonValue());
await resultHandle.dispose();
5-3. Page.exposeFunction
Page.exposeFunction
,這個 API 用來在頁面註冊全局函數,非常有用:
因爲有時候需要在頁面處理一些操作的時候需要用到一些函數,雖然可以通過 Page.evaluate()
API 在頁面定義函數,比如下面代碼實現給 Page 上下文的 window 對象添加 md5 函數:
const puppeteer = require('puppeteer');
const crypto = require('crypto');
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
page.on('console', msg => console.log(msg.text));
await page.exposeFunction('md5', text =>
crypto.createHash('md5').update(text).digest('hex')
);
await page.evaluate(async () => {
// use window.md5 to compute hashes
const myString = 'PUPPETEER';
const myHash = await window.md5(myString);
console.log(`md5 of ${myString} is ${myHash}`);
});
await browser.close();
});
可以看出,Page.exposeFunction API
使用起來是很方便的,也非常有用,在比如給 window
對象註冊 readfile
全局函數:
const puppeteer = require('puppeteer');
const fs = require('fs');
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
page.on('console', msg => console.log(msg.text));
await page.exposeFunction('readfile', async filePath => {
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf8', (err, text) => {
if (err)
reject(err);
else
resolve(text);
});
});
});
await page.evaluate(async () => {
// use window.readfile to read contents of a file
const content = await window.readfile('/etc/hosts');
console.log(content);
});
await browser.close();
});
6. 等待執行
等待執行相關的 API 主要由 Page.waitFor
所提供
page.waitFor(selectorOrFunctionOrTimeout[, options[, …args]])
,下面三個的綜合 APIpage.waitForFunction(pageFunction[, options[, …args]])
,等待 pageFunction 執行完成之後page.waitForNavigation(options)
,等待頁面基本元素加載完之後,比如同步的 HTML, CSS, JS 等代碼page.waitForSelector(selector[, options])
,等待某個選擇器的元素加載之後,這個元素可以是異步加載的,這個 API 非常有用,你懂的。
比如我想獲取某個通過 js 異步加載的元素,那麼直接獲取肯定是獲取不到的。這個時候就可以使用 page.waitForSelector
來解決:
await page.waitForSelector('.gl-item'); //等待元素加載之後,否則獲取不到異步加載的元素
const links = await page.$$eval('.gl-item > .gl-i-wrap > .p-img > a', links => {
return links.map(a => {
return {
href: a.href.trim(),
name: a.title
}
});
});
7. 自動化網頁性能測試
通過 page.getMetrics()
可以得到一些頁面性能數據, 捕獲網站的時間線跟蹤,以幫助診斷性能問題。
- Timestamp 度量標準採樣的時間戳
- Documents 頁面文檔數
- Frames 頁面 frame 數
- JSEventListeners 頁面內事件監聽器數
- Nodes 頁面 DOM 節點數
- LayoutCount 頁面佈局總數
- RecalcStyleCount 樣式重算數
- LayoutDuration 所有頁面佈局的合併持續時間
- RecalcStyleDuration 所有頁面樣式重新計算的組合持續時間。
- ScriptDuration 所有腳本執行的持續時間
- TaskDuration 所有瀏覽器任務時長
- JSHeapUsedSize JavaScript 佔用堆大小
- JSHeapTotalSize JavaScript 堆總量
總結
總的來說, puppeteer
是一款非常不錯的 headless
工具,操作簡單,功能強大。用來做UI自動化測試,和一些小工具都是很不錯的。