Die meisten Anleitungen zu Proxys in Node.js empfehlen, https-proxy-agent, socks-proxy-agent und ein paar weitere Pakete hinzuzufügen. In Wirklichkeit enthält der integrierte HTTP-Client undici seit Version 18+ bereits ProxyAgent, der die meisten Aufgaben des Parsens und Testens von APIs über Proxys abdeckt. In diesem Artikel werden wir besprechen, wie man HTTP/HTTPS-Tunneling über undici einrichtet, IP-Rotation zwischen Anfragen organisiert und was zu tun ist, wenn SOCKS5 benötigt wird, ohne eine zusätzliche Abhängigkeit einfügen zu wollen.
Warum undici statt axios und request
undici ist ein HTTP/1.1 und HTTP/2 Client, der vom Node.js-Team entwickelt wurde und seit Version 18 die Grundlage für das globale fetch bildet. Es hat keine transitive Abhängigkeiten, ist schneller als axios in den meisten Benchmarks zum Connection Pooling und was für unser Thema wichtig ist – es hat ein natives Modul für die Arbeit mit Proxys über HTTP CONNECT Tunneling.
Wenn Sie Wildberries, Ozon oder Werbekreative aus verschiedenen Regionen parsen oder testen, ist jedes zusätzliche Paket in node_modules ein potenzieller Ausfallpunkt bei der Aktualisierung von Node.js. undici wird mit der Laufzeit geliefert, sodass Sie eine aktuelle Implementierung von TLS, HTTP/2 und Keep-Alive erhalten, ohne das Risiko, dass eine veraltete Bibliothek in einer neuen Hauptversion nicht mehr funktioniert.
Die Installation ist denkbar einfach:
npm install undici
Wenn Sie Node.js 20+ verwenden, ist ein Teil der API global ohne Import verfügbar, aber für ProxyAgent ist der Import trotzdem erforderlich, da er standardmäßig nicht im globalen fetch exportiert wird.
Grundkonfiguration von ProxyAgent
Ein minimales funktionierendes Beispiel für eine Anfrage über einen Proxy sieht so aus:
import { ProxyAgent, fetch } from 'undici';
const proxyAgent = new ProxyAgent('http://proxy.example.com:8080');
const response = await fetch('https://httpbin.org/ip', {
dispatcher: proxyAgent,
});
const data = await response.json();
console.log(data);
Der Schlüsselpunkt ist der Parameter dispatcher. Dies ist nicht die Option agent, wie im alten Modul http, sondern der Dispatcher von undici, der die Verbindung noch vor dem TLS-Handshake abfängt und einen Tunnel über die CONNECT-Methode zum Zielhost herstellt. Für HTTPS-Anfragen über Proxys ist dies entscheidend: Ein normaler HTTP-Proxy sieht den Inhalt des verschlüsselten Traffics nicht, er leitet einfach die Bytes innerhalb des TCP-Tunnels weiter.
Wenn Sie nicht mit fetch, sondern mit dem Low-Level undici.request arbeiten, ist die Syntax ähnlich:
import { request, ProxyAgent } from 'undici';
const proxyAgent = new ProxyAgent('http://proxy.example.com:8080');
const { statusCode, body } = await request('https://api.example.com/data', {
dispatcher: proxyAgent,
});
console.log(statusCode);
for await (const chunk of body) {
console.log(chunk.toString());
}
Für Produktionsparser, die Hunderte von Anfragen pro Minute stellen, ist es wichtig, nicht für jede Anfrage einen neuen ProxyAgent zu erstellen – das killt das Connection Pooling. Erstellen Sie eine Agenteninstanz einmal für den Verbindungs-Pool und verwenden Sie sie zwischen den Aufrufen wieder.
Proxy-Authentifizierung mit Benutzername und Passwort
Die meisten kostenpflichtigen Proxy-Anbieter stellen den Zugang über ein Benutzername/Passwort-Paar und nicht über IP-Whitelisting bereit. In undici werden die Anmeldedaten direkt in die Proxy-URL übergeben:
import { ProxyAgent, fetch } from 'undici';
const login = 'user123';
const password = 'pass456';
const proxyUrl = `http://${login}:${password}@gate.proxycove.com:9000`;
const proxyAgent = new ProxyAgent(proxyUrl);
const response = await fetch('https://httpbin.org/ip', {
dispatcher: proxyAgent,
});
console.log(await response.json());
Beachten Sie: Wenn der Benutzername oder das Passwort Sonderzeichen (@, :, /) enthalten, müssen diese durch encodeURIComponent geleitet werden, da der URL-Parser innerhalb von undici die Verbindungszeichenfolge sonst nicht korrekt analysiert und Sie eine Authentifizierungsfehler 407 anstelle einer verständlichen Fehlermeldung erhalten.
Für Aufgaben, bei denen die Stabilität der Sitzung wichtig ist – zum Beispiel die Autorisierung auf einem Marktplatz, die an eine IP während der Sitzung gebunden ist – ist es bequemer, residential Proxys mit einer Sticky-Sitzung zu verwenden: Die IP ändert sich für eine bestimmte Zeit nicht, was bedeutet, dass der Dienst keine "Sprünge" zwischen den Regionen sieht und nicht bei jeder Anfrage eine erneute CAPTCHA-Anforderung benötigt.
Proxy-Rotation zwischen Anfragen
Wenn Sie eine Liste von N Proxys haben und die Aufgabe darin besteht, die Anfragen gleichmäßig zwischen ihnen zu verteilen, ist die einfachste Variante – Round-Robin ohne externe Bibliotheken für Warteschlangen:
import { ProxyAgent, fetch } from 'undici';
const proxyList = [
'http://user:[email protected]:8000',
'http://user:[email protected]:8000',
'http://user:[email protected]:8000',
];
let currentIndex = 0;
function getNextAgent() {
const proxyUrl = proxyList[currentIndex];
currentIndex = (currentIndex + 1) % proxyList.length;
return new ProxyAgent(proxyUrl);
}
async function fetchWithRotation(url) {
const agent = getNextAgent();
return fetch(url, { dispatcher: agent });
}
for (let i = 0; i < 10; i++) {
const res = await fetchWithRotation('https://httpbin.org/ip');
console.log(await res.json());
}
Dieser Ansatz funktioniert gut für das Parsen von Produktkarten oder die Überwachung von Preisen, wenn jede nachfolgende Anfrage logisch unabhängig von der vorherigen ist. Für Aufgaben, bei denen eine hohe Geschwindigkeit beim Wechsel der IP für jede Anfrage ohne manuelle Verwaltung der Liste erforderlich ist, ist es einfacher, Proxys mit automatischer Rotation auf der Anbieterseite zu verwenden – dann reduziert sich der gesamte obige Code auf eine statische URL des Gateways, und die Rotation übernimmt Datacenter-Proxys oder ein Residential-Pool mit zeitgesteuerter Rotation.
Wenn nicht einfach eine runde Rotation, sondern eine gewichtete erforderlich ist – zum Beispiel, um schnelleren Proxys Priorität einzuräumen – fügen Sie Gewicht in die Datenstruktur ein:
const weightedProxies = [
{ url: 'http://user:[email protected]:8000', weight: 5 },
{ url: 'http://user:[email protected]:8000', weight: 1 },
];
function pickWeighted(list) {
const total = list.reduce((sum, p) => sum + p.weight, 0);
let rnd = Math.random() * total;
for (const p of list) {
if (rnd < p.weight) return p.url;
rnd -= p.weight;
}
return list[0].url;
}
Überprüfung der Proxy-Verfügbarkeit und Ausschluss von "toten" IPs
Ein Proxy-Pool "verdirbt" mit der Zeit: Ein Teil der IPs wird von der Zielwebsite gesperrt, ein Teil hört auf zu antworten. Damit die Rotation keine Anfragen über tote Adressen sendet, fügen Sie eine Health-Check mit Timeout über den integrierten AbortController hinzu:
import { ProxyAgent, fetch } from 'undici';
async function isProxyAlive(proxyUrl, timeoutMs = 5000) {
const agent = new ProxyAgent(proxyUrl);
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch('https://httpbin.org/ip', {
dispatcher: agent,
signal: controller.signal,
});
return res.status === 200;
} catch {
return false;
} finally {
clearTimeout(timer);
}
}
async function filterAliveProxies(proxyList) {
const checks = await Promise.all(
proxyList.map(async (url) => ({ url, alive: await isProxyAlive(url) }))
);
return checks.filter((c) => c.alive).map((c) => c.url);
}
Führen Sie eine solche Überprüfung alle paar Minuten mit einem Hintergrundtimer durch und aktualisieren Sie den aktiven Pool, anstatt die gesamte statische Liste zu aktualisieren. Dies ist besonders wichtig, wenn Sie eigene Proxys auf günstigen Datacenter-IP-Adressen betreiben – dort ist der Ausfallprozentsatz höher als bei verwalteten Anbieterpools.
SOCKS5 ohne externe Bibliotheken
Wichtiger Hinweis: Der integrierte ProxyAgent in undici funktioniert über HTTP CONNECT-Tunneling und versteht "out of the box" nur HTTP/HTTPS-Proxys. Das SOCKS5-Protokoll funktioniert anders – es handelt sich um ein binäres Handshake auf TCP-Ebene, und undici analysiert es nicht nativ. Wenn Sie dringend SOCKS5 benötigen, ohne socks-proxy-agent zu installieren, können Sie einen minimalen Connector manuell über das net-Modul und die connect-Option bei einem benutzerdefinierten Agent implementieren:
import net from 'node:net';
import { Agent, fetch } from 'undici';
function socksHandshake(socket, targetHost, targetPort) {
return new Promise((resolve, reject) => {
// Schritt 1: Begrüßung, Methode ohne Authentifizierung
socket.write(Buffer.from([0x05, 0x01, 0x00]));
socket.once('data', (greeting) => {
if (greeting[1] !== 0x00) {
return reject(new Error('SOCKS5: Authentifizierungsmethode nicht unterstützt'));
}
const hostBuf = Buffer.from(targetHost, 'utf8');
const request = Buffer.concat([
Buffer.from([0x05, 0x01, 0x00, 0x03, hostBuf.length]),
hostBuf,
Buffer.from([targetPort >> 8, targetPort & 0xff]),
]);
socket.write(request);
socket.once('data', (reply) => {
if (reply[1] !== 0x00) {
return reject(new Error('SOCKS5: Verbindung abgelehnt'));
}
resolve(socket);
});
});
});
}
const socksAgent = new Agent({
connect: (options, callback) => {
const socket = net.connect(1080, 'socks-proxy.example.com', async () => {
try {
await socksHandshake(socket, options.hostname, 443);
callback(null, socket);
} catch (err) {
callback(err, null);
}
});
socket.on('error', (err) => callback(err, null));
},
});
const response = await fetch('https://httpbin.org/ip', { dispatcher: socksAgent });
console.log(await response.json());
Dies ist ein vereinfachtes Handshake ohne Authentifizierung mit Benutzername und Passwort (Methode 0x02) und ohne Unterstützung für IPv6 – für die Produktion sollten Sie einen Zweig mit Authentifizierung und die Verarbeitung verschiedener Adresstypen hinzufügen. Aber um die Mechanik zu verstehen – das reicht aus: SOCKS5 benötigt kein TLS innerhalb des Tunnels während des Handshakes, im Gegensatz zu HTTP CONNECT, weshalb der Code kürzer ist, als es auf den ersten Blick scheint.
In der Praxis wird SOCKS5 häufiger nicht zum Parsen von Webseiten verwendet, sondern zum Durchleiten von Traffic von mobilen Anwendungen und Anti-Detect-Browsern wie Dolphin Anty oder AdsPower, wo das Protokoll für die Arbeit auf Socket-Ebene und nicht für HTTP-Anfragen erforderlich ist. Wenn Ihre Aufgabe darin besteht, HTTP/HTTPS-Traffic in Node.js zu verarbeiten, ist der integrierte ProxyAgent normalerweise ausreichend, und es macht keinen Sinn, sich mit einem manuellen SOCKS5-Connector zu beschäftigen.
Fehlerbehandlung von Timeouts und Retry-Logik
Proxys trennen Verbindungen häufiger als direkte Verbindungen – das ist normal für Residential-Netzwerke, wo ein Knoten jederzeit offline gehen kann. Eine Retry-Wrapper mit exponentieller Verzögerung ist erforderlich:
import { ProxyAgent, fetch } from 'undici';
async function fetchWithRetry(url, proxyList, maxRetries = 3) {
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
const proxyUrl = proxyList[attempt % proxyList.length];
const agent = new ProxyAgent(proxyUrl);
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 8000);
try {
const res = await fetch(url, { dispatcher: agent, signal: controller.signal });
clearTimeout(timer);
if (res.status >= 500) throw new Error(`Serverfehler: ${res.status}`);
return res;
} catch (err) {
clearTimeout(timer);
lastError = err;
const delay = 500 * 2 ** attempt;
await new Promise((r) => setTimeout(r, delay));
}
}
throw new Error(`Alle Versuche erschöpft: ${lastError?.message}`);
}
Diese Wrapper löst gleichzeitig das Problem der Rotation bei Fehlern: Wenn der aktuelle Proxy ausfällt, wird der nächste Versuch automatisch an eine andere IP aus der Liste gesendet, anstatt weiterhin auf denselben toten Knoten zuzugreifen.
Vergleich von undici ProxyAgent, https-proxy-agent und socks-proxy-agent
| Werkzeug | Abhängigkeiten | Unterstützung für SOCKS5 | HTTP/2 | Geschwindigkeit |
|---|---|---|---|---|
| undici ProxyAgent | nein (in Node 18+ integriert) | nein, out of the box | ja | hoch |
| https-proxy-agent | 1-2 Pakete | nein | nein | mittel |
| socks-proxy-agent | 3-4 Pakete | ja | nein | mittel |
Für reines HTTP/HTTPS-Parsen mit minimalen Abhängigkeiten gewinnt undici ProxyAgent durch seine Integration und Kompatibilität mit HTTP/2. Für SOCKS5 in der Produktion ist es einfacher, einen bewährten socks-proxy-agent zu verwenden, als einen selbstgeschriebenen Handshake zu unterstützen – die manuelle Implementierung aus dem vorherigen Abschnitt ist gut für das Lernen und seltene Edge-Cases, aber nicht für stark belastete Produktionen mit Tausenden von Verbindungen pro Stunde.
Praktische Anwendungsfälle: Parsing und Geo-Testing
Preisüberwachung auf Marktplätzen. Ein Skript auf undici mit Proxy-Rotation und Retry-Logik aus den obigen Abschnitten eignet sich perfekt, um das Rate-Limit auf Wildberries und Ozon zu umgehen: Jede nachfolgende Anfrage für das Produkt wird von einer neuen IP gesendet, und der Health-Check schließt automatisch gesperrte Adressen aus dem Pool aus.
Testen von Werbekreativen nach Geo. Wenn Sie überprüfen müssen, wie Inhalte einem Benutzer aus einem bestimmten Land für Google Ads oder Facebook Ads bereitgestellt werden, reicht es aus, die Proxy-URL im ProxyAgent auf einen Knoten der gewünschten Region zu ändern – es ist kein Browser erforderlich, wenn die Überprüfung über die API oder die HTML-Antwort des Servers erfolgt.
Überprüfung der Verfügbarkeit von Inhalten in verschiedenen Ländern. Für Aufgaben, bei denen die Website je nach IP unterschiedliche Inhalte bereitstellt, erscheinen Residential-Verbindungen für den Zielserver nicht von einem normalen Benutzer zu unterscheiden, was das Risiko einer Blockierung im Vergleich zu Datacenter-IP-Bereichen verringert. Dieses Szenario ist besonders relevant für mobile Proxys, wenn der Zielservice den Netztyp überprüft und offensichtliche Datacenter-Subnetze blockiert.
Massenüberprüfung von API-Endpunkten. Bei Lasttests Ihres eigenen APIs über einen externen Proxy-Pool ist es wichtig, das Connection Pooling zu überwachen – undici verwendet standardmäßig TCP-Verbindungen innerhalb eines ProxyAgent, was die Zeit für TLS-Handshakes bei Hunderten aufeinanderfolgender Anfragen an einen Host erheblich spart.
Fazit
Der integrierte undici ProxyAgent deckt die meisten Aufgaben mit HTTP/HTTPS-Proxys in Node.js ab, ohne dass externe Pakete erforderlich sind: Authentifizierung über die URL, Tunneling über CONNECT, Kompatibilität mit HTTP/2 und Connection Pooling funktionieren sofort. Rotation und Health-Check von Proxys lassen sich leicht mit reinen Promises und Timern zusammenstellen, und SOCKS5 kann bei Bedarf manuell über einen benutzerdefinierten Connect-Connector implementiert werden, obwohl es für die Produktion sinnvoller ist, eine fertige Bibliothek speziell für SOCKS zu verwenden.
Wenn Ihr Skript auf undici für das Parsen von Marktplätzen oder die Preisüberwachung gedacht ist, sollten Sie auf residential Proxys achten – sie sind widerstandsfähiger gegenüber Anti-Bot-Systemen als klassische Datacenter-IP-Adressen und erfordern seltener eine zusätzliche CAPTCHA-Lösung bei massenhaften Anfragen an Wildberries, Ozon oder Avito.