Udemy Black Friday Sale — Thousands of Web Development & Software Development courses are on sale for only $10 for a limited time! Full details and course recommendations can be found here.javascript
本文將會教你如何用 JavaScript 自動化 web 爬蟲,技術上用到了 Google 團隊開發的 Puppeteer。 Puppeteer 運行在 Node 環境,能夠用來操做 headless Chrome。何謂 Headless Chrome?通俗來說就是在不打開 Chrome 瀏覽器的狀況下使用提供的 API 模擬用戶的瀏覽行爲。前端
若是你仍是不理解,你能夠想象成使用 JavaScript 全自動化操做 Chrome 瀏覽器。java
先確保你已經安裝了 Node 8 及以上的版本,沒有的話,能夠先到 官網 裏下載安裝。注意,必定要選「Current」處顯示的版本號大於 8 的。node
若是你是第一次接觸 Node,最好先看一下入門教程:Learn Node JS — The 3 Best Online Node JS Courses.android
安裝好 Node 以後,建立一個項目文件夾,而後安裝 Puppeteer。安裝 Puppeteer 的過程當中會附帶下載匹配版本的 Chromium(譯者注:國內網絡環境可能會出現安裝失敗的問題,能夠設置環境變量 PUPPETEER_SKIP_CHROMIUM_DOWNLOAD = 1
跳過下載,反作用是每次使用 launch
方法時,須要手動指定瀏覽器的執行路徑):ios
npm install --save puppeteer
複製代碼
Puppeteer 安裝好以後,咱們就能夠開始寫一個簡單的例子。這個例子直接照搬自官方文檔,它能夠對給定的網站進行截圖。git
首先建立一個 js 文件,名字隨便起,這裏咱們用 test.js
做爲示例,輸入如下代碼:github
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();
複製代碼
下面咱們來逐行分析上面的代碼。web
getPic()
方法。細心的讀者會發現,getPic()
前面有個 async
前綴,它表示 getPic()
方法是個異步方法。async
和 await
成對出現,屬於 ES 2017 新特性。介於它是個異步方法,因此調用以後返回的是 Promise
對象。當 async
方法返回值時,對應的 Promise
對象會將這個值傳遞給 resolve
(若是拋出異常,那麼會將錯誤信息傳遞給 Reject
)。chrome
在 async
方法中,可使用 await
表達式暫停方法的執行,直到表達式裏的 Promise
對象徹底解析以後再繼續向下執行。看不懂不要緊,後面我再詳細講解,到時候你就明白了。
接下來,咱們將會深刻分析 getPic()
方法:
const browser = await puppeteer.launch();
複製代碼
這段代碼用於啓動 puppeteer,實質上打開了一個 Chrome 的實例,而後將這個實例對象賦給變量 browser
。由於使用了 await
關鍵字,代碼運行到這裏會阻塞(暫停),直到 Promise
解析完畢(不管執行結果是否成功)
const page = await browser.newPage();
複製代碼
接下來,在上文獲取到的瀏覽器實例中新建一個頁面,等到其返回以後將新建的頁面對象賦給變量 page
。
await page.goto('https://google.com');
複製代碼
使用上文獲取到的 page
對象,用它來加載咱們給的 URL 地址,隨後代碼暫停執行,等待頁面加載完畢。
await page.screenshot({path: 'google.png'});
複製代碼
等到頁面加載完成以後,就能夠對頁面進行截圖了。screenshot()
方法接受一個對象參數,能夠用來配置截圖保存的路徑。注意,不要忘了加上 await
關鍵字。
await browser.close();
複製代碼
最後,關閉瀏覽器。
在命令行輸入如下命令執行示例代碼:
node test.js
複製代碼
如下是示例裏的截圖結果:
是否是很厲害?這只是熱身,下面教你怎麼在非 headless 環境下運行代碼。
非 headless?百聞不如一見,本身先動手試一下吧,把第 4 行的代碼:
const browser = await puppeteer.launch();
複製代碼
換成這句:
const browser = await puppeteer.launch({headless: false});
複製代碼
而後再次運行:
node test.js
複製代碼
是否是更炫酷了?當配置了 {headless: false}
以後,就能夠直觀的看到代碼是怎麼操控 Chrome 瀏覽器的。
這裏還有一個小問題,以前咱們的截圖有點沒截完整的感受,那是由於 page
對象默認的截屏尺寸有點小的緣故,咱們能夠經過下面的代碼從新設置 page
的視口大小,而後再截取:
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();
複製代碼
經過上面的例子,你應該掌握了 Puppeteer 的基本用法,下面再來看一個稍微複雜點的例子。
開始前,不妨先看看 官方文檔。你會發現 Puppeteer 能幹不少事,像是模擬鼠標的點擊、填充表單數據、輸入文字、讀取頁面數據等。
在接下來的教程裏,咱們將爬一個叫 Books To Scrape 的網站,這個網站是專門用來給開發者作爬蟲練習用的。
仍是在以前建立的文件夾裏,新建一個 js 文件,這裏用 scrape.js
做爲示例,而後輸入如下代碼:
const puppeteer = require('puppeteer');
let scrape = async () => {
// Actual Scraping goes Here...
// Return a value
};
scrape().then((value) => {
console.log(value); // Success!
});
複製代碼
有了上一個例子的經驗,這段代碼要看懂應該不難。若是你仍是看不懂的話......那也沒啥問題就是了。
首先,仍是引入 puppeteer
依賴,而後定義一個 scrape()
方法,用來寫爬蟲代碼。這個方法返回一個值,到時候咱們會處理這個返回值(示例代碼是直接打印出這個值)
先在 scrape 方法中添加下面這一行測試一下:
let scrape = async () => {
return 'test';
};
複製代碼
在命令行輸入 node scrape.js
,不出問題的話,控制檯會打印一個 test
字符串。測試經過後,咱們來繼續完善 scrape
方法。
步驟 1:前期準備
和例 1 同樣,先獲取瀏覽器實例,再新建一個頁面,而後加載 URL:
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;
};
複製代碼
再來分析一下上面的代碼:
首先,咱們建立了一個瀏覽器實例,將 headless
設置爲 false
,這樣就能直接看到瀏覽器的操做過程:
const browser = await puppeteer.launch({headless: false});
複製代碼
而後建立一個新標籤頁:
const page = await browser.newPage();
複製代碼
訪問 books.toscrape.com
:
await page.goto('http://books.toscrape.com/');
複製代碼
下面這一步可選,讓代碼暫停執行 1 秒,保證頁面能徹底加載完畢:
await page.waitFor(1000);
複製代碼
任務完成以後關閉瀏覽器,返回執行結果。
browser.close();
return result;
複製代碼
步驟 1 結束。
步驟 2: 開爬
打開 Books to Scrape 網站以後,想必你也發現了,這裏面有海量的書籍,只是數據都是假的而已。先從簡單的開始,咱們先抓取頁面裏第一本書的數據,返回它的標題和價格信息(紅色邊框選中的那本)。
查一下文檔,注意到這個方法能模擬頁面點擊:
page.click(selector[, options])
selector
選擇器,定位須要進行點擊的元素,若是有多個元素匹配,以第一個爲準。這裏可使用開發者工具查看元素的選擇器,在圖片上右擊選中 inspect:
上面的操做會打開開發者工具欄,以前選中的元素也會被高亮顯示,這個時候點擊前面的三個小點,選擇 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 API ,例如 querySelector()
。
首先建立 page.evaluate()
方法,將其返回值保存在 result
變量中:
const result = await page.evaluate(() => {
// return something
});
複製代碼
一樣,要在方法裏選擇咱們要用到的元素,再次打開開發者工具,選擇須要 inspect 的元素:
標題是個簡單的 h1
元素,使用下面的代碼獲取:
let title = document.querySelector('h1');
複製代碼
其實咱們須要的只是元素裏的文字部分,能夠在後面加上 .innerText
,代碼以下:
let title = document.querySelector('h1').innerText;
複製代碼
獲取價格信息同理:
恰好價格元素上有個 price_color
class,能夠用這個 class 做爲選擇器獲取到價格對應的元素:
let price = document.querySelector('.price_color').innerText;
複製代碼
這樣,標題和價格都有了,把它們放到一個對象裏返回:
return {
title,
price
}
複製代碼
回顧剛纔的操做,咱們獲取到了標題和價格信息,將它們保存在一個對象裏返回,返回結果賦給 result
變量。因此,如今你的代碼應該是這樣:
const result = await page.evaluate(() => {
let title = document.querySelector('h1').innerText;
let price = document.querySelector('.price_color').innerText;
return {
title,
price
}
});
複製代碼
而後只須要將 result
返回便可,返回結果會打印到控制檯:
return result;
複製代碼
最後,綜合起來代碼以下:
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' }
複製代碼
操做正確的話,在控制檯會看到正確的輸出結果,到此爲止,你已經完成了 web 爬蟲。
稍加思考一下你會發現,標題和價格信息是直接展現在首頁的,因此,徹底不必進入詳情頁去抓取這些數據。既然這樣,不妨再進一步思考,可否抓取全部書的標題和價格信息?
因此,抓取的方式其實有不少,須要你本身去發現。另外,上面提到的直接在主頁抓取數據也不必定可行,由於有些標題可能會顯示不全。
拔高題
目標 —— 抓取主頁全部書籍的標題和價格信息,而且用數組的形式保存返回。正確的輸出應該是這樣:
開幹吧,夥計,其實實現起來和上面的例子相差無幾,若是你以爲實在太難,能夠參考下面的提示。
提示:
其實最大的區別在於你須要遍歷整個結果集,代碼的大體結構以下:
const result = await page.evaluate(() => {
let data = []; // 建立一個空數組
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); // 打印結果
});
複製代碼
謝謝觀看!若是你有學習 NodeJS 的意向,能夠移步 Learn Node JS — The 3 Best Online Node JS Courses。
每週我都會發布 4 篇有關 web 開發的技術文章,歡迎訂閱!或者你也能夠在 Twitter 上 關注我
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。