Node Fetch:HTTP 請求與代理集成的技術指南

HTTP 請求是現代 Web 開發中通信的基本單元。瀏覽器利用它們來渲染頁面,服務器通過它們進行通信以協調分佈式系統,而應用程序則依賴它們來調用 API 和外部服務。對於 Node.js 開發者而言,能夠通過編程方式發起這些請求是一項必備技能,它支撐著從數據採集到服務集成等所有環節。

由 WHATWG 制定的 Fetch API 已成為 JavaScript 環境中處理 HTTP 請求的現代方法。 這一最初僅適用於瀏覽器的規範,如今已成為 Node.js 的原生功能,在許多場景下消除了對第三方庫的需求。然而,當網絡請求必須經過代理服務器時,Fetch API 便面臨著特定的挑戰——這種需求常見於企業環境、繞過地理限制,或是構建網頁抓取管道時。

本指南探討了在 Node.js 中使用 Fetch API 處理 HTTP 請求的技術現狀,特別關注代理集成。它深入分析了 Fetch 在 Node.js 中的演變歷程,對比了現有的實現方法,併為每種方法提供了詳盡的代碼示例。 討論內容涵蓋基於環境的配置、自定義代理的實現、Undici 的 ProxyAgent 以及 global-agent 包,旨在幫助開發者掌握相關知識,從而針對具體用例選擇並實現合適的解決方案。

理解 Node.js 中的 Fetch API

Node.js 中 HTTP 請求的演變

Node.js 自最早的版本起就通過內置的 httphttps 模塊支持 HTTP 請求。這些模塊雖然功能強大且靈活,但依賴於基於回調的 API,在處理複雜的請求序列時,可能會導致代碼嵌套過深、難以閱讀。

Fetch API 標誌著一次範式轉變,它引入了一種基於 Promise 的機制,能夠優雅地處理異步操作。當發出請求時,Fetch 會返回一個 Promise,該 Promise 會在收到服務器響應時解析,從而使開發者能夠使用 async/await 語法,從而編寫出更簡潔、更易於維護的代碼。

Fetch 邁向 Node.js 的歷程始於 node-fetch 庫,這是一個輕量級的實現,將瀏覽器的 Fetch API 語法引入了服務器端 JavaScript。在 Node.js 添加原生支持之前,該庫就已經被廣泛採用。

Native Fetch 於 Node.js 18 版本中以實驗性功能的形式發佈,並從 Node.js 21 版本起成為穩定功能。 到 Node.js 22 和 24 版本時,其實現已顯著成熟,內置代理支持於 22.21.0 版本引入,並在 24.0.0 及後續版本中得到了進一步增強。

原生 Fetch 與 node-fetch 庫的對比

Node.js 18 及以上版本自帶一個內置的全局 fetch() 函數,該函數由 Undici 提供支持——Undici 是由 Node.js 項目開發的一款高性能 HTTP 客戶端庫。這種原生實現具有以下幾個優勢:

  • 零依賴——無需額外安裝
  • 性能——基於 Undici 的優化 HTTP 堆棧構建
  • 未來兼容性——與不斷演進的 WHATWG 規範保持一致

然而,該 node-fetch 該庫在特定場景下仍然具有實用價值:

  • 舊版 Node.js —— 在 Node.js 16 或更早版本上運行的項目
  • 特定特性——某些原生獲取行為存在差異的特殊情況
  • 遷移靈活性——現有代碼庫的漸進式遷移路徑

代理集成方面的關鍵區別在於,原生 Fetch 和 node-fetch 使用不同的代理接口。 node-fetch 支持 agent 與 Node.js http.Agent API 兼容的選項,而原生 Fetch(由 Undici 提供支持)則需要一個 dispatcher 選項,該選項需支持 Undici 的 Dispatcher 接口。

Fetch 如何處理請求和響應

fetch() 該函數接受一個 URL 和一個可選的 options 對象,並返回一個 Promise,該 Promise 解析後會返回一個 Response 對象。理解這一流程對於有效集成代理至關重要:

const response = await fetch('https://api.example.com/data');
const data = await response.json();

Fetch API 的主要特點:

基於 Promise 的解析——Promise 會在響應頭到達時立即解析,而非等到響應正文完全下載完畢。響應正文以 ReadableStream 的形式提供,從而能夠高效處理大型有效載荷。

響應正文方法——Response 對象提供了多種用於處理響應正文的方法: response.text() 用於處理純文本或 HTML, response.json() 針對 JSON 數據,以及 response.buffer() 用於二進制數據。

錯誤處理——與舊方法不同,Fetch 不會因 HTTP 錯誤狀態碼(如 404、500 等)而拒絕 Promise。只有網絡層級的故障——如 DNS 錯誤、連接被拒或請求被中止——才會觸發拒絕。開發者必須顯式檢查 response.okresponse.status.

代理集成:核心挑戰

為什麼需要代理服務器

代理服務器在客戶端應用程序和目標服務器之間充當中介。在 Node.js 應用程序中,代理可滿足以下幾種常見需求:

企業網絡合規性——企業環境通常要求所有出站流量必須通過企業代理服務器,以便進行安全監控、訪問控制和流量日誌記錄。部署在這些環境中的應用程序必須支持代理功能,才能正常運行。

地理訪問——API 和 Web 服務通常會根據請求源 IP 地址實施地理限制。通過特定地區的代理服務器轉發請求,即可訪問受地域限制的內容。

速率限制與IP信譽——從單個IP地址頻繁發送大量請求的網頁抓取和數據採集操作,往往會觸發速率限制或IP封鎖。將請求分散到一組代理IP地址中,可以降低這一風險。

隱私與匿名性——代理服務器可以隱藏源IP地址,為敏感操作提供額外的隱私保護。

為什麼 Fetch 本身不支持代理

一個關鍵的技術細節:無論是 node-fetch ,Node.js 原生的 Fetch 也不原生支持代理。這種設計選擇源於 Fetch API(負責處理請求/響應語義)與底層網絡層(負責處理連接建立和路由)之間的關注點分離。

對於 node-fetch,該庫使用了 Node.js 的 httphttps 模塊進行網絡傳輸。這些模塊支持可通過代理設置進行配置的自定義代理。但是, node-fetch 本身並不提供內置的代理配置——開發者必須提供自定義代理。

對於原生 Fetch 而言,情況則更為複雜。原生 Fetch 由 Undici 提供支持,而 Undici 使用的是 Dispatcher 接口而非傳統的 Agent 接口。雖然 Undici 提供了支持代理的分發器,但全局 fetch() 函數並不會自動應用它們。

這一架構決策意味著,無論開發人員使用 node-fetch 還是原生 Fetch。

解決方案 1:環境變量配置(Node.js 22.21 及以上版本)

NODE_USE_ENV_PROXY 功能

Node.js 22.21.0 和 24.0.0 為原生 fetch() 函數,通過 NODE_USE_ENV_PROXY 環境變量引入了對原生函數的內置代理支持。啟用後,Node.js 會解析標準的代理環境變量,並將 HTTP 和 HTTPS 請求通過指定的代理進行轉發。

對於在企業環境中工作的開發人員,或者在任何需要所有請求都採用一致代理配置的場景中,此功能大大簡化了開發流程。

配置步驟

啟用該功能——在 NODE_USE_ENV_PROXY=1 在您的環境中:

export NODE_USE_ENV_PROXY=1

設置代理環境變量——配置標準代理變量:

export HTTP_PROXY=http://proxy.company.com:8080
export HTTPS_PROXY=http://proxy.company.com:8080
export NO_PROXY=localhost,127.0.0.1,.company.com

運行應用程序——在配置生效的情況下執行 Node.js 腳本:

node app.js

或者,使用命令行參數 --use-env-proxy ,而無需顯式設置環境變量:

node --use-env-proxy app.js

支持的 Node.js 版本

功能 最低版本
fetch() 通過以下方式支持代理 NODE_USE_ENV_PROXY Node.js 22.21.0 或 24.0.0 及以上版本
http/https 請求代理支持 Node.js 22.21.0 或 24.5.0 及以上版本

侷限性與注意事項

環境變量方法雖然簡單,但也有其侷限性:

全局範圍——代理配置適用於所有請求,對於需要針對不同目標使用不同代理的應用程序而言,這可能並不合適。

不支持按請求進行精細配置——若不覆蓋全局配置,單個請求無法使用不同的代理。

代理身份驗證——基本身份驗證可包含在代理 URL 中(例如, http://user:pass@proxy.company.com:8080),但更復雜的身份驗證可能需要採用其他方法。

版本要求——此功能僅在較新的 Node.js 版本中提供,因此其在舊版環境中的使用受到限制。

代碼示例

// When NODE_USE_ENV_PROXY=1 and HTTP_PROXY/HTTPS_PROXY are set,
// all fetch() calls route through the configured proxy automatically

async function fetchData() {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
}

fetchData().catch(console.error);

方案 2:使用 node-fetch 的自定義代理

理解代理模式

對於使用 node-fetch 庫的項目,代理集成需要創建一個自定義代理,並通過 agent 選項進行傳遞。 https-proxy-agent 包提供了必要的代理實現。

這種方法可以對代理配置進行精細控制,既能為不同的請求配置不同的代理,又能與舊版本的 Node.js 保持兼容。

安裝

安裝所需的依賴項:

npm install node-fetch https-proxy-agent

對於支持原生 fetch 的 Node.js 18 及以上版本, node-fetch 若您更傾向於使用原生 Fetch,則該選項可選(儘管原生 Fetch 不支持 agent 選項)。

基本實現

import fetch from 'node-fetch';
import { HttpsProxyAgent } from 'https-proxy-agent';

// Create the proxy agent
const proxyAgent = new HttpsProxyAgent('http://proxy.example.com:8080');

// Use the agent in the fetch request
const response = await fetch('https://api.example.com/data', {
    agent: proxyAgent
});

const data = await response.json();
console.log(data);

代理身份驗證

當代理服務器需要身份驗證時,可以在代理 URL 中包含憑據:

const proxyAgent = new HttpsProxyAgent(
    'https://username:password@proxy.example.com:8080'
);

支持 SOCKS5 代理

https-proxy-agent 該包通過 socks-proxy-agent 軟件包:

npm install socks-proxy-agent
import { SocksProxyAgent } from 'socks-proxy-agent';

const proxyAgent = new SocksProxyAgent('socks5://user:pass@proxy.example.com:1080');

const response = await fetch('https://api.example.com/data', {
    agent: proxyAgent
});

會話複用

對於同一會話中涉及多個請求的網頁抓取場景,重複使用同一個用戶代理可在不同請求之間保持 IP 地址的一致性:

const proxyAgent = new HttpsProxyAgent('http://proxy.example.com:8080');

// First request
const response1 = await fetch('https://example.com/page1', { agent: proxyAgent });

// Second request - same proxy IP
const response2 = await fetch('https://example.com/page2', { agent: proxyAgent });

環境變量集成

對於生產環境部署,請將代理憑據存儲在環境變量中,而不是硬編碼:

import fetch from 'node-fetch';
import { HttpsProxyAgent } from 'https-proxy-agent';
import dotenv from 'dotenv';

dotenv.config();

const proxyAgent = new HttpsProxyAgent(process.env.HTTP_PROXY);

const response = await fetch('https://api.example.com/data', {
    agent: proxyAgent
});

解決方案 3:Undici ProxyAgent 用於原生 Fetch

Undici 調度器模型

Node.js 原生 Fetch 由 Undici 提供支持,後者使用 Dispatcher 接口來管理 HTTP 連接。若要將原生 Fetch 請求通過代理進行路由,開發人員必須從 Undici 導入 ProxyAgent ,並將其作為 dispatcher 選項傳遞該對象。

該方法相當於原生 Fetch 的自定義代理方法。它適用於支持原生 Fetch 的 Node.js 版本(18 及以上),並提供與 node-fetch 方法。

基本實現

import { ProxyAgent } from 'undici';

const proxyAgent = new ProxyAgent('http://proxy.example.com:8080');

const response = await fetch('https://api.example.com/data', {
    dispatcher: proxyAgent
});

const data = await response.json();
console.log(data);

身份驗證

代理身份驗證通過代理 URL 進行處理:

const proxyAgent = new ProxyAgent('http://user:pass@proxy.example.com:8080');

環境變量代理

Undici 提供 EnvHttpProxyAgent,該功能可從環境變量中讀取代理配置:

import { EnvHttpProxyAgent, setGlobalDispatcher } from 'undici';

// Set environment variables: HTTP_PROXY, HTTPS_PROXY, NO_PROXY
const proxyAgent = new EnvHttpProxyAgent();

// Apply to all fetch() calls globally
setGlobalDispatcher(proxyAgent);

// Now all fetch() calls use the proxy
const response = await fetch('https://api.example.com/data');

全局調度器配置

對於所有請求都應使用同一代理的應用場景,設置全局分發器是最有效的方法:

import { EnvHttpProxyAgent, setGlobalDispatcher } from 'undici';

if (process.env.HTTPS_PROXY || process.env.HTTP_PROXY) {
    setGlobalDispatcher(new EnvHttpProxyAgent());
}

// All fetch() calls now route through the proxy
async function fetchData() {
    const response = await fetch('https://api.example.com/data');
    return response.json();
}

重要兼容性說明

Node.js 版本——此方法需要 Node.js 18 及以上版本才能原生支持 Fetch。

導入方法——必須直接導入 Undici。全局 fetch() 函數不會自動暴露 Undici 的調度器選項。

ProxyAgent 與 HttpsProxyAgentProxyAgent Undici 中的該組件相當於 HttpsProxyAgent 來自 https-proxy-agent 包中的實現。它們不能互換使用。

解決方案 4:global-agent 包

概述

global-agent 該包提供了一種替代的代理配置方法,只需極少的代碼修改即可實現代理支持。其工作原理是對 Node.js 的全局 HTTP/HTTPS 代理進行猴子補丁操作,從而影響所有通過 http.request, https.request以及基於它們構建的庫所發出的所有請求。

安裝與配置

npm install global-agent
import 'global-agent/bootstrap';

// Set environment variables
process.env.GLOBAL_AGENT_HTTP_PROXY = 'http://proxy.example.com:8080';
process.env.GLOBAL_AGENT_HTTPS_PROXY = 'http://proxy.example.com:8080';
process.env.GLOBAL_AGENT_NO_PROXY = 'localhost,127.0.0.1';

與 node-fetch 配合使用

import 'global-agent/bootstrap';
import fetch from 'node-fetch';

// The global-agent bootstrap makes node-fetch use the proxy automatically
const response = await fetch('https://api.example.com/data');
const data = await response.json();

優點與侷限性

優點:

  • 只需進行最少的代碼修改
  • 支持多種 HTTP 庫
  • 基於環境變量的配置

限制:

  • 與按請求的代理相比,控制粒度較低
  • 可能無法與所有 HTTP 客戶端實現兼容
  • 與某些代理類型可能存在的兼容性問題

Node.js 應用程序的代理選擇

家庭代理與數據中心代理的對比

在將代理集成到 Node.js 應用程序時,所選代理的類型會顯著影響成功率,特別是在網絡爬蟲和數據採集操作中。

住宅代理——這些IP地址源自真實的住宅網絡,看起來像是來自合法的家庭互聯網連接。它們具有更高的可信度評分,且不太可能觸發目標平臺上的屏蔽機制。對於需要持續訪問受驗證碼保護或受到嚴格監控的網站的應用而言,住宅代理具有明顯的優勢。

數據中心代理——這些IP地址源自商業數據中心,更容易被識別,有時也會被封鎖。不過,它們提供更高的帶寬和更低的延遲,因此適用於那些對IP真實性要求不如性能高的應用場景。

IPFLY 的動態家庭代理可提供覆蓋 190 多個國家/地區的 9000 多萬個家庭 IP 地址。 其輪換功能使 Node.js 應用程序能夠將請求分散到不同的 IP 地址上,從而保持較低的“每 IP 請求數”比率,確保該數值始終低於檢測閾值。憑藉 0.6 秒的平均響應時間和 99.9% 的可用性,該基礎設施能夠支持高流量的自動化工作流,且不會引入延遲。

對於需要固定IP分配的應用——例如基於會話的工作流或經過身份驗證的API訪問——IPFLY的靜態住宅代理提供100%專屬、經互聯網服務提供商(ISP)註冊的住宅IP地址,這些地址在長期使用中保持穩定。 對 HTTP/HTTPS 和 SOCKS5 協議的支持,確保了與本指南中討論的所有代理集成方法的兼容性。

IPFLY 的數據中心代理具備 99.9% 的可用性,覆蓋全球主要地區,可為無需住宅 IP 地址的高性能應用提供所需的帶寬和可靠性。

代理協議的選擇

代理協議必須同時符合代理提供商的能力和 Node.js 應用程序的要求:

HTTP/HTTPS 代理——支持 HTTP 和 HTTPS 流量。HTTPS 代理可處理加密流量,對於安全的 API 通信至關重要。大多數代理集成方法默認都支持 HTTP/HTTPS 代理。

SOCKS5 代理——在更底層運行,處理任何基於 TCP 的流量,無論其應用協議如何。對於使用非 HTTP 協議的應用程序,SOCKS5 代理提供了更大的靈活性。

《Both》 https-proxy-agent 和 Undici 的 ProxyAgent 均可在適當配置下支持 SOCKS5 代理。

代理輪換策略

對於網絡爬蟲和數據採集應用而言,實現代理輪換對於保持持續訪問至關重要:

請求級輪詢——每個請求都從代理IP池中使用一個不同的代理IP。這種方法能最大限度地提高IP多樣性,但可能不適用於基於會話的工作流。

會話級輪換——在整個會話期間使用單一代理 IP,並在不同會話之間進行輪換。這種方法可確保需要穩定的 IP-賬戶配對的工作流保持一致性。

基於健康狀況的輪換——當代理 IP 表現不佳、延遲過高或發生阻塞時,系統會對其進行輪換。這種方法旨在優化可靠性和成功率。

對於實現輪詢的 Node.js 應用程序,可以將代理的創建封裝在一個函數中,該函數從代理 URL 池中進行選擇:

import { HttpsProxyAgent } from 'https-proxy-agent';

const proxyPool = [
    'http://proxy1.example.com:8080',
    'http://proxy2.example.com:8080',
    'http://proxy3.example.com:8080'
];

function getProxyAgent() {
    const proxy = proxyPool[Math.floor(Math.random() * proxyPool.length)];
    return new HttpsProxyAgent(proxy);
}

// Use in requests
const response = await fetch('https://api.example.com/data', {
    agent: getProxyAgent()
});

Node.js 代理集成最佳實踐

錯誤處理與重試邏輯

通過代理進行的網絡請求會引入額外的故障模式。對於生產環境中的應用程序而言,實現健壯的錯誤處理和重試邏輯至關重要:

async function fetchWithRetry(url, options, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
        try {
            const response = await fetch(url, options);
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
            }
            return response;
        } catch (error) {
            console.warn(`Attempt ${i + 1} failed:`, error.message);
            if (i === maxRetries - 1) throw error;
            // Exponential backoff
            await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
        }
    }
}

TLS 與證書驗證

企業代理服務器通常會使用自簽名證書,或者使用未包含在 Node.js 默認信任存儲庫中的證書頒發機構。常見錯誤: UNABLE_TO_VERIFY_LEAF_SIGNATURE.

解決方案 1 – 使用 NODE_EXTRA_CA_CERTS 環境變量將企業 CA 證書添加到 Node.js 的信任存儲中。

方案 2 – 將代理配置為使用自定義 CA 證書:

import { HttpsProxyAgent } from 'https-proxy-agent';
import fs from 'fs';

const ca = fs.readFileSync('/path/to/corporate-ca.pem');

const proxyAgent = new HttpsProxyAgent({
    host: 'proxy.company.com',
    port: 8080,
    ca: ca
});

重要提示:由於存在安全風險,強烈建議不要在生產環境中禁用證書驗證(rejectUnauthorized: false)在生產環境中存在安全風險,因此強烈建議不要禁用證書驗證。

連接池與性能

對於高併發應用,通過重用代理代理(而非為每個請求創建新的代理),可以藉助保持持久連接來提升性能:

// Create once, reuse many times
const proxyAgent = new HttpsProxyAgent('http://proxy.example.com:8080');

// Reuse for multiple requests
const results = await Promise.all([
    fetch(url1, { agent: proxyAgent }),
    fetch(url2, { agent: proxyAgent }),
    fetch(url3, { agent: proxyAgent })
]);

基於環境的配置

對於部署在不同環境(開發、預生產、生產)中的應用程序,基於環境的代理配置可簡化管理:

import { HttpsProxyAgent } from 'https-proxy-agent';

const proxyUrl = process.env.HTTP_PROXY || process.env.HTTPS_PROXY;

const getFetchOptions = () => {
    if (proxyUrl) {
        return { agent: new HttpsProxyAgent(proxyUrl) };
    }
    return {};
};

// Use in requests
const response = await fetch('https://api.example.com/data', getFetchOptions());
Node Fetch:HTTP 請求與代理集成的技術指南

Node.js 中的 Fetch API 為 HTTP 請求提供了一種基於 Promise 的現代化方法,但其代理集成需要進行顯式配置。合適的解決方案取決於 Node.js 版本、應用程序的具體要求以及可用的代理基礎設施。

對於在 Node.js 22.21 及以上或 24.0 及以上版本上運行的應用程序, NODE_USE_ENV_PROXY 環境變量提供了最簡單的配置方式,只需對代碼進行最少的修改,即可將所有請求通過指定的代理進行路由。這種方法非常適合企業環境,因為在該類環境中,所有請求都需要保持一致的代理配置。

對於需要精細控制的應用程序,採用基於 https-proxy-agentnode-fetch 的自定義代理方案,可實現按請求選擇代理、靈活的身份驗證,併兼容舊版 Node.js。該方案非常適合網頁抓取、數據採集以及需要將不同請求路由至不同代理的應用程序。

對於原生 Fetch 用戶來說,Undici 的 ProxyAgentEnvHttpProxyAgent 通過調度器接口提供了同等的功能。全局調度器配置既具備與環境變量方法相同的簡便性,又能在需要時保持按請求的精細控制。

global-agent 該包提供了一種折中方案,通過基於環境的配置(該配置適用於多種HTTP庫),僅需最少的代碼修改即可實現代理支持。

選擇合適的代理基礎設施同樣重要。住宅代理能提供更高的信任評分並降低被封鎖的風險,而數據中心代理則能提供更高的性能和更低的延遲。具體選擇取決於應用程序的具體要求以及目標平臺的敏感程度。

Node Fetch:HTTP 請求與代理集成的技術指南

對於需要可靠代理基礎設施來處理 HTTP 請求、網頁抓取或 API 集成的 Node.js 應用程序,IPFLY 提供了一系列專為性能、可靠性和兼容性而設計的專業代理解決方案:

  • 動態住宅代理——覆蓋190多個國家/地區的9000多萬個住宅IP地址,具備低延遲性能,可為Node.js應用程序提供高效的IP輪換功能。
  • 靜態住宅代理——專屬且持久的住宅IP地址,可確保訪問模式穩定,並支持基於會話的工作流程。
  • 數據中心代理——專為帶寬密集型應用設計的高性能代理基礎設施,可用性高達99.9%。

立即構建您的 Node.js 代理基礎設施。訪問 IPFLY 主頁,探索全系列代理解決方案,或立即註冊,即可立即使用專業代理功能,以滿足您的開發和數據採集需求。