如何使用 Puppeteer.js 抓取网站数据?

javascriptweb developmentfront end technology更新于 2024/6/16 19:12:00

网页抓取是自动化网页数据收集过程的最佳方式之一。通常我们将网页抓取器称为"爬虫",它只是浏览网页,然后从我们选择的页面中抓取数据。自动化数据抓取过程有很多原因,因为它比手动从不同网页提取数据的过程容易得多。当我们想要从中提取数据的网页没有提供任何 API 时,抓取也是一种非常好的解决方案。

在本教程中,我们将了解如何使用 Puppeteer.js 在 Node.js 中创建网页抓取工具。

我们将分不同阶段构建此抓取工具 −

  • 第一步,我们将对应用程序进行编码,使其能够打开 Chromium,并加载我们将要抓取的特殊网站链接。

  • 下一步,我们将学习如何抓取单个页面上的所有图书的详细信息。

  • 最后,我们将学习如何抓取多个页面上的所有图书的详细信息。

先决条件

要构建此节点应用程序,有两个先决条件:

  • Node.js − 在本地机器上安装最新的稳定节点版本。

  • 代码编辑器/ IDE − 拥有一个不错的代码编辑器或您选择的 IDE。

现在我们已经完成了抓取理论和先决条件,现在是时候关注开始开发网络抓取工具的主要步骤了。

项目设置

我们需要做的第一件事是为网络抓取工具设置项目。一旦安装了"node",下一步就是在根目录中创建一个项目,然后在其中安装所需的依赖项。

我们将使用"npm"来安装依赖项。

我将该项目命名为"book-scraping-app",为此,我创建了一个同名文件夹,在其中我将运行下面显示的命令。

npm init

一旦运行上述命令,我们将在终端中得到不同的提示,请随意输入您选择的相应值。完成后,将创建一个名为"package.json"的文件。

package.json

在我的情况下,package.json 文件看起来像这样。

{
   "name": "book-scraper-app",
   "version": "0.1.0",
   "description": "",
   "main": "index.js",
   "scripts": {
      "test": "echo "Error: no test specified" && exit 1"
   },
   "author": ""Mukul Latiyan"",
   "license": "ISC"
}

现在下一步是能够在我们的项目中添加 puppeteer,我们使用下面显示的命令来执行此操作 -

npm install --save-dev puppeteer

上述命令将安装 puppeteer 以及 puppeteer 内部使用的 Chromium 版本。

一旦我们运行上述命令,我们可以通过检查 package.json 来验证我们是否已成功安装 puppeteer。

{
   "name": "book-scraper-app",
   "version": "0.1.0",
   "description": "",
   "main": "index.js",
   "scripts": {
      "test": "echo "Error: no test specified" && exit 1"
   },
   "author": ""Mukul Latiyan"",
   "license": "ISC",
   "devDependencies": {
      "puppeteer": "^15.5.0"
   }
}

现在安装了 puppeteer,下一步是更改脚本,以便我们能够以不同的方式执行我们的应用程序。

只需在"package.json"的"scripts"标签中添加以下代码即可。

{
"start": "node index.js"
}

浏览器实例设置

我们已完成项目设置。现在让我们关注浏览器实例设置,我们希望确保在运行应用程序时,Chromium 浏览器中会打开特定的 URL。

总的来说,我们将在四个文件中包含业务逻辑的代码。这些将是 −

  • browser.js − 用于通过 puppeteer 启动浏览器实例

  • index.js − 我们的 Web 应用程序的起点

  • pageController.js − 一个用于启动抓取器的简单控制器

  • pageScraper.js − 整个抓取逻辑将在此处显示。

因此,让我们首先创建 browser.js 文件,并将以下代码放入其中。

browser.js

const puppeteer = require('puppeteer');

async function browserInit() {
   let browser;
   try {
      console.log("Opening the browser......");
      browser = await puppeteer.launch({
         headless: false,
         ignoreDefaultArgs: ['--disable-extensions'],
         args: ["--disable-setuid-sandbox"], 'ignoreHTTPSErrors': true
      });
   } catch (err) {
      console.log("Could not create a browser instance => : ", err);
   }
   return browser;
}
module.exports = {
   browserInit
};

在上面的代码中,我们使用 launch 方法,该方法只是启动 Chromium 浏览器的一个实例。此 launch 方法返回一个 JS Promise,因此我们必须确保使用"then"或"await"块来处理它。

我们使用"await"关键字,然后将整个代码包装在"try catch"块中。

还应注意,我们在 launch() 方法的 JSON 参数中使用了不同的值。这些主要是 −

  • headless − 允许我们使用界面运行浏览器,从而确保我们的脚本执行。

  • ignoreHTTPSErrors − 确保您也被允许访问那些未通过安全协议网络托管的网站。

  • ignoreDefaultArgs − 确保我们能够打开 chromium 并避免任何可能阻碍我们任务的扩展。

现在,我们完成了 browser.js 文件,下一步是使用 index.js 文件。

index.js

考虑下面显示的代码。

const browserObject = require('./browser');
const scraperController = require('./pageController');

let browserInstance = browserObject.browserInit();

scraperController(browserInstance)

在上面的代码中,我们导入了"browser.js"文件和"pageController.js"文件,然后将浏览器实例传递给我们将在"pageCotroller.js"文件中编写的函数。

pageController.js

现在让我们创建一个名为"pageController.js"的文件,然后将以下代码放入其中。

const pageScraper = require('./pageScraper');
async function scrapeAll(browserInstance){
   let browser;
   try{
      browser = await browserInstance;
      await pageScraper.scraper(browser);    
   	 
   }
   catch(err){
      console.log("Could not resolve the browser instance => ", err);
   }
}
module.exports = (browserInstance) => scrapeAll(browserInstance)

在上面的代码中,我们导出了一个函数,该函数依次获取浏览器实例,然后将其传递给名为 scrapeAll() 的函数。此 scrapeAll() 函数依次将浏览器实例传递给 pageScraper.scraper()。

pageScraper.js

最后,我们需要编写"pageScraper.js"文件。请考虑下面显示的 pageScraper 代码。

const scraperObject = {
   url: 'http://books.toscrape.com/catalogue/category/books/childrens_11/index.html',
   async scraper(browser){
      let page = await browser.newPage();
      console.log(`Navigating to ${this.url}...`);
      await page.goto(this.url);
   }
}
module.exports = scraperObject;

在上面的代码中,我们有一个固定的 URL,一旦 Chromium 启动,我们就会导航到该 URL,然后我们只需调用 goto() 方法并等待响应即可。

现在,我们完成了第一个主要设置或运行 Chromium,然后等待响应。

目录结构

上述代码的目录结构应如下所示。

├── browser.js
├── index.js
├── package-lock.json
├── package.json
├── pageController.js
└── pageScraper.js

0 directories, 6 files

启动项目

要启动项目,我们需要运行下面显示的命令 -

npm run start

一旦我们运行上述命令,Chromium 浏览器将打开,然后我们将被重定向到我们在"pageScraper.js"文件中存在的 URL。

抓取数据

现在让我们关注如何借助加载数据的选择器提取该 URL 上存在的不同书籍的详细信息。

考虑下面显示的更新的 pageScraper.js 文件代码。

const scraperObject = {
   url: 'http://books.toscrape.com/catalogue/category/books/childrens_11/index.html',
   async scraper(browser) {
      let page = await browser.newPage();
      console.log(`Navigating to ${this.url}...`);
      await page.goto(this.url);
      await page.waitForSelector('.page_inner');
    
      // 提取所有所需书籍的链接
      let urls = await page.$$eval('section ol > li', links => {
         links = links.filter(link => link.querySelector('.instock.availability > i').textContent !== "In stock")
         = links.map(el => el.querySelector('h3 > a').href)
         return links;
      });
      console.log(urls);
   }
}
module.exports = scraperObject;

在上面的代码中,我们使用 pageSelector() 方法,等待包含与在 dom 中呈现的书籍相关信息的主 div。接下来,我们使用 eval() 方法,它帮助我们通过选择器部分 ol > li 获取 URL 元素。

输出

现在,如果我们重新运行该应用程序,将打开一个新的 Chromium 浏览器,然后您将获得该 URL 中存在的书籍的不同 URL。

> my-scraper@0.1.0 start
> node index.js

Opening the browser......
Navigating to http://books.toscrape.com/catalogue/category/books/childrens_11/index.html...
[
   'http://books.toscrape.com/catalogue/birdsong-a-story-in-pictures_975/index.html',
   'http://books.toscrape.com/catalogue/the-bear-and-the-piano_967/index.html',
   'http://books.toscrape.com/catalogue/the-secret-of-dreadwillow-carse_944/index.html',
   'http://books.toscrape.com/catalogue/the-white-cat-and-the-monk-a-retelling-of-the-poem-pangur-ban_865/index.html',
   'http://books.toscrape.com/catalogue/little-red_817/index.html',
   'http://books.toscrape.com/catalogue/walt-disneys-alice-in-wonderland_777/index.html',
   'http://books.toscrape.com/catalogue/twenty-yawns_773/index.html',
   'http://books.toscrape.com/catalogue/rain-fish_728/index.html',
   'http://books.toscrape.com/catalogue/once-was-a-time_724/index.html',
   'http://books.toscrape.com/catalogue/luis-paints-the-world_714/index.html',
   'http://books.toscrape.com/catalogue/nap-a-roo_567/index.html',
   'http://books.toscrape.com/catalogue/the-whale_501/index.html',
   'http://books.toscrape.com/catalogue/shrunken-treasures-literary-classics-short-sweet-and-silly_484/index.html',
   'http://books.toscrape.com/catalogue/raymie-nightingale_482/index.html',
   'http://books.toscrape.com/catalogue/playing-from-the-heart_481/index.html',
   'http://books.toscrape.com/catalogue/maybe-something-beautiful-how-art-transformed-a-neighborhood_386/index.html',
   'http://books.toscrape.com/catalogue/the-wild-robot_288/index.html',
   'http://books.toscrape.com/catalogue/the-thing-about-jellyfish_283/index.html',
   'http://books.toscrape.com/catalogue/the-lonely-ones_261/index.html',
   'http://books.toscrape.com/catalogue/the-day-the-crayons-came-home-crayons_241/index.html'
]

现在让我们对 pageScraper.js 进行修改,以便我们可以从所有抓取的链接中提取 URL,并打开一个新的页面实例,然后检索相关数据。

pageScraper.js

考虑下面显示的更新后的 pageScraper.js 文件代码。

const scraperObject = {
   url: 'http://books.toscrape.com/catalogue/category/books/childrens_11/index.html',
   async scraper(browser) {
        let page = await browser.newPage();
        console.log(`Navigating to ${this.url}...`);
        await page.goto(this.url);
        // 等待所需的 DOM 呈现
        await page.waitForSelector('.page_inner');
        
        // 获取所有所需书籍的链接
        let urls = await page.$$eval('section ol > li', links => {
    
         // 确保要抓取的书籍有库存
         links = links.filter(link => link.querySelector('.instock.availability > i').textContent !== "In stock")
       
         // 从数据中提取链接
         links = links.map(el => el.querySelector('h3 > a').href)
         return links;
      });
    
      let pagePromise = (link) => new Promise(async(resolve, reject) => {
         let sampleObj = {};
         let newPage = await browser.newPage();
         await newPage.goto(link);
         sampleObj['bookTitle'] = await newPage.$eval('.product_main > h1', text => text.textContent);
         sampleObj['bookPrice'] = await newPage.$eval('.price_color', text => text.textContent);
         sampleObj['noAvailable'] = await newPage.$eval('.instock.availability', text => {
            
            // 删除新行和制表符空格
            text = text.textContent.replace(/(\r
\t|
|\r|\t)/gm, ""); // 获取可用库存数量 let regexp = /^.*\((.*)\).*$/i; let stockAvailable = regexp.exec(text)[1].split(' ')[0]; return stockAvailable; }); sampleObj['imageUrl'] = await newPage.$eval('#product_gallery img', img => img.src); sampleObj['bookDescription'] = await newPage.$eval('#product_description', div => div.nextSibling.nextSibling.textContent); sampleObj['upc'] = await newPage.$eval('.table.table-striped > tbody > tr > td', table => table.textContent); resolve(sampleObj); await newPage.close(); }); for(link in urls){ let currentPageData = await pagePromise(urls[link]); // scrapedData.push(currentPageData); console.log(currentPageData); } } } module.exports = scraperObject;

现在我们可以从所有 URL 中提取 URL,当我们循环遍历这个数组时,就会打开一个新的 URL 并在该页面上抓取数据,然后关闭该页面,然后为数组中存在的下一个 URL 打开一个新页面。

输出

现在您可以重新运行该应用程序,您应该会看到以下输出 -

> my-scraper@0.1.0 start
> node index.js

Opening the browser......
Navigating to http://books.toscrape.com/catalogue/category/books/childrens_11/index.html...
{
   bookTitle: 'Birdsong: A Story in Pictures',
   bookPrice: '£54.64',
   noAvailable: '19',
   imageUrl: 'http://books.toscrape.com/media/cache/af/2f/af2fe2419ea136f2cd567aa92082c3ae.jpg',
   bookDescription: "Bring the thrilling story of one red bird to life. When an innocent bird meets two cruel kids, their world is forever changed. But exactly how that change unfolds is up to you, in the tradition of Kamishibai—Japanese paper theater. The wordless story by master cartoonist James Sturm is like a haiku—the elegant images leave space for children to inhabit this timeless tale—a Bring the thrilling story of one red bird to life. ...more",
   upc: '9528d0948525bf5f'
}
{
   bookTitle: 'The Bear and the Piano',
   bookPrice: '£36.89',
   noAvailable: '18',
   imageUrl: 'http://books.toscrape.com/media/cache/d0/87/d0876dcd1a6530a4cb54903aad7a3e28.jpg',
   bookDescription: 'One day, a young bear stumbles upon something he has never seen before in the forest. As time passes, he teaches himself how to play the strange instrument, and eventually the beautiful sounds are heard by a father and son who are picnicking in the woods....more',
   upc: '9f6568e9c95f60b0'
}
….

结论

在本教程中,我们学习了如何使用 puppeteer.js 在 node.js 中创建网络抓取工具。


相关文章