譯者按: 本文通過簡單的例子介紹如何使用Puppeteer來爬取網(wǎng)頁數(shù)據(jù)创南,特別是用谷歌開發(fā)者工具獲取元素選擇器值得學(xué)習(xí)。
- 原文: A Guide to Automating & Scraping the Web with JavaScript (Chrome + Puppeteer + Node JS)
- 譯者: Fundebug
為了保證可讀性崇众,本文采用意譯而非直譯掂僵。另外,本文版權(quán)歸原作者所有顷歌,翻譯僅用于學(xué)習(xí)锰蓬。
我們將會學(xué)到什么?
在這篇文章眯漩,你講會學(xué)到如何使用JavaScript自動化抓取網(wǎng)頁里面感興趣的內(nèi)容芹扭。我們將會使用Puppeteer,Puppeteer是一個Node庫,提供接口來控制headless Chrome冯勉。Headless Chrome是一種不使用Chrome來運(yùn)行Chrome瀏覽器的方式。
如果你不知道Puppeteer摹芙,也不了解headless Chrome灼狰,那么你只要知道我們將要編寫JavaScript代碼來自動化控制Chrome就行。
準(zhǔn)備工作
你需要安裝版本8以上的Node浮禾,你可以在這里找到安裝方法交胚。確保選擇Current版本,因?yàn)樗?+盈电。
當(dāng)你將Node安裝好以后蝴簇,創(chuàng)建一個新的文件夾,將Puppeteer安裝在該文件夾下匆帚。
npm install --save puppeteer
例1:截屏
當(dāng)你把Puppeteer安裝好了以后熬词,我們來嘗試第一個簡單的例子。這個例子來自于Puppeteer文檔(稍微改動)吸重。我們編寫的代碼將會把你要訪問的網(wǎng)頁截屏并保存為png文件互拾。
首先,創(chuàng)建一個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行:主函數(shù)
getPic()
包含了所有的自動化代碼骑疆; - 第12行:調(diào)用
getPic()
函數(shù)。
這里需要提醒注意getPic()
函數(shù)是一個async
函數(shù)替废,使用了ES 2017 async/await
特性箍铭。該函數(shù)是一個異步函數(shù),會返回一個Promise
椎镣。如果async
最終順利返回值坡疼,Promise
則可以順利reslove
,得到結(jié)果衣陶;否則將會reject
一個錯誤柄瑰。
因?yàn)槲覀兪褂昧?code>async函數(shù),我們使用await
來暫停函數(shù)的執(zhí)行剪况,直到Promise
返回教沾。
接下來我們深入理解一下getPic()
:
-
第4行:
const broswer = await puppeteer.launch();
這行代碼啟動puppeteer,我們實(shí)際上啟動了一個Chrome實(shí)例译断,并且和我們聲明的
browser
變量綁定起來授翻。因?yàn)槲覀兪褂昧?code>await關(guān)鍵字,該函數(shù)會暫停直到Promise
完全被解析。也就是說成功創(chuàng)建Chrome實(shí)例或則報錯堪唐。 -
第5行:
const page = await browser.newPage();
我們在瀏覽器中創(chuàng)建一個新的頁面巡语,通過使用await關(guān)鍵字來等待頁面成功創(chuàng)建。
-
第6行:
await page.goto('https://google.com');
使用
page.goto()
打開谷歌首頁淮菠。 -
第7行:
await page.screenshot({path: 'google.png'});
調(diào)用
screenshot()
函數(shù)將當(dāng)前頁面截屏男公。 -
第9行:
await browser.close();
將瀏覽器關(guān)閉。
執(zhí)行實(shí)例
使用Node
執(zhí)行:
node test.js
下面截取的圖片google.png
:
現(xiàn)在我們來使用non-headless模式試試合陵。將第4行代碼改為:
const browser = await puppeteer.launch({headless: false});
然后運(yùn)行試試枢赔。你會發(fā)現(xiàn)谷歌瀏覽器打開了,并且導(dǎo)航到了谷歌搜索頁面拥知。但是截屏沒有居中踏拜,我們可以調(diào)節(jié)一下頁面的大小配置。
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:爬取數(shù)據(jù)
首先速梗,了解一下Puppeteer的API。文檔提供了非常豐富的方法不僅支持在網(wǎng)頁上點(diǎn)擊襟齿,而且可以填寫表單镀琉,讀取數(shù)據(jù)。
接下來我們會爬取Books to Scrape蕊唐,這是一個偽造的網(wǎng)上書店專門用來練習(xí)爬取數(shù)據(jù)屋摔。
在當(dāng)前目錄下,我們創(chuàng)建一個scrape.js
文件替梨,輸入如下代碼:
const puppeteer = require('puppeteer');
let scrape = async () => {
// 爬取數(shù)據(jù)的代碼
// 返回數(shù)據(jù)
};
scrape().then((value) => {
console.log(value); // 成功钓试!
});
第一步:基本配置
我們首先創(chuàng)建一個瀏覽器實(shí)例,打開一個新頁面副瀑,并且導(dǎo)航到要爬取數(shù)據(jù)的頁面弓熏。
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;
};
注意其中有一行代碼讓瀏覽器延時關(guān)閉。這行代碼本來是不需要的糠睡,主要是方便查看頁面是否完全加載挽鞠。
await page.waitFor(1000);
第二步:抓取數(shù)據(jù)
我們接下來要選擇頁面上的第一本書,然后獲取它的標(biāo)題和價格狈孔。
查看Puppeteer API信认,可以找到定義點(diǎn)擊的函數(shù):
page.click(selector[, options])
- selector <string> 一個選擇器來指定要點(diǎn)擊的元素。如果多個元素滿足均抽,那么默認(rèn)選擇第一個嫁赏。
幸運(yùn)的是,谷歌開發(fā)者工具提供一個可以快速找到選擇器元素的方法油挥。在圖片上方右擊潦蝇,選擇檢查(Inspect)選項款熬。
<div style="text-align: center;">
<img style="width:75%;" src="guide-to-automating-scraping-the-web-with-js/first_book_inspect.png" />
</div>
谷歌開發(fā)者工具的Elements
界面會打開,并且選定部分對應(yīng)的代碼會高亮攘乒。右擊左側(cè)的三個點(diǎn)贤牛,選擇拷貝(Copy),然后選擇拷貝選擇器(Copy selector)则酝。
接下來將拷貝的選擇器插入到函數(shù)中殉簸。
await page.click('#default > div > div > div > div > section > div:nth-child(2) > ol > li:nth-child(1) > article > div.image_container > a > img');
加入了點(diǎn)擊事件的代碼執(zhí)行后會直接跳轉(zhuǎn)到詳細(xì)介紹這本書的頁面。而我們則關(guān)心它的標(biāo)題和價格部分堤魁。
為了獲取它們喂链,我們首選需要使用page.evaluate()
函數(shù)返十。該函數(shù)可以讓我們使用內(nèi)置的DOM選擇器妥泉,比如querySelector()
。
const result = await page.evaluate(() => {
// return something
});
然后洞坑,我們使用類似的手段獲取標(biāo)題的選擇器盲链。
使用如下代碼可以獲取該元素:
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!
});
運(yùn)行node scrape.js
即可返回數(shù)據(jù)
{ title: 'A Light in the Attic', price: '£51.77' }
例3:進(jìn)一步優(yōu)化
從主頁獲取所有書籍的標(biāo)題和價格布蔗,然后將它們返回。
提示
和例2的區(qū)別在于我們需要用一個循環(huán)來獲取所有書籍的信息浪腐。
const result = await page.evaluate(() => {
let data = []; // Create an empty array
let elements = document.querySelectorAll('xxx'); // 獲取所有書籍元素
// 循環(huán)處理每一個元素
// 獲取標(biāo)題
// 獲取價格
data.push({title, price}); // 將結(jié)果存入數(shù)組
return data; // 返回數(shù)據(jù)
});
解法
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 = []; // 初始化空數(shù)組來存儲數(shù)據(jù)
let elements = document.querySelectorAll('.product_pod'); // 獲取所有書籍元素
for (var element of elements){ // 循環(huán)
let title = element.childNodes[5].innerText; // 獲取標(biāo)題
let price = element.childNodes[7].children[0].innerText; // 獲取價格
data.push({title, price}); // 存入數(shù)組
}
return data; // 返回數(shù)據(jù)
});
browser.close();
return result;
};
scrape().then((value) => {
console.log(value); // Success!
});
關(guān)于Fundebug
Fundebug專注于JavaScript纵揍、微信小程序、微信小游戲议街、支付寶小程序泽谨、React Native、Node.js和Java實(shí)時BUG監(jiān)控特漩。 自從2016年雙十一正式上線吧雹,F(xiàn)undebug累計處理了7億+錯誤事件,得到了Google涂身、360吮炕、金山軟件、百姓網(wǎng)等眾多知名用戶的認(rèn)可访得。歡迎免費(fèi)試用龙亲!
版權(quán)聲明
轉(zhuǎn)載時請注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/11/01/guide-to-automating-scraping-the-web-with-js/