返回博客
指南
加布里埃尔·乔奇2021年7月28日阅读时间:7分钟

使用 Puppeteer 高级 Node.JS 进行网络抓取

使用 Puppeteer 高级 Node.JS 进行网络抓取

使用 Puppeteer 进行网络抓取概述

使用 Puppeteer 进行网络抓取概述

Google 设计 Puppeteer 的目的是在 Node.js 中提供一个简单而强大的界面,用于使用 Chromium 浏览器引擎自动执行测试和各种任务。它默认无头运行,但可以配置为运行完整的 Chrome 浏览器或 Chromium 浏览器。

Puppeteer 团队构建的应用程序接口使用DevTools 协议控制网络浏览器(如 Chrome 浏览器),并执行不同的任务,如

  • 截屏并生成 PDF 页面
  • 自动提交表单
  • 用户界面测试(点击按钮、键盘输入等)
  • 抓取 SPA 并生成预渲染内容(服务器端渲染)

您可以在浏览器中手动执行的大多数操作也可以使用 Puppeteer 完成。此外,这些操作还可以自动完成,这样您就可以节省更多时间,专注于其他事务。

Puppeteer 对开发人员也很友好。熟悉Mocha 等其他流行测试框架的人在使用 Puppeteer 时会有宾至如归的感觉,并且会发现有一个活跃的社区为 Puppeteer 提供支持。这使得 Puppeteer 在开发人员中的受欢迎程度大幅提高。

当然,Puppeteer 不仅仅适用于测试。毕竟,如果它能做标准浏览器能做的任何事情,那么它对网络搜刮程序就会非常有用。也就是说,它可以帮助执行 javascript 代码,使搜刮器可以访问页面的 HTML,并通过滚动页面或点击随机部分来模仿正常的用户行为。

这些亟需的功能使无头浏览器成为任何商业数据提取工具和除最简单的自制网络刮擦工具外的所有工具的核心组件。

先决条件

先决条件

首先,请确保您的计算机上安装了最新版本的Node.jsPuppeteer。如果没有,可以按照以下步骤安装所有先决条件。

您可以从此处下载并安装 Node.js。Node 的默认软件包管理器npm已预装 Node.js。

要安装 Puppeteer 库,可在项目根目录下运行以下命令:

npm install puppeteer
# 或 "yarn add puppeteer"

请注意,安装 Puppeteer 时,它还会下载最新版本的 Chromium,该版本保证能与 API 配合使用。

木偶人在行动

木偶人在行动

使用该库可以做很多不同的事情。由于我们的主要关注点是网络搜刮,因此我们将讨论如果你想提取网络数据,最有可能感兴趣的用例。

截图

截图

让我们从一个基本例子开始。我们将编写一个脚本,对我们选择的网站进行截屏。

请记住,Puppeteer 是一个基于承诺的库(它在引擎盖下对无头 Chrome 实例执行异步调用)。因此,让我们使用async/await 来保持代码的简洁。

首先,在项目根目录下新建一个名为index.js的文件。

在该文件中,我们需要定义一个异步函数,并将其封装在所有 Puppeteer 代码中。

const puppeteer = require('puppeteer')

async function snapScreenshot() {
	try {
		const URL = 'https://old.reddit.com/'
		const browser = await puppeteer.launch()
		const page = await browser.newPage()

		await page.goto(URL)
		await page.screenshot({ path: 'screenshot.png' })

		await browser.close()
	} catch (error) {
		console.error(error)
	}
}

snapScreenshot()

首先,使用puppeteer.launch()命令启动浏览器实例。然后,我们使用浏览器实例创建一个新页面。为了导航到所需网站,我们可以使用goto()方法,并将 URL 作为参数传递。要截屏,我们将使用screenshot()方法。我们还需要传递图片的保存位置。

请注意,Puppeteer 将初始页面尺寸设置为 800×600px,这定义了截图尺寸。你可以使用setViewport()方法自定义页面大小。

不要忘记关闭浏览器实例。然后只需在终端运行node index.js

就是这么简单!现在你应该能在项目文件夹中看到一个名为screenshot.png 的新文件。

提交表格

提交表格

如果由于某种原因,除非登录,否则要抓取的网站不会显示内容,那么可以使用 Puppeteer 自动执行登录过程。

首先,我们需要检查要扫描的网站并找到登录字段。我们可以右键单击该元素,然后选择 "检查 "选项。

在网页上右键单击,确保“检查”选项处于高亮状态,即可打开开发者工具

在我的例子中,输入在一个类名为login-form 的表单中。我们可以使用type()方法输入登录凭证。

此外,如果你想确保它执行正确的操作,可以添加无头 参数,并在启动 Puppeteer 实例时将其设置为false。然后,你就会看到 Puppeteer 是如何为你完成整个过程的。

const puppeteer = require('puppeteer')

async function login() {
   try {
       const URL = 'https://old.reddit.com/'
       const browser = await puppeteer.launch({headless: false})
       const page = await browser.newPage()

       await page.goto(URL)

       await page.type('.login-form input[name="user"]', 'EMAIL@gmail.com')
       await page.type('.login-form input[name="passwd"]', 'PASSWORD')

       await Promise.all([
           page.click('.login-form .submit button'),
           page.waitForNavigation(),
       ]);
      
       await browser.close()

   } catch (error) {
       console.error(error)
   }
}

login()

要模拟鼠标点击,我们可以使用click()方法。点击登录按钮后,我们应等待页面加载。我们可以使用waitForNavigation()方法做到这一点。

如果我们输入了正确的凭据,现在应该已经登录了!

抓取多个页面

抓取多个页面

本文将使用/r/learnprogramming子红人区。因此,我们要导航到网站,抓取每篇帖子的标题和 URL。为此,我们将使用 evaluate()方法。

代码应该是这样的

const puppeteer = require('puppeteer')

async function tutorial() {
   try {
       const URL = 'https://old.reddit.com/r/learnprogramming/'
       const browser = await puppeteer.launch()
       const page = await browser.newPage()

       await page.goto(URL)
       let data = await page.evaluate(() => {
           let results = []
           let items = document.querySelectorAll('.thing')
           items.forEach((item) => {
               results.push({
                   url: item.getAttribute('data-url'),
                   title: item.querySelector('.title').innerText,
               })
           })
           return results
       })

       console.log(data)
       await browser.close()

   } catch (error) {
       console.error(error)
   }
}

tutorial()

使用前面介绍的 Inspect 方法,我们可以通过.thing选择器抓取所有帖子。我们会遍历这些帖子,并获取每个帖子的 URL 和标题,然后将它们推入一个数组。

整个过程完成后,你可以在控制台中看到结果。

代码编辑器将抓取的输出以对象形式显示,其中包含 Reddit 帖子的 URL 和标题

很好,我们搜索到了第一页。但是,我们怎样才能搜刮到这个 subreddit 的多个页面呢?

这比你想象的要简单。代码如下

const puppeteer = require('puppeteer')

async function tutorial() {
   try {
       const URL = 'https://old.reddit.com/r/learnprogramming/'
       const browser = await puppeteer.launch({headless: false})
       const page = await browser.newPage()

       await page.goto(URL)
       let pagesToScrape = 5;
       let currentPage = 1;
       let data = []
       while (currentPage <= pagesToScrape) {
           let newResults = await page.evaluate(() => {
               let results = []
               let items = document.querySelectorAll('.thing')
               items.forEach((item) => {
                   results.push({
                       url: item.getAttribute('data-url'),
                       text: item.querySelector('.title').innerText,
                   })
               })
               return results
           })
           data = data.concat(newResults)
           if (currentPage < pagesToScrape) {
               await page.click('.next-button a')
               await page.waitForSelector('.thing')
               await page.waitForSelector('.next-button a')
           }
           currentPage++;
       }
       console.log(data)
       await browser.close()
   } catch (error) {
       console.error(error)
   }
}

tutorial()

我们需要一个变量来确定要抓取的页面数量,另一个变量是当前页面。当当前页面小于或等于我们要抓取的页面数时,我们会抓取页面上每个帖子的 URL 和标题。获取每个页面后,我们会将新结果与已搜索到的结果连接起来。

然后,我们点击下一页按钮,重复刮擦过程,直到达到所需的提取页面数。我们还需要在每个页面后递增当前页面。

更简单的选择

更简单的选择

恭喜您你已经成功地用 Puppeteer 创建了自己的网络刮板。希望您喜欢本教程!

即便如此,我们在本指南中创建的脚本仍无法完成大量艰巨的工作。它缺少了几个关键环节,而这些关键环节能让网络刮擦感觉完美无瑕。使用移动或住宅代理以及解决验证码问题只是其中缺少的几个功能。

如果你正在寻找一种更专业的数据提取方式,不妨看看WebScrapingAPI 能实现哪些功能,看看它是否适合你。有一个免费套餐,你只需投入 30 分钟的时间。

祝你刮网愉快

关于作者
加布里埃尔·乔奇,WebScrapingAPI 全栈开发工程师
加布里埃尔-西奥奇全栈开发工程师

加布里埃尔·乔奇(Gabriel Cioci)是 WebScrapingAPI 的全栈开发工程师,负责构建和维护该平台的网站、用户面板以及面向用户的核心功能模块。

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网页数据。