Puppeteer之爬蟲入門


譯者按: 本文通過簡單的例子介紹如何使用Puppeteer來爬取網頁數據,特別是用谷歌開發者工具獲取元素選擇器值得學習。

為了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原作者所有,翻譯僅用於學習。

我們將會學到什么?

在這篇文章,你講會學到如何使用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!
});

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM