譯者按: 本文通過簡單的例子介紹如何使用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行:
const page = await browser.newPage();我們在瀏覽器中創建一個新的頁面,通過使用await關鍵字來等待頁面成功創建。
-
第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!
});
關於Fundebug
Fundebug專注於JavaScript、微信小程序、微信小游戲、支付寶小程序、React Native、Node.js和Java實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了7億+錯誤事件,得到了Google、360、金山軟件、百姓網等眾多知名用戶的認可。歡迎免費試用!

版權聲明
轉載時請注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/11/01/guide-to-automating-scraping-the-web-with-js
