返回博客
指南
Robert SfichiLast updated on Apr 28, 20263 min read

使用 JavaScript 和 Node.Js 进行网络抓取

使用 JavaScript 和 Node.Js 进行网络抓取

说实话,从现在开始,互联网的数据量只会越来越大。对此我们确实无能为力。这就是网络爬虫派上用场的地方。

在接下来的文章中,我们将向您展示如何使用 JavaScript 作为主要编程语言来构建自己的 Web 爬虫。

了解基于 JavaScript 的网页抓取

网页爬虫是一种软件工具,它能帮助您自动化完成从第三方网站收集有用数据的繁琐过程。通常,这一过程包括向特定网页发送请求、读取 HTML 代码,并解析该代码以提取数据。

为何要进行数据抓取?

假设您想创建一个比价平台。您需要从几家网店获取若干商品的价格。借助网页抓取工具,您只需几分钟就能完成这项工作。

也许您正试图为公司获取新的潜在客户,甚至想找到最优惠的机票或酒店价格。在为撰写本文而进行网络爬取和调研时,我们偶然发现了 Brisk Voyage。

Brisk Voyage 是一款帮助用户寻找廉价、最后一刻周末旅行的网络应用。它利用某种网络爬虫技术,持续监测航班和酒店价格。当爬虫发现价格异常低廉的旅行方案时,用户会收到一封包含预订说明的电子邮件。

网络爬虫有哪些用途?

开发者使用网络爬虫进行各种数据采集,但最常见的应用场景包括:

  • 市场分析
  • 价格比较
  • 潜在客户开发
  • 学术研究
  • 收集机器学习的训练和测试数据集

使用 JavaScript 和 Node.js 进行网页抓取面临哪些挑战?

你知道那些让你承认自己不是机器人的小复选框吗?唉,它们并不总能成功地挡住机器人。

但大多数时候它们确实有效,一旦搜索引擎发现你未经许可试图抓取其网站,就会限制你的访问权限。

网络爬虫面临的另一大障碍是网站结构的变更。网站结构的细微变化都可能让我们白白浪费大量时间。网络爬虫工具需要频繁更新才能适应变化并完成任务。

网络爬虫面临的另一项挑战是地理封锁。根据您的物理位置,如果请求来自不可信的地区,网站可能会完全禁止您的访问。

为应对这些挑战并助您专注于产品开发,我们推出了 WebScrapingAPI。这是一个易于使用、具备企业级扩展能力的 API,可帮助您收集和管理 HTML 数据。 我们对速度极度追求,采用全球轮换代理网络,目前已有超过 10,000 家客户正在使用我们的服务。如果您觉得没有时间从头开始构建网络爬虫,可以尝试使用 WebScrapingAPI 的免费套餐。

API:网络爬虫的便捷之选

大多数网络应用程序都提供 API,允许用户以预定且有条理的方式访问其数据。用户向特定端点发送请求,应用程序便会返回用户明确要求的所有数据。通常情况下,这些数据已格式化为 JSON 对象。

使用应用程序接口时,通常无需担心前述的障碍。尽管如此,API 也会进行更新。这种情况下,用户必须时刻关注所使用的 API,并相应地更新代码,以免功能失效。

此外,API 的文档至关重要。如果 API 的功能未被清晰记录,用户将因此浪费大量时间。

理解网络

要真正理解互联网,需要掌握大量知识。让我们简要介绍一下所有有助于您更好地理解网络爬虫的术语。

HTTP(超文本传输协议)是网络上任何数据交换的基础。顾名思义,HTTP 是一种客户端-服务器协议。作为 HTTP 客户端的网页浏览器会向 HTTP 服务器建立连接并发送消息,例如:“嘿!最近怎么样?能把那些图片发给我吗?”。服务器通常会返回响应(如 HTML 代码),并关闭连接。

假设您需要访问 Google。如果您在网页浏览器中输入网址并按下回车键,HTTP 客户端(即浏览器)将向服务器发送以下消息:

GET / HTTP/1.1
Host: google.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/web\p,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Connection: keep-aliveUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36

消息的第一行包含请求方法(GET)、请求的目标路径(在本例中仅为 '/',因为我们只访问了 www.google.com)、HTTP 协议版本,以及多个标头,如 Connection 或 User-Agent。

下面我们来谈谈该过程中最重要的几个标头字段:

  • Host:你在网页浏览器中输入地址并按下回车键后所访问的服务器域名。
  • User-Agent:此处显示发起请求的客户端信息。如您所见,__(Macintosh; Intel Mac OS X 10_11_6)__ 部分表明我使用的是 MacBook,而 __(Chrome/56.0.2924.87)__ 则表示我使用的浏览器是 Chrome。
  • Accept:通过此标头,客户端限制服务器仅向其发送特定类型的响应,例如 application/JSON 或 text/plain。
  • Referrer:该标头字段包含发起请求的页面的地址。网站会利用此标头根据用户来源调整其内容。

服务器响应可能如下所示:

HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu) Content-Type: text/html; charset=utf-8 
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>The content of the document</body>
</html>

如您所见,第一行是 HTTP 响应码:**200 OK。这表示抓取操作已成功。

如果我们通过网页浏览器发送请求,浏览器会解析 HTML 代码,获取 CSS、JavaScript 文件、图片等所有其他资源,并渲染出网页的最终版本。在接下来的步骤中,我们将尝试实现该过程的自动化。

通过 Node.js 理解网页抓取

JavaScript最初的创建目的是帮助用户为网站添加动态内容。在早期,它无法直接与计算机或其数据进行交互。当您访问网站时,浏览器会读取JavaScript代码,并将其转换为计算机可处理的几行代码。

Node.js 的出现,使 JavaScript 不仅能在客户端运行,还能在服务器端运行。Node.js 可以定义为一款用于服务器端编程的免费开源 JavaScript 环境。它能帮助用户快速构建和运行网络应用程序。

const http = require('http');
const port = 8000;
const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello world!');
});
server.listen(port, () => {
  console.log(`Server running on port 8000.`);
});

若您尚未安装 Node.js,请查看下一步的安装指南。否则,请创建一个新的 index.js 文件,并在终端中输入 `node index.js` 运行它,随后打开浏览器并访问 localhost:8000。您应该会看到以下字符串:“Hello world!”。

所需工具

Chrome - 请按照此处的安装指南进行操作。

  • VSCode - 您可从本页面下载,并按照说明将其安装到您的设备上。
  • Node.js - 在开始使用 Axios、Cheerio 或 Puppeteer 之前,我们需要安装 Node.js 及 Node 包管理器(NPM)。安装 Node.js 和 NPM 的最简便方法是从 Node.js 官方网站获取安装程序并运行。

安装完成后,您可以在终端窗口中运行 `node -v``npm -v` 来验证 Node.js 是否已安装。版本号应高于 v14.15.5。如果您在此过程中遇到问题,还有另一种安装 Node.js 的方法。

现在,让我们创建一个新的 NPM 项目。为该项目创建一个新文件夹,并运行 `npm init -y`。接下来,我们开始安装依赖项。

  • Axios——一个用于从 Node.js 发起 HTTP 请求的 JavaScript 库。在新创建的文件夹中运行 `npm install axios`
  • Cheerio——一个开源库,通过解析标记语言并提供用于操作解析结果数据的 API,帮助我们提取有用信息。要使用 Cheerio,您可以通过选择器来选择 HTML 文档中的标签。选择器的形式如下:$("div")。这个特定选择器可帮助我们选中页面上的所有 <div> 元素。

要安装 Cheerio,请在项目文件夹中运行 npm install cheerio

  • Puppeteer——一个 Node.js 库,通过提供高级 API 来控制 Chrome 或 Chromium。

要使用 Puppeteer,需通过类似命令进行安装:npm install puppeteer。请注意,安装此包时,系统还会自动安装一个最新版本的 Chromium,该版本保证与您所使用的 Puppeteer 版本兼容。

数据源检查

首先,您需要通过 Chrome 或任何其他网页浏览器访问您想要抓取的网站。要成功抓取所需的数据,您必须了解该网站的结构。

目标网站勘察

成功访问网站后,请像普通用户一样进行操作。例如,如果您访问了 /r/dundermifflin 子版块,可以点击主页上的帖子查看详情,浏览评论和赞数,甚至可以按特定时间段内的投票数对帖子进行排序。

如您所见,该网站包含一个帖子列表,每个帖子都有相应的赞数和评论。

仅通过查看网站的URL,就能了解其大量信息。在此案例中,https://www.old.reddit.com/r/DunderMifflin 代表基础URL,即引导我们进入“The Office”Reddit社区的路径。 当你开始按点赞数对帖子进行排序时,会发现基础 URL 已变为 https://www.old.reddit.com/r/DunderMifflin/top/?t=year。

查询参数是 URL 的扩展部分,用于根据传递的数据来定义具体的内容或操作。在本例中,“?t=year”代表所选的时间范围,即我们希望查看该时间段内点赞数最高的帖子。

只要你仍处于这个特定的子版块中,基础 URL 就会保持不变。唯一会改变的是查询参数。我们可以将它们视为应用于数据库的过滤器,用于检索我们想要的数据。只需更改 URL,你就可以切换时间范围,仅查看过去一个月或一周内点赞数最高的帖子。

使用开发者工具进行检查

在接下来的步骤中,您将进一步了解页面上信息是如何组织的。您需要这样做,才能更好地理解我们实际上可以从源数据中抓取哪些内容。

开发者工具可帮助您交互式地探索网站的文档对象模型(DOM)。我们将使用 Chrome 的开发者工具,但您也可以使用任何您熟悉的网页浏览器。在 Chrome 中,您可以在页面任意位置右键点击,然后选择“检查”选项来打开它。

在屏幕上弹出的新菜单中,请选择“元素”选项卡。这将显示网站的交互式 HTML 结构。

您可以通过编辑结构、展开或折叠元素,甚至删除元素来与网站进行交互。请注意,这些更改仅对您可见。

正则表达式及其作用

正则表达式(也称为 RegEx)可帮助您创建规则,从而查找和管理不同的字符串。如果您需要解析大量信息,掌握正则表达式将为您节省大量时间。

初次接触正则表达式时,它似乎有些复杂,但实际上使用起来相当简单。让我们看一个例子:\d。使用这个表达式,您可以轻松匹配 0 到 9 之间的任意数字。 当然,还有更复杂的表达式,例如:^(\(\d{3}\)|^\d{3}[.-]?)?\d{3}[.-]?\d{4}$。该表达式可匹配电话号码,无论区号是否用括号包围,或数字之间是否用点分隔。

如您所见,正则表达式非常易于使用,只要花足够的时间掌握,它就能发挥巨大的作用。

了解 Cheerio.js

在成功安装了前面介绍的所有依赖项,并使用开发者工具检查了 DOM 之后,你就可以开始实际的抓取工作了。

有一点需要牢记:若您要抓取的页面是 SPA(单页应用),Cheerio 可能并非最佳选择。原因是 Cheerio 无法像网页浏览器那样“思考”。因此,在接下来的步骤中我们将使用 Puppeteer。但在那之前,让我们先来了解 Cheerio 的强大之处。

为了测试 Cheerio 的功能,让我们尝试收集之前介绍的子版块 /r/dundermifflin 中的所有帖子标题。

创建一个名为 index.js 的新文件,并输入或直接复制以下代码:

const axios = require("axios");
const cheerio = require("cheerio");
const fetchTitles = async () => {
	try {
		const response = await axios.get('https://old.reddit.com/r/DunderMifflin/');
                const html = response.data;
		const $ = cheerio.load(html);
		const titles = [];
		$('div > p.title > a').each((_idx, el) => {
			const title = $(el).text()
			titles.push(title)
		});
		return titles;
	} catch (error) {
		throw error;
	}};
fetchTitles().then((titles) => console.log(titles));

为了更好地理解上述代码,我们将解释异步函数 fetchTitles() 的作用:

首先,我们使用 Axios 库向旧版 Reddit 网站发起一个 GET 请求。 随后,Cheerio 在第 10 行加载了该请求的结果。通过开发者工具,我们发现包含所需信息的元素是几个锚点标签。为了确保我们只选择包含帖子标题的锚点标签,我们将使用以下选择器同时选择它们的父元素:$('div > p.title &g;t; a')

为了单独获取每个标题(而非一堆毫无意义的字符),我们需要使用 each() 函数遍历每篇帖子。最后,对每个项调用 text() 方法将返回该特定帖子的标题。

要运行该脚本,只需在终端中输入 `node index.js` 并按回车键。此时应会看到一个包含所有文章标题的数组。

Node.js 中的 DOM

由于 Node.js 无法直接访问网页的 DOM,我们可以使用 JSDOM。根据其文档说明,JSDOM 是针对 Node.js 环境实现的纯 JavaScript 版本,涵盖了众多 Web 标准,尤其是 WHATWG DOM 和 HTML 标准。

换言之,借助 JSDOM,我们可以创建 DOM 并使用与操作网页浏览器 DOM 相同的方法对其进行操作。

JSDOM 使您能够与需要爬取的网站进行交互。如果您熟悉操作网页浏览器 DOM,那么理解 JSDOM 的功能将不会太费力。

为了更好地理解 JSDOM 的工作原理,让我们先安装它,创建一个新的 index.js 文件,并输入或复制以下代码:

const { JSDOM } = require('jsdom')
const { document } = new JSDOM(
  '<h1 class="string">Dunder mifflin, the people person\'s paper people!</h2>'
).window
const string = document.querySelector('.string')
console.log(string.innerHTML)
string.textContent = 'Hello world'
console.log(string.innerHTML)

如您所见,JSDOM 创建了一个新的文档对象模型(DOM),我们可以使用与操作浏览器 DOM 相同的方法对其进行操作。在第 3 行,DOM 中创建了一个新的 h1 元素。利用标题的 class 属性,我们在第 7 行选中该元素,并在第 10 行更改其内容。您可以通过打印更改前后的 DOM 元素来观察差异。

要运行该程序,请打开一个新的终端窗口,输入 `node index.js`,然后按回车键。

当然,使用 JSDOM 还可以执行更复杂的操作,例如打开网页并与之交互、填写表单以及点击按钮。

就个人看法而言,JSDOM 是一个不错的选择,但 Puppeteer 在过去几年中获得了广泛关注。

了解 Puppeteer:如何解析 JavaScript 页面

使用 Puppeteer,你可以完成在网页浏览器中手动能做的大部分操作,例如填写表单、生成页面截图或自动化 UI 测试。

让我们通过截取 Reddit 社区 /r/dundermifflin 的屏幕截图,来更好地理解其功能。如果您之前已安装了该依赖项,请继续执行下一步。如果尚未安装,请在项目文件夹中运行 npm i puppeteer。现在,创建一个新的 index.js 文件,并输入或复制以下代码:

const puppeteer = require('puppeteer')
async function takeScreenshot() {
	try {
		const URL = 'https://www.old.reddit.com/r/dundermifflin/'
		const browser = await puppeteer.launch()
		const page = await browser.newPage()
		await page.goto(URL)
		await page.pdf({ path: 'page.pdf' })
		await page.screenshot({ path: 'screenshot.png' })
		await browser.close()
	} catch (error) {
		console.error(error)
	}
}
takeScreenshot()

我们创建了 takeScreenshot() 异步函数。

如您所见,首先通过 puppeteer.launch() 命令启动了一个浏览器实例。随后我们创建了一个新页面,并通过将 URL 作为参数调用 goto() 函数,将该页面跳转至指定 URL。pdf()screenshot() 方法分别用于生成包含网页内容的 PDF 文件和图像。

最后,在第 13 行关闭了浏览器实例。要运行该程序,请在终端中输入 `node index.js` 并按回车键。您应该会在 projects 文件夹中看到两个新文件,分别名为 page.pdfscreenshot.png

Puppeteer 的替代方案

如果您不习惯使用 Puppeteer也可以选择 NightwatchJS、NightmareJS 或 CasperJS 等替代方案。

以 Nightmare 为例。由于它使用 Electron 而非 Chromium,因此打包体积稍小。可通过运行 `npm install nightmare` 命令安装 Nightmare。我们将尝试使用 Nightmare 代替 Puppeteer,复现之前成功截取网页截图的流程。

创建一个新的 index.js 文件,并输入或复制以下代码:

const Nightmare = require('nightmare')
const nightmare = new Nightmare()
return nightmare.goto('https://www.old.reddit.com/r/dundermifflin')
	.screenshot('./nightmare-screenshot.png')
 	.end()
	.then(() => {
		console.log('Done!')
	})
	.catch((err) => {
		console.error(err)
	})

如您所见,在第 2 行我们创建了一个新的 Nightmare 实例,在第 4 行将浏览器指向要截图的网页,在第 5 行截取并保存截图,并在第 6 行结束 Nightmare 会话。

要运行它,请在终端中输入 `node index.js` 并按回车键。你应该会在项目文件夹中看到两个新文件:`nightmare-screenshot.png`

主要收获

如果你还在这里,恭喜!你已经掌握了构建自己的网页爬虫所需的所有信息。让我们花一点时间总结一下你到目前为止学到的内容:

  • 网络爬虫是一种软件工具,可帮助你自动化从第三方网站收集有用数据的繁琐过程。
  • 人们使用网页爬虫进行各种数据采集:市场分析、价格比较或潜在客户开发。
  • 类似于网页浏览器的 HTTP 客户端,可帮助你向服务器发送请求并接收响应。
  • JavaScript最初是为了帮助用户向网站添加动态内容而创建的。Node.js 是一个工具,它使 JavaScript 不仅能在客户端运行,还能在服务器端运行。
  • Cheerio 是一个开源库,通过解析 HTML 并提供用于操作解析结果数据的 API,帮助我们提取有用信息。
  • Puppeteer 是一个 Node.js 库,通过提供高级 API 来控制 Chrome 或 Chromium。借助它,您可以完成在网页浏览器中手动能做的大部分操作,例如填写表单、生成页面截图或实现自动化。
  • 仅通过查看网站的 URL,您就能了解其大量数据。
  • 开发者工具可帮助您交互式地探索网站的文档对象模型(DOM)。
  • 正则表达式可帮助您创建规则,从而查找和管理不同的字符串。
  • JSDOM 是一款工具,它创建了一个新的文档对象模型(DOM),您可以使用与操作浏览器 DOM 相同的方法来操作它。

希望本指南说明清晰,并能为您接下来的项目提供所需的所有信息。如果您仍觉得不想亲自动手,随时可以尝试使用 WebScrapingAPI。

感谢您阅读到最后!

关于作者
Robert Sfichi, 全栈开发工程师 @ WebScrapingAPI
Robert Sfichi全栈开发工程师

罗伯特·斯菲奇是 WebScrapingAPI 的团队成员,致力于产品开发,并协助构建可靠的解决方案,以支持该平台及其用户。

开始构建

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

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