puppeteer是一個node庫,他能提供一系列操做Chrome的API,經過這些API咱們能夠用程序代碼來操縱Chrome去完成各類操做。javascript
他運行在node環境,由Chorme官方團隊在維護,既然是操做瀏覽器,那麼咱們能夠手動進行的操做在pupeteer上都能進行。前端
另外pupeteer的英文就是木偶的意思,咱們使用他就像操做木偶玩耍同樣,能夠輕鬆的作到:java
固然puppeteer不止能夠作這些,經過在代碼中注入js腳本,咱們幾乎能夠作到全部咱們能夠用代碼實現的功能。node
在puppeteer中,Chrome默認運行在"無頭"模式,而所謂的無頭模式,不過是不加載瀏覽器的UI界面,實際上並不會影響咱們的操做。npm
使用無頭模式,在無界面的狀況下按照咱們編寫的代碼去運行Chrome,能夠減小人爲因素的影響,使運行更加穩定。編程
固然咱們能夠經過他的屬性值headless來控制讓頁面友好的顯示出來,甚至能夠控制頁面顯示多大。json
在前端咱們能夠用它來爬取一些頁面數據進行分析,能夠進行網絡請求的攔截來達到某種業務效果,總之puppeteer很是的好用。網頁爬蟲
既然puppeteer是調用的API來操做的瀏覽器,那麼咱們能夠想到,puppeterr下的瀏覽器,與咱們平時所見到的瀏覽器有什麼不同呢?瀏覽器
又是什麼在吊起了瀏覽器進程,讓咱們可使用代碼與瀏覽器進行通訊的呢?markdown
首先讓咱們瞭解一下puppeteer對瀏覽器的總體架構的分解
其次,node庫中的這些API,他們實現的原理實際上是經過Chrome DevTools Protocol(CDP) 協議與瀏覽器進行通訊的。
而node所作的就是將底層經過協議控制的瀏覽器操做,進行簡化封裝,讓咱們能夠簡單的使用。
好比下面是經過CDP來進行頁面的跳轉:
const cdp = new CDP();
await cdp.connect(wsEndpoint);
const targetsResponse = await cdp.send('Target.getTargets');
const pageTarget = targetsResponse.targetInfos.find((t) => t.type === 'page');
const attachResponse = await cdp.send('Target.attachToTarget', { targetId: pageTarget.targetId, flatten: true });
const sessionId = attachResponse.sessionId;
const navigateResponse = await cdp.send('Page.navigate', { url: 'https://m.jk.cn' }, sessionId);
console.log('navigateResponse', navigateResponse);
複製代碼
而使用puppeteer提供的API的話咱們兩行代碼足以搞定:
const page = await browser.newPage();
await page.goto('https://m.jk.cn');
複製代碼
整體來看對話,他先是將咱們平常用到的瀏覽分紅了各個部分,而後實例化出一個瀏覽器對象,包括瀏覽器上下文,各個頁面等。能夠看到,puppeteer實際上是對CDP操做瀏覽器對代碼進行了簡化,再由node封裝導出API供咱們使用。
再把與瀏覽器通訊對CDP操做進行簡化封裝,最後使用node進行導出API,省去了中間複雜的CDP通訊過程,讓咱們直接使用代碼的方式操做瀏覽器。
咱們使用瀏覽器,大多的操做都是在頁面上進行點擊操做,鍵盤輸入操做等,其實瀏覽器自己提供的還有截圖,保存頁面pdf等功能。
在經過puppeteer對功能的封裝簡化以後,咱們使用幾行代碼即可以完成截圖,保存pdf。
值得一提的是,使用puppeteer幾乎全部操做都是異步的,會返回Promise,咱們可使用.then去處理,可是在node環境下咱們可使用async,await優雅處理
所以咱們的運行環境Nodejs 的版本不能低於 v7.6.0, 須要支持 async,await。
首先在項目目錄下
npm install puppeteer --save
複製代碼
這樣咱們就能夠開始使用puppeteer以編程的方式去操做瀏覽器啦。
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://www.baidu.com');
await page.screenshot({path: 'baidu.png'});
await browser.close();
})();
複製代碼
上面的這幾行代碼就已經實現了咱們從打開瀏覽器,跳轉頁面,截圖,到關閉瀏覽器的全部過程
先經過 puppeteer.launch() 建立一個瀏覽器實例 Browser 對象 而後經過 Browser 對象建立頁面 Page 對象 而後 page.goto() 跳轉到指定的頁面 調用 page.screenshot() 對頁面進行截圖 關閉瀏覽器 一樣的,若是咱們要獲取頁面的pdf咱們只須要調用他提供的page下的API
await page.pdf({
path: 'page.pdf',
printBackground:true,
format: 'A4'
});
複製代碼
能夠看到,每一個API均可以傳入參數,這些參數能夠肯定咱們要將獲取到的文件保存在哪path,printBackground是否須要背景圖,以及要什麼樣的尺寸format。
更多的參數屬性能夠參考官方提供的參數說明。
經過puppeteer咱們能夠調用簡單的API完成一些普通的瀏覽器操做,除了這些他還能作的事情其實還不少,咱們能夠模擬用戶實現登陸,或是爬取頁面的數據。
結合入門的基礎操做,這裏以登陸咱們豌豆app爲列作一個模擬登陸的demo,如下爲部分代碼:
(async () => {
try{
const browser = await puppeteer.launch({
headless:false,
slowMo:300
});
const page = await browser.newPage();
await page.emulate(puppeteer.devices['iPhone 6']);
await page.goto('https://m.wandougongzhu.cn/user/login');
await page.tap('.link-btn');
await page.type('#app > div > div > div:nth-child(3) > input', '12345678901', {delay:300});
await page.type('#app > div > div > div:nth-child(4) > input', '***********', {delay:300});
await page.screenshot({ path: 'full.png', fullPage: true });
await page.tap('#app > div > div > div.btn-box > div');
await page.waitForTimeout(2000);
await page.screenshot({ path: 'login.png', fullPage: true });
await browser.close();
} catch (error) {
console.log(error);
}
})();
複製代碼
爲了方便展現,咱們能夠設置headless爲false這樣就能夠顯示瀏覽器的頁面,接着咱們分析一下代碼
page.emulate調用此方法可讓頁面展現爲一種手機模式 page.tap是對頁面上對DOM元素進行點擊,這裏咱們點擊使用帳號密碼登陸 page.type這個方法能夠獲取頁面上對文本輸入DOM,獲取焦點,並輸入內容,這裏咱們輸入帳號密碼 而後獲取頁面上對登陸按鈕,進行點擊登陸 在使用page.waitForTimeout作一個頁面加載對等待,最後截圖關閉
這樣咱們就實現了以代碼對方式模擬用戶進行登陸操做,而且咱們能夠設置輸入信息對間隔時間,這樣能夠繞過一些頁面對反自動化對規則,保證代碼對穩定性。
基於這種操做咱們能夠實現一些自動化的測試,好比一些表單的提交,按鈕的點擊測試,數據的輸入等等。
經過上面的demo咱們能夠看出,puppeteer的操做大可能是針對與頁面上的DOM元素展開的,只要獲取到頁面上的DOM元素咱們就能夠展開一系列的操做。
如今的一些網頁大多數都是採用js進行後期的渲染加載,使用puppeteer這種獲取到DOM以後在對DOM進行操做對方法,會更穩定一些。
puppeteer是根據頁面上的真實存在的DOM進行各類操做的,那麼只要咱們能夠獲取到DOM元素,咱們就能夠獲取到元素在頁面上展現的值。
根據這個思路,咱們就能夠結合js完成一個簡單的網頁爬蟲,經過注入的js代碼,切換頁面,獲取頁面DOM元素,進而拿到數據。
(async () => {
let data = [];
const browser = await puppeteer.launch({
headless: false,
});
const page = await browser.newPage();
for (let mo = 1; mo < 4; mo++) {
for (let pg = 1; pg <= 10; pg++) {
mo = mo.toString().padStart(2, "0");
await page.goto(
"https://www.bilibili.com/v/music/cover/?spm_id_from=333.5.b_7375626e6176.3#" +
`/all/click/0/${pg}/2021-${mo}-01,2021-${mo}-28`
);
await page.waitForSelector(".vd-list-cnt > ul > li > div > div.r > a");
let titles = await page.$$eval(".vd-list-cnt > ul > li > div > div.r > a",
(links) => links.map((item) => item.innerText)
);
console.log(titles);
data = data.concat(titles);
}
}
fs.writeFile("data.json", JSON.stringify(data, null, "\t"), function (err) {
if (err) {
console.log(err);
}
});
})();
複製代碼
上面就是結合js進行的簡單的爬蟲,一切咱們在頁面上看到的,均可以使用puppeteer去爬取,而且能夠穩定的爬取到數據,避免一些頁面的渲染都在js中,使用日常的爬蟲很難在拿到數據。
經過這個簡單的爬蟲相信你們對puppeteer的印象又加深了很多,但願你們經過puppeteer與js代碼的結合,能夠完成一些業務上的難題。