譯者按: 本文通過簡單的例子介紹如何使用Puppeteer來爬取網頁數據,特別是用谷歌開發者工具獲取元素選擇器值得學習。
- 原文: A Guide to Automating & Scraping the Web with JavaScript (Chrome + Puppeteer + Node JS)
- 譯者: Fundebug
為了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原作者所有,翻譯僅用於學習。
我們將會學到什么?
在這篇文章,你講會學到如何使用JavaScript自動化抓取網頁里面感興趣的內容。我們將會使用Puppeteer,Puppeteer是一個Node庫,提供接口來控制headless Chrome。Headless Chrome是一種不使用Chrome來運行Chrome瀏覽器的方式。
如果你不知道Puppeteer,也不了解headless Chrome,那么你只要知道我們將要編寫JavaScript代碼來自動化控制Chrome就行。
准備工作
你需要安裝版本8以上的Node,你可以在這里找到安裝方法。確保選擇Current版本,因為它是8+。
當你將Node安裝好以后,創建一個新的文件夾,將Puppeteer安裝在該文件夾下。
npm install –save puppeteer
例1:截屏
當你把Puppeteer安裝好了以后,我們來嘗試第一個簡單的例子。這個例子來自於Puppeteer文檔(稍微改動)。我們編寫的代碼將會把你要訪問的網頁截屏並保存為png文件。
首先,創建一個test.js
文件,並編寫如下代碼。
const puppeteer = require('puppeteer'); async function getPic() { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://google.com'); await page.screenshot({path: 'google.png'}); await browser.close(); } getPic();
我們來一行一行理解一下代碼的含義。
- 第1行:引入我們需要的庫Puppeteer;
- 第3-10行:主函數
getPic()
包含了所有的自動化代碼; - 第12行:調用
getPic()
函數。
這里需要提醒注意getPic()
函數是一個async
函數,使用了ES 2017 async/await
特性。該函數是一個異步函數,會返回一個Promise
。如果async
最終順利返回值,Promise
則可以順利reslove
,得到結果;否則將會reject
一個錯誤。
因為我們使用了async
函數,我們使用await
來暫停函數的執行,直到Promise
返回。
接下來我們深入理解一下getPic()
:
-
第4行:
const broswer = await puppeteer.launch();
這行代碼啟動puppeteer,我們實際上啟動了一個Chrome實例,並且和我們聲明的
browser
變量綁定起來。因為我們使用了await
關鍵字,該函數會暫停直到Promise
完全被解析。也就是說成功創建Chrome實例或則報錯
。 -
第5行:
我們在瀏覽器中創建一個新的頁面,通過使用await關鍵字來等待頁面成功創建
const page = await browser.newPage();
-
第6行:
await page.goto('https://google.com');
使用
page.goto()
打開谷歌首頁
。 -
第7行:
await page.screenshot({path: 'google.png'});
調用
screenshot()
函數將當前頁面截屏
。 -
第9行:
將瀏覽器關閉
await browser.close();
執行實例
使用Node
執行:
node test.js
下面截取的圖片google.png
:
現在我們來使用non-headless模式試試。將第4行代碼改為:
const browser = await puppeteer.launch({headless: false});
然后運行試試。你會發現谷歌瀏覽器打開了,並且導航到了谷歌搜索頁面。但是截屏沒有居中,我們可以調節一下頁面的大小配置。
await page.setViewport({width: 1000, height: 500});
截屏的效果會更加漂亮。
下面是最終版本的代碼:
const puppeteer = require('puppeteer'); async function getPic() { const browser = await puppeteer.launch({headless: false}); const page = await browser.newPage(); await page.goto('https://google.com'); await page.setViewport({width: 1000, height: 500}) await page.screenshot({path: 'google.png'}); await browser.close(); } getPic();
例2:爬取數據
首先,了解一下Puppeteer的API。文檔提供了非常豐富的方法不僅支持在網頁上點擊,而且可以填寫表單,讀取數據。
接下來我們會爬取Books to Scrape,這是一個偽造的網上書店專門用來練習爬取數據。
在當前目錄下,我們創建一個scrape.js
文件,輸入如下代碼:
const puppeteer = require('puppeteer'); let scrape = async () => { // 爬取數據的代碼 // 返回數據 }; scrape().then((value) => { console.log(value); // 成功! });
第一步:基本配置
我們首先創建一個瀏覽器實例,打開一個新頁面,並且導航到要爬取數據的頁面。
let scrape = async () => { const browser = await puppeteer.launch({headless: false}); const page = await browser.newPage(); await page.goto('http://books.toscrape.com/'); await page.waitFor(1000); // Scrape browser.close(); return result; };
注意其中有一行代碼讓瀏覽器延時關閉。這行代碼本來是不需要的,主要是方便查看頁面是否完全加載。
await page.waitFor(1000);
第二步:抓取數據
我們接下來要選擇頁面上的第一本書,然后獲取它的標題和價格。
查看Puppeteer API,可以找到定義點擊的函數:
page.click(selector[, options])
- selector 一個選擇器來指定要點擊的元素。如果多個元素滿足,那么默認選擇第一個。
幸運的是,谷歌開發者工具提供一個可以快速找到選擇器元素的方法。在圖片上方右擊,選擇檢查(Inspect)選項。
谷歌開發者工具的Elements
界面會打開,並且選定部分對應的代碼會高亮。右擊左側的三個點,選擇拷貝(Copy),然后選擇拷貝選擇器(Copy selector)。
接下來將拷貝的選擇器插入到函數中。
await page.click('#default > div > div > div > div > section > div:nth-child(2) > ol > li:nth-child(1) > article > div.image_container > a > img');
加入了點擊事件的代碼執行后會直接跳轉到詳細介紹這本書的頁面。而我們則關心它的標題和價格部分。
為了獲取它們,我們首選需要使用page.evaluate()
函數。該函數可以讓我們使用內置的DOM選擇器,比如querySelector()
。
const result = await page.evaluate(() => { // return something });
然后,我們使用類似的手段獲取標題的選擇器。
使用如下代碼可以獲取該元素:
let title = document.querySelector('h1');
但是,我們真正想要的是里面的文本文字。因此,通過.innerText
來獲取。
let title = document.querySelector('h1').innerText;
價格也可以用相同的方法獲取。
let price = document.querySelector('.price_color').innerText;
最終,將它們一起返回,完整代碼如下:
const result = await page.evaluate(() => { let title = document.querySelector('h1').innerText; let price = document.querySelector('.price_color').innerText; return { title, price } });
所有的代碼整合到一起,如下:
const puppeteer = require('puppeteer'); let scrape = async () => { const browser = await puppeteer.launch({headless: false}); const page = await browser.newPage(); await page.goto('http://books.toscrape.com/'); await page.click('#default > div > div > div > div > section > div:nth-child(2) > ol > li:nth-child(1) > article > div.image_container > a > img'); await page.waitFor(1000); const result = await page.evaluate(() => { let title = document.querySelector('h1').innerText; let price = document.querySelector('.price_color').innerText; return { title, price } }); browser.close(); return result; }; scrape().then((value) => { console.log(value); // Success! });
運行node scrape.js
即可返回數據
{ title: 'A Light in the Attic', price: '£51.77' }
例3:進一步優化
從主頁獲取所有書籍的標題和價格,然后將它們返回。
提示
和例2的區別在於我們需要用一個循環來獲取所有書籍的信息。
const result = await page.evaluate(() => { let data = []; // Create an empty array let elements = document.querySelectorAll('xxx'); // 獲取所有書籍元素 // 循環處理每一個元素 // 獲取標題 // 獲取價格 data.push({title, price}); // 將結果存入數組 return data; // 返回數據 });
解法
const puppeteer = require('puppeteer'); let scrape = async () => { const browser = await puppeteer.launch({headless: false}); const page = await browser.newPage(); await page.goto('http://books.toscrape.com/'); const result = await page.evaluate(() => { let data = []; // 初始化空數組來存儲數據 let elements = document.querySelectorAll('.product_pod'); // 獲取所有書籍元素 for (var element of elements){ // 循環 let title = element.childNodes[5].innerText; // 獲取標題 let price = element.childNodes[7].children[0].innerText; // 獲取價格 data.push({title, price}); // 存入數組 } return data; // 返回數據 }); browser.close(); return result; }; scrape().then((value) => { console.log(value); // Success! });