网络抓取是自动化从网络收集数据的过程的最佳方法之一。通常我们将网络抓取工具称为“爬虫”,它只是在网上冲浪,然后向我们提供从我们选择的页面中抓取的数据。自动化数据抓取过程有很多原因,因为它比从不同网页手动提取数据的过程要容易得多。当我们要从中提取数据的网页没有为我们提供任何 API 时,抓取也是一个非常好的解决方案
在本教程中,我们将了解如何使用 Puppeteer.js 在 Node.js 中创建网络抓取工具。
我们将在不同的阶段构建这个抓取工具 -
第一步,我们将对应用程序进行编码,使其能够打开 Chromium 并加载我们将抓取的特殊网站链接。
下一步,我们将学习如何抓取单个页面上所有书籍的详细信息。
最后,我们将学习如何抓取多个页面中存在的所有书籍的详细信息。
为了能够构建此节点应用程序,有两个先决条件:
Node.js - 在本地计算机上安装最新稳定版本的节点。
代码编辑器/ IDE - 选择一个合适的代码编辑器或 IDE。
现在我们已经了解了抓取理论和先决条件,现在是我们重点关注开始开发网络抓取工具的主要步骤的时候了。
我们需要的第一件事是为网络抓取工具设置项目。一旦我们安装了“节点”,下一步就是在根目录中创建一个项目,然后在其中安装所需的依赖项。
我们将使用“npm”来安装依赖项。
我将该项目命名为“book-scraping-app”,为此,我创建了一个同名的文件夹,在其中我将运行如下所示的命令。
npm init
运行上述命令后,终端中将出现不同的提示,请随意输入您选择的相应值。完成后,将创建一个名为“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 - 我们网络应用程序的起点
pageController.js - 一个启动抓取工具的简单控制器
pageScraper.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 参数中使用了不同的值。这些主要是 -
无头 - 允许我们通过界面运行浏览器,从而确保我们的脚本执行。
ignoreHTTPSErrors - 确保您也可以访问那些不是通过安全协议网络托管的网站。
ignoreDefaultArgs - 确保我们能够打开 chromium 并避免任何可能阻止我们任务的扩展。
现在,我们已经完成了 browser.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”的文件,然后将以下代码放入其中。
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 代码。
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'); // extracting the links of all the required books 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 文件代码。
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); // Wait for the required DOM to be rendered await page.waitForSelector('.page_inner'); // Get the link to all the required books let urls = await page.$$eval('section ol > li', links => { // Make sure the book to be scraped is in stock links = links.filter(link => link.querySelector('.instock.availability > i').textContent !== "In stock") // Extract the links from the data 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 => { // Strip new line and tab spaces text = text.textContent.replace(/(rt||r|t)/gm, ""); // Get the number of stock available 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 中创建网络抓取工具。