说实话,从现在开始,互联网的数据量只会越来越大。对此我们确实无能为力。这就是网络爬虫派上用场的地方。
在接下来的文章中,我们将向您展示如何使用 JavaScript 作为主要编程语言来构建自己的 Web 爬虫。

说实话,从现在开始,互联网的数据量只会越来越大。对此我们确实无能为力。这就是网络爬虫派上用场的地方。
在接下来的文章中,我们将向您展示如何使用 JavaScript 作为主要编程语言来构建自己的 Web 爬虫。
网页爬虫是一种软件工具,它能帮助您自动化完成从第三方网站收集有用数据的繁琐过程。通常,这一过程包括向特定网页发送请求、读取 HTML 代码,并解析该代码以提取数据。
假设您想创建一个比价平台。您需要从几家网店获取若干商品的价格。借助网页抓取工具,您只需几分钟就能完成这项工作。
也许您正试图为公司获取新的潜在客户,甚至想找到最优惠的机票或酒店价格。在为撰写本文而进行网络爬取和调研时,我们偶然发现了 Brisk Voyage。
Brisk Voyage 是一款帮助用户寻找廉价、最后一刻周末旅行的网络应用。它利用某种网络爬虫技术,持续监测航班和酒店价格。当爬虫发现价格异常低廉的旅行方案时,用户会收到一封包含预订说明的电子邮件。
开发者使用网络爬虫进行各种数据采集,但最常见的应用场景包括:
你知道那些让你承认自己不是机器人的小复选框吗?唉,它们并不总能成功地挡住机器人。
但大多数时候它们确实有效,一旦搜索引擎发现你未经许可试图抓取其网站,就会限制你的访问权限。
网络爬虫面临的另一大障碍是网站结构的变更。网站结构的细微变化都可能让我们白白浪费大量时间。网络爬虫工具需要频繁更新才能适应变化并完成任务。
网络爬虫面临的另一项挑战是地理封锁。根据您的物理位置,如果请求来自不可信的地区,网站可能会完全禁止您的访问。
为应对这些挑战并助您专注于产品开发,我们推出了 WebScrapingAPI。这是一个易于使用、具备企业级扩展能力的 API,可帮助您收集和管理 HTML 数据。 我们对速度极度追求,采用全球轮换代理网络,目前已有超过 10,000 家客户正在使用我们的服务。如果您觉得没有时间从头开始构建网络爬虫,可以尝试使用 WebScrapingAPI 的免费套餐。
大多数网络应用程序都提供 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。
下面我们来谈谈该过程中最重要的几个标头字段:
服务器响应可能如下所示:
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 文件、图片等所有其他资源,并渲染出网页的最终版本。在接下来的步骤中,我们将尝试实现该过程的自动化。
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 - 请按照此处的安装指南进行操作。
安装完成后,您可以在终端窗口中运行 `node -v` 和 `npm -v` 来验证 Node.js 是否已安装。版本号应高于 v14.15.5。如果您在此过程中遇到问题,还有另一种安装 Node.js 的方法。
现在,让我们创建一个新的 NPM 项目。为该项目创建一个新文件夹,并运行 `npm init -y`。接下来,我们开始安装依赖项。
要安装 Cheerio,请在项目文件夹中运行 npm install cheerio。
要使用 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}$。该表达式可匹配电话号码,无论区号是否用括号包围,或数字之间是否用点分隔。
如您所见,正则表达式非常易于使用,只要花足够的时间掌握,它就能发挥巨大的作用。
在成功安装了前面介绍的所有依赖项,并使用开发者工具检查了 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,我们可以使用 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,你可以完成在网页浏览器中手动能做的大部分操作,例如填写表单、生成页面截图或自动化 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.pdf 和 screenshot.png。
如果您不习惯使用 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`。
如果你还在这里,恭喜!你已经掌握了构建自己的网页爬虫所需的所有信息。让我们花一点时间总结一下你到目前为止学到的内容:
希望本指南说明清晰,并能为您接下来的项目提供所需的所有信息。如果您仍觉得不想亲自动手,随时可以尝试使用 WebScrapingAPI。
感谢您阅读到最后!
