Puppeteer之爬蟲入門

譯者按: 本文通過(guò)簡(jiǎn)單的例子介紹如何使用Puppeteer來(lái)爬取網(wǎng)頁(yè)數(shù)據(jù),特別是用谷歌開發(fā)者工具獲取元素選擇器值得學(xué)習(xí)。

為了保證可讀性,本文采用意譯而非直譯。另外,本文版權(quán)歸原作者所有,翻譯僅用于學(xué)習(xí)。

我們將會(huì)學(xué)到什么?

在這篇文章,你講會(huì)學(xué)到如何使用JavaScript自動(dòng)化抓取網(wǎng)頁(yè)里面感興趣的內(nèi)容。我們將會(huì)使用Puppeteer,Puppeteer是一個(gè)Node庫(kù),提供接口來(lái)控制headless Chrome。Headless Chrome是一種不使用Chrome來(lái)運(yùn)行Chrome瀏覽器的方式。

如果你不知道Puppeteer,也不了解headless Chrome,那么你只要知道我們將要編寫JavaScript代碼來(lái)自動(dòng)化控制Chrome就行。

準(zhǔn)備工作

你需要安裝版本8以上的Node,你可以在這里找到安裝方法。確保選擇Current版本,因?yàn)樗?+。

當(dāng)你將Node安裝好以后,創(chuàng)建一個(gè)新的文件夾,將Puppeteer安裝在該文件夾下。

npm install --save puppeteer

例1:截屏

當(dāng)你把Puppeteer安裝好了以后,我們來(lái)嘗試第一個(gè)簡(jiǎn)單的例子。這個(gè)例子來(lái)自于Puppeteer文檔(稍微改動(dòng))。我們編寫的代碼將會(huì)把你要訪問(wèn)的網(wǎng)頁(yè)截屏并保存為png文件。

首先,創(chuàng)建一個(gè)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();

我們來(lái)一行一行理解一下代碼的含義。

  • 第1行:引入我們需要的庫(kù)Puppeteer;
  • 第3-10行:主函數(shù)getPic()包含了所有的自動(dòng)化代碼;
  • 第12行:調(diào)用getPic()函數(shù)。

這里需要提醒注意getPic()函數(shù)是一個(gè)async函數(shù),使用了ES 2017 async/await特性。該函數(shù)是一個(gè)異步函數(shù),會(huì)返回一個(gè)Promise。如果async最終順利返回值,Promise則可以順利reslove,得到結(jié)果;否則將會(huì)reject一個(gè)錯(cuò)誤。

因?yàn)槲覀兪褂昧?code>async函數(shù),我們使用await來(lái)暫停函數(shù)的執(zhí)行,直到Promise返回。

接下來(lái)我們深入理解一下getPic()

  • 第4行:

      const broswer = await puppeteer.launch();
    

    這行代碼啟動(dòng)puppeteer,我們實(shí)際上啟動(dòng)了一個(gè)Chrome實(shí)例,并且和我們聲明的browser變量綁定起來(lái)。因?yàn)槲覀兪褂昧?code>await關(guān)鍵字,該函數(shù)會(huì)暫停直到Promise完全被解析。也就是說(shuō)成功創(chuàng)建Chrome實(shí)例或則報(bào)錯(cuò)。

  • 第5行:

      const page = await browser.newPage();
    

    我們?cè)跒g覽器中創(chuàng)建一個(gè)新的頁(yè)面,通過(guò)使用await關(guān)鍵字來(lái)等待頁(yè)面成功創(chuàng)建。

  • 第6行:

      await page.goto('https://google.com');
    

    使用page.goto()打開谷歌首頁(yè)。

  • 第7行:

       await page.screenshot({path: 'google.png'});
    

    調(diào)用screenshot()函數(shù)將當(dāng)前頁(yè)面截屏。

  • 第9行:

       await browser.close();
    

    將瀏覽器關(guān)閉。

執(zhí)行實(shí)例

使用Node執(zhí)行:

  node test.js

下面截取的圖片google.png

現(xiàn)在我們來(lái)使用non-headless模式試試。將第4行代碼改為:

  const browser = await puppeteer.launch({headless: false});

然后運(yùn)行試試。你會(huì)發(fā)現(xiàn)谷歌瀏覽器打開了,并且導(dǎo)航到了谷歌搜索頁(yè)面。但是截屏沒有居中,我們可以調(diào)節(jié)一下頁(yè)面的大小配置。

await page.setViewport({width: 1000, height: 500});

截屏的效果會(huì)更加漂亮。

下面是最終版本的代碼:

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)頁(yè)上點(diǎn)擊,而且可以填寫表單,讀取數(shù)據(jù)。

接下來(lái)我們會(huì)爬取Books to Scrape,這是一個(gè)偽造的網(wǎng)上書店專門用來(lái)練習(xí)爬取數(shù)據(jù)。

在當(dāng)前目錄下,我們創(chuàng)建一個(gè)scrape.js文件,輸入如下代碼:

const puppeteer = require('puppeteer');

let scrape = async () => {
  // 爬取數(shù)據(jù)的代碼
  
  // 返回?cái)?shù)據(jù)
};

scrape().then((value) => {
    console.log(value); // 成功!
});

第一步:基本配置

我們首先創(chuàng)建一個(gè)瀏覽器實(shí)例,打開一個(gè)新頁(yè)面,并且導(dǎo)航到要爬取數(shù)據(jù)的頁(yè)面。

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;
};

注意其中有一行代碼讓瀏覽器延時(shí)關(guān)閉。這行代碼本來(lái)是不需要的,主要是方便查看頁(yè)面是否完全加載。

await page.waitFor(1000);

第二步:抓取數(shù)據(jù)

我們接下來(lái)要選擇頁(yè)面上的第一本書,然后獲取它的標(biāo)題和價(jià)格。

查看Puppeteer API,可以找到定義點(diǎn)擊的函數(shù):

page.click(selector[, options])

  • selector <string> 一個(gè)選擇器來(lái)指定要點(diǎn)擊的元素。如果多個(gè)元素滿足,那么默認(rèn)選擇第一個(gè)。

幸運(yùn)的是,谷歌開發(fā)者工具提供一個(gè)可以快速找到選擇器元素的方法。在圖片上方右擊,選擇檢查(Inspect)選項(xiàng)。

<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界面會(huì)打開,并且選定部分對(duì)應(yīng)的代碼會(huì)高亮。右擊左側(cè)的三個(gè)點(diǎn),選擇拷貝(Copy),然后選擇拷貝選擇器(Copy selector)。

接下來(lái)將拷貝的選擇器插入到函數(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í)行后會(huì)直接跳轉(zhuǎn)到詳細(xì)介紹這本書的頁(yè)面。而我們則關(guān)心它的標(biāo)題和價(jià)格部分。

為了獲取它們,我們首選需要使用page.evaluate()函數(shù)。該函數(shù)可以讓我們使用內(nèi)置的DOM選擇器,比如querySelector()。

const result = await page.evaluate(() => {
// return something
});

然后,我們使用類似的手段獲取標(biāo)題的選擇器。

使用如下代碼可以獲取該元素:

let title = document.querySelector('h1');

但是,我們真正想要的是里面的文本文字。因此,通過(guò).innerText來(lái)獲取。

let title = document.querySelector('h1').innerText;

價(jià)格也可以用相同的方法獲取。


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即可返回?cái)?shù)據(jù)

{ title: 'A Light in the Attic', price: '£51.77' }

例3:進(jìn)一步優(yōu)化

從主頁(yè)獲取所有書籍的標(biāo)題和價(jià)格,然后將它們返回。

提示

和例2的區(qū)別在于我們需要用一個(gè)循環(huán)來(lái)獲取所有書籍的信息。

const result = await page.evaluate(() => {
  let data = []; // Create an empty array
  let elements = document.querySelectorAll('xxx'); // 獲取所有書籍元素 
  // 循環(huán)處理每一個(gè)元素
    // 獲取標(biāo)題
    // 獲取價(jià)格
    data.push({title, price}); // 將結(jié)果存入數(shù)組
  return data; // 返回?cái)?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ù)組來(lái)存儲(chǔ)數(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; // 獲取價(jià)格

            data.push({title, price}); // 存入數(shù)組
        }

        return data; // 返回?cái)?shù)據(jù)
    });

    browser.close();
    return result;
};

scrape().then((value) => {
    console.log(value); // Success!
});

關(guān)于Fundebug

Fundebug專注于JavaScript、微信小程序、微信小游戲、支付寶小程序、React Native、Node.js和Java實(shí)時(shí)BUG監(jiān)控。 自從2016年雙十一正式上線,F(xiàn)undebug累計(jì)處理了7億+錯(cuò)誤事件,得到了Google、360、金山軟件、百姓網(wǎng)等眾多知名用戶的認(rèn)可。歡迎免費(fèi)試用!

版權(quán)聲明

轉(zhuǎn)載時(shí)請(qǐng)注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/11/01/guide-to-automating-scraping-the-web-with-js/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容