返回博客
指南
Mihnea-Octavian ManolacheLast updated on Mar 31, 20261 min read

如何在 Node Fetch 中使用代理并构建网络爬虫

如何在 Node Fetch 中使用代理并构建网络爬虫

首先,在构建网页爬虫时,代理无疑非常重要。其次,node-fetch 是最受欢迎的 JavaScript HTTP 客户端之一。因此,将二者结合起来创建网页爬虫是很有意义的。

正因如此,今天我们将探讨如何在 node-fetch 中使用代理。作为额外内容,我们还将基于这一架构构建一个网页爬虫。

读完本文后:

  • 您将深入理解代理在网页抓取中的工作原理
  • 您将学会如何将代理与 node-fetch 集成
  • 您的个人作品集将新增一个项目

什么是代理,为何要在网页抓取中使用它?

在计算机网络中,代理充当客户端与服务器之间的“中间件”。代理服务器的架构相当复杂,但从宏观层面来看,使用代理时发生的过程如下:

  • 您“接入”代理服务器,并指定您试图访问(抓取)的目标(服务器)
  • 代理服务器连接到目标服务器并获取结果(例如网站的 HTML 文件)
  • 随后,代理服务器将从目标服务器获取的响应数据回传给您

在此过程中,您的 IP 地址对目标服务器始终处于隐藏状态,因为您实际上从未直接连接到该服务器。这正是代理在网页抓取中如此重要的主要原因。它们“隐藏”了网页抓取工具的 IP 地址,从而避免被反机器人系统封锁。

为何选择 node-fetch 进行网页抓取?

如果 JavaScript 是您最喜欢的编程语言,您可以使用许多 HTTP 客户端来构建网络爬虫。在最流行的选项中,包括 axios、got 以及此处列出的其他几个。但 node-fetch 仍然是最常被下载的 npm 包之一,这背后是有原因的。

首先,它是首个在 Node.js 中实现 Fetch API 的包。随后,从 v17.5.0 开始,Node.js 团队正式集成了 Fetch API,因此它不再需要作为第三方依赖。但截至 Node.js 19.3.0 版本,fetch 仍被标记为实验性功能。因此,node-fetch 依然是更稳定的解决方案。

就数据抓取而言,node-fetch 是一个绝佳的工具,正如其名,它用于从各种来源获取资源。这或许正是数据抓取最基本的定义。

如何在 node-fetch 中使用代理?

简而言之,node-fetch 没有内置方法支持代理。因此,如果您想构建一个网络爬虫,但您的基础设施依赖于 node-fetch,那么您可能会面临泄露真实 IP 的风险。这意味着您可能会被反机器人软件封禁。

不过,好在有解决办法。其中一种由 Nathan Rajlich 提供,他开发了一个实现 http.Agent 接口的模块,名为 https-proxy-agent。该模块可通过 npm 安装。此外,将其与 node-fetch 结合使用非常简单:

import fetch from 'node-fetch';

import HttpsProxyAgent from "https-proxy-agent";

const fetch_proxy = async () => {

   	const proxy = new HttpsProxyAgent('http://1.255.134.136:3128');

   	const response = await fetch('https://httpbin.org/ip', { agent: proxy});

   	const data = await response.text();

   	console.log(data);

}

fetch_proxy()

为了进行本次测试,我使用了 Proxy Scrape 提供的免费代理。正如预期的那样,响应信息显示请求源自代理 IP,而非我的本地 IP:

"origin": "1.255.134.136"

另一种方案是使用 node-fetch-with-proxy。该包在 node-fetch 的基础上,将 proxy-agent 作为其自身的依赖项。要使其生效,您只需设置 ‘HTTP_PROXY’ 环境变量即可。该变量的值应为您的代理服务器 IP 地址和端口号。 随后,您可以使用标准的 node-fetch 语法进行请求,这些请求将自动被路由到代理服务器。以下是一个示例:

import fetch from "node-fetch-with-proxy";

fetch('http://httpbin.org/ip')

.then(res => res.json())

.then(json => console.log(json));

如何使用 node-fetch 通过代理构建网络爬虫?

值得一提的是,我们已经构建了一个网页爬虫。上面的代码示例完全实现了任何网页爬虫的功能,即从网站收集数据。然而,在实际应用中,网页爬虫会稍微复杂一些。例如,需要对原始数据进行处理,或者需要检查并解析收集到的头部信息或 Cookie。因此,让我们深入探讨一下,将我们的第一个示例改造成一个真正的爬虫。 先明确一下目标:

  • 我们应该能够返回原始 HTML
  • 我们应该能够将整个响应作为 JSON 对象返回
  • 应能根据特定选择器提取元素

假设您已经安装了 node-fetch 和 https-proxy-agent,我们还需要一个工具:HTML 解析器。我做网页抓取时总是首选 cheerio。因此,请确保在您的项目中安装它。言归正传,让我们开始吧:

#1:导入依赖项

首先,我们需要导入上述提到的包。我想这一部分无需多做解释:

import fetch from 'node-fetch';

import HttpsProxyAgent from "https-proxy-agent";

import * as cheerio from 'cheerio';

#2:抓取逻辑

我们需要爬虫能够执行三项操作:返回行级 HTML、返回完整响应,以及根据 CSS 选择器返回元素。其中一项我们之前已经部分实现了。但让我们将所有内容拆分为三个函数:

const raw_html = async (proxyServer, targetURL) => {

   const proxy = new HttpsProxyAgent(proxyServer);

   const response = await fetch(targetURL, { agent: proxy});

   const data = await response.text();

   return data;

}

const json_response = async (proxyServer, targetURL) => {

   const proxy = new HttpsProxyAgent(proxyServer);

   const response = await fetch(targetURL, { agent: proxy});

   const data = {

       url: response.url,

       status: response.status,

       Headers: response.headers,

       body: await response.text()

   }

   return data;

}

const select_css = async (proxyServer, targetURL, cssSelector) => {

   const proxy = new HttpsProxyAgent(proxyServer);

   const response = await fetch(targetURL, { agent: proxy});

   const html = await response.text();

   const $ = cheerio.load(html);

   return $(cssSelector).text();

}

#3:参数解析器

我们可以利用终端参数来区分爬虫中实现的这三个选项。 Node.js 中有专门用于解析终端参数的选项,但我倾向于保持简单。因此我们将使用 `process.argv`,它会生成一个参数数组。请注意,该数组的前两个元素是“node”和你的脚本名称。例如,如果运行 `node scraper.js raw_html`,参数数组将如下所示:

[

  '/usr/local/bin/node',

  'path_to_directory/scraper.js',

  'raw_html'

]

忽略前两个元素,我们将采用以下逻辑:

  • 第一个参数指定我们要运行的函数;
  • 第二个参数指向目标(我们要抓取的网站);
  • 第三个参数指向代理服务器;
  • 第四个参数则指向 CSS 选择器。

因此,运行我们的爬虫的命令应如下所示:

~ » node scraper.js raw_html https://webscrapingapi.com http://1.255.134.136:3128

这简单来说就是从 WebScrapingAPI 的首页提取原始 HTML,并使用 http://1.255.134.136 作为代理中间件。现在最后一步是编写这些参数的逻辑代码,以便我们的代码能够理解该运行命令:

const ACTION = process.argv[2]

const TARGET = process.argv[3]

const PROXY = process.argv[4]

const SELECTOR = process.argv[5]
switch (ACTION) {

   case 'raw_html':

console.log(await raw_html(PROXY, TARGET))

break

   case 'json_response':

console.log(await json_response(PROXY, TARGET))

break

   case 'select_css':

SELECTOR ? console.log(await select_css(PROXY, TARGET, SELECTOR)) : console.log('Please specify a CSS selector!')

break

   default:

conssole.log('Please choose between `raw_html`, `json_response` and `select_css`')

}

基本上就是这样。恭喜!您已成功使用 node-fetch 配合代理创建了一个功能齐全的网页抓取工具。现在,我挑战您为这个抓取工具添加更多功能,打造您自己的版本,并将其作为个人作品集中的亮点。

仅使用 node-fetch 配合代理可能不足以满足网页抓取需求

正如我常说的,隐蔽的爬网远不止于隐藏 IP 地址。实际上,在网页爬虫中使用代理服务器仅仅是抵御反机器人软件的一层防护。 另一层防护是通过[在请求中设置自定义头部](链接 https://trello.com/c/n8xZswSI/14-2-8-january-article-13-http-headers-with-axios)来更改用户代理。

以 Web Scraping API 为例,我们拥有一支专门研究定制规避技术的团队。其中一些技术甚至会修改无头浏览器的默认值,以避免被指纹识别。

此外,由于现代网站使用 JavaScript 动态渲染内容,像 node-fetch 这样的简单 HTTP 客户端可能不够用。您可能需要考虑使用真正的网页浏览器。Python 的 Selenium 或 Node 的 Puppeteer 就是您可以考虑的两个选项。

结论

在构建网络爬虫时,结合 node-fetch 使用代理是一个绝佳的起点。但需注意,二者并非“直接兼容”,你需要借助第三方解决方案来实现连接。所幸可选方案众多,且 JavaScript 社区总是乐于帮助新手。

不过,构建隐蔽的爬虫更为困难,你需要设计出更复杂的规避技术。但我习惯在一切事物中寻找机遇。何不将今天所学加以拓展呢?希望到最后,你能开发出基于 Node-fetch 和代理的最佳网页爬虫。一如既往,我的建议是:持续学习!

关于作者
Mihnea-Octavian Manolache, 全栈开发工程师 @ WebScrapingAPI
Mihnea-Octavian Manolache全栈开发工程师

Mihnea-Octavian Manolache 是 WebScrapingAPI 的全栈及 DevOps 工程师,负责开发产品功能并维护确保平台平稳运行的基础设施。

开始构建

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

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