Eine einzelne Anfrage durch einen Proxy in Node zu schicken ist ein Zehn-Zeilen-Problem. Jedes Tutorial endet dort, und dann wächst das Projekt: ein zweiter HTTP-Client taucht auf, Zugangsdaten werden in drei Dateien fest codiert, jemand braucht Deutschland statt der Vereinigten Staaten, und niemand kann beantworten, ob die Anfragen tatsächlich von dort abgehen, wo sie sollen.
Dies ist eher ein Einrichtungsleitfaden als ein Syntaxleitfaden. Wenn Sie die genaue Agent-Konfiguration brauchen, die jeder Client erwartet, einschließlich des Grunds, warum Axios proxy: false neben seinem Agent benötigt, wird das ausführlich behandelt in using residential proxies in Node.js with Axios and Got. Im Folgenden geht es darum, wie man ein Projekt so aufbaut, dass die Verdrahtung korrekt bleibt, während das Projekt wächst.
Entscheiden Sie, welche Clients Sie tatsächlich unterstützen
Node hat drei gebräuchliche HTTP-Clients, und sie nehmen Proxys über unterschiedliche Mechanismen entgegen. Alle drei zu unterstützen ist in einer Codebasis jeden Alters normal, aber es sollte eine Entscheidung sein und kein Zufall.
Axios braucht einen expliziten Agent, weil seine eigene proxy-Option HTTPS-Tunneling nicht so handhabt, wie man es erwarten würde. Installieren Sie https-proxy-agent.
Got nimmt den Agent in einem agent.https-Slot statt als Option auf oberster Ebene.
Natives fetch läuft über undici, daher braucht es ProxyAgent als Dispatcher. Undici wird mit Node ausgeliefert, aber Sie wollen es als explizite Abhängigkeit haben, wenn Sie Dispatcher selbst konstruieren.
Die praktische Konsequenz ist, dass Ihre Abhängigkeitsliste davon bestimmt wird, welche Clients Sie behalten, sodass das Beibehalten zweier Clients bedeutet, zwei Proxy-Pfade zu pflegen. Die meisten Codebasen wären besser dran, sich auf einen zu standardisieren und die Nachzügler umzustellen, und diejenigen, die wirklich zwei brauchen, sollten zumindest wissen, warum.
Zugangsdaten gehören in die Umgebung, nicht in den Quellcode
Das Gateway nimmt das Targeting im Benutzernamen entgegen, was bedeutet, dass der Zugangsdaten-String und die Routing-Konfiguration derselbe String sind. Das ist praktisch, und es ist auch der Weg, wie Proxy-Zugangsdaten am Ende committet werden.
Halten Sie die Teile in der Konfiguration getrennt:
PROXY_HOST=p.shifter.ioPROXY_PORT=443PROXY_USER=customer-USERNAMEPROXY_PASS=your-passwordPROXY_COUNTRY=usSetzen Sie dann den Benutzernamen zur Laufzeit zusammen. Die Regel, die Ihnen später hilft, lautet: Keine Datei außerhalb Ihres Proxy-Moduls sollte eine wörtliche Proxy-URL enthalten. Sobald ein Zugangsdaten-String mit eingebettetem Targeting inline in einem Scraper auftaucht, wird er kopiert, und die Kopien driften auseinander.
Setzen Sie das Passwort nicht in eine URL, die Sie loggen. Proxy-URLs sind der häufigste Weg, wie Zugangsdaten in einen Log-Aggregator gelangen, weil die naheliegende Debug-Zeile die gesamte URL ist.
Ein Modul baut die Agents
Die strukturelle Entscheidung, die am meisten zählt, ist, genau eine Stelle zu haben, die Konfiguration in einen Agent verwandelt. Es ist ein kleines Modul und es verhindert eine große Klasse von Problemen.
import { HttpsProxyAgent } from "https-proxy-agent";
const { PROXY_HOST, PROXY_PORT, PROXY_USER, PROXY_PASS } = process.env;
export function proxyUrl({ country, session, ttl } = {}) { const parts = [PROXY_USER]; if (country) parts.push("country", country); if (session) parts.push("sid", session); if (session && ttl) parts.push("ttl", String(ttl)); const user = parts.join("-"); return `https://${user}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}`;}
export function agentFor(opts) { return new HttpsProxyAgent(proxyUrl(opts));}Zwei Details in diesem Snippet sind es wert, explizit genannt zu werden. Targeting-Flags werden aus benannten Optionen gebaut, statt an jeder Aufrufstelle Strings zusammenzufügen, was verhindert, dass ein Tippfehler in einem Flag-Namen zu einem 407 wird, der wie ein Zugangsdatenproblem aussieht. Und ttl wird nur hinzugefügt, wenn eine Session vorhanden ist, weil eine time to live nichts hat, was sie am Leben halten könnte, ohne einen Session-Bezeichner.
Aufrufer sehen dann nie eine URL:
const agent = agentFor({ country: "de", session: "job-141", ttl: 600 });
// axiosawait axios.get(url, { httpsAgent: agent, proxy: false });
// gotawait got(url, { agent: { https: agent } });Natives fetch ist dasjenige, das keinen Agent entgegennimmt, da undici stattdessen einen Dispatcher will:
import { ProxyAgent, fetch } from "undici";import { proxyUrl } from "./proxy.js";
const dispatcher = new ProxyAgent(proxyUrl({ country: "de" }));await fetch(url, { dispatcher });proxyUrl separat zu exportieren ist das, was es dem fetch-Pfad ermöglicht, sich die Konfiguration mit den anderen beiden zu teilen, ohne den String-Aufbau zu duplizieren.
Agents wiederverwenden und Sessions im Voraus entscheiden
Ein Agent hält einen Connection Pool. Einen pro Anfrage zu konstruieren wirft diesen Pool jedes Mal weg, was sich als Latenz zeigt, die Sie fälschlicherweise dem Netzwerk zuschreiben werden.
Bauen Sie Agents einmal pro verwendeter Konfiguration und cachen Sie sie. Wenn Sie durch fünf Länder rotieren, sind das fünf Agents, die für die Lebensdauer des Prozesses gehalten werden, nicht einer pro Anfrage.
Sessions sind die damit verbundene Entscheidung. Ohne einen Session-Bezeichner rotiert das Gateway, was Sie für unabhängige Anfragen wollen. Mit sid erhalten Sie eine sticky IP, gehalten für die von Ihnen angegebene ttl, was Sie für alles mit Kontinuität wollen: einen paginierten Ergebnissatz, einen mehrstufigen Ablauf, alles, wo die zweite Anfrage vom selben Ort kommen muss wie die erste. Die Standard-Sticky-Zeit beträgt 120 Sekunden, wenn Sie keine festlegen.
Dies bei der Einrichtung zu wählen statt pro Aufrufstelle ist der Unterschied zwischen einer kohärenten Rotationsrichtlinie und einer Codebasis, in der zufällig die Hälfte der Anfragen sticky ist. Die Kompromisse werden dargelegt in sticky vs rotating residential proxies.
Verifizieren, bevor Sie darauf aufbauen
Schreiben Sie das Verifizierungsskript, bevor Sie den Scraper schreiben. Es dauert fünf Minuten und verwandelt eine ganze Kategorie zukünftiger Verwirrung in eine sofortige Antwort.
import { agentFor } from "./proxy.js";import axios from "axios";
const agent = agentFor({ country: "de" });const { data } = await axios.get("https://api.ipify.org?format=json", { httpsAgent: agent, proxy: false,});console.log(data);Wenn das Ihre eigene Adresse ausgibt, ist der Agent nicht angehängt, und jede Anfrage im Projekt geht direkt hinaus. Das ist ein wirklich häufiger Zustand, der tagelang bestehen kann, weil nichts fehlschlägt: der Code funktioniert, er nutzt nur den Proxy nicht. Zu prüfen, ob das Land tatsächlich dem entspricht, was Sie angefordert haben, ist die zweite Hälfte der Prüfung, und die Methode dafür ist ordnungsgemäß beschrieben in testing proxy speed, success rate and location accuracy.
Machen Sie daraus ein Skript in package.json, damit jeder es ausführen kann, wenn etwas seltsam aussieht.
Fehler einmal zuordnen
Proxy-Fehler sind spezifisch genug, um automatisch diagnostiziert zu werden, und das an einer Stelle zu tun ist besser, als sie wiederholt um drei Uhr morgens zu interpretieren:
- 407 bedeutet, dass die Zugangsdaten falsch sind oder ein Targeting-Flag fehlerhaft ist. Ein falsch geschriebenes Flag landet hier, weshalb es sich lohnt, Ihre Flag-Namen vor Ihrem Passwort zu prüfen.
- 502 bedeutet, dass keine Exits Ihrem Filter entsprechen. Der Filter ist zu eng, statt dass das Netzwerk ausgefallen wäre.
- 509 bedeutet, dass das Bandbreitenkontingent aufgebraucht ist.
- Connection refused bedeutet meist einen veralteten Host oder Port, der von einer älteren Konfiguration übrig geblieben ist.
Nur vorübergehende Fehler verdienen einen erneuten Versuch. Einen 407 in einer Schleife erneut zu versuchen verbrennt Ihr Rate Limit an einem Problem, das sich nie von selbst lösen wird, und einen 509 erneut zu versuchen bewirkt gar nichts. Wickeln Sie erneute Versuche in echten Backoff ein statt in eine feste Verzögerung, wie beschrieben in rate limiting and request throttling.
Nebenläufigkeit bewusst begrenzen
Node startet bereitwillig zehntausend Anfragen, und keiner der drei Clients wird Sie davon abhalten. Unter einem Proxy ist das schlimmer als sonst, weil jede dieser Anfragen ein Tunnel ist, der aufgebaut werden muss.
Nutzen Sie von Anfang an einen Concurrency-Limiter, statt erst nach dem ersten Vorfall einen hinzuzufügen. Ein moderates Limit mit gleichmäßigem Durchsatz übertrifft einen unbegrenzten Ausbruch, der Abwehrmaßnahmen auslöst und dann seine Zeit mit erneuten Versuchen verbringt.
Dev, CI und Produktion unterscheiden sich
Drei praktische Hinweise, die in jedem Node-Projekt auftauchen, sobald Proxys darin sind.
Residential-Traffic wird nach Bandbreite abgerechnet, sodass eine Testsuite, die echte Ziele über den Proxy anspricht, eine wiederkehrende Rechnung ohne Nutzen ist. Mocken Sie die HTTP-Schicht in Unit-Tests und behalten Sie eine kleine Anzahl echter Anfragen in einer separaten, manuell ausgelösten Prüfung.
Die lokale Entwicklung sollte dasselbe Modul und dieselben Umgebungsvariablen wie die Produktion verwenden, mit unterschiedlichen Werten. Konfiguration, die nur in der Produktion existiert, ist Konfiguration, die niemand getestet hat.
Und Container-Images sollten keine Zugangsdaten einbacken. Übergeben Sie sie zur Laufzeit, genau wie jedes andere Geheimnis.
FAQ
Brauche ich https-proxy-agent, wenn ich nur fetch verwende?
Nein. Undicis ProxyAgent deckt diesen Pfad ab. Sie brauchen das separate Agent-Paket für Axios und Got.
Warum braucht Axios proxy: false, wenn ich ihm bereits einen Agent gegeben habe?
Weil Axios sonst versuchen würde, seine eigene Proxy-Verarbeitung zusätzlich zum Agent anzuwenden, und die beiden sich nicht vertragen. Es auf false zu setzen übergibt die Arbeit vollständig an den Agent.
Kann ich das Targeting pro Anfrage statt pro Agent festlegen?
Sie können, aber jede unterschiedliche Konfiguration ist ein eigener Agent, sodass es Sie den Connection Pool kostet, wenn Sie einen pro Anfrage bauen. Cachen Sie sie nach Konfigurationsschlüssel.
Sollte das Land ein Konfigurationswert oder ein Argument pro Aufruf sein?
In der Praxis beides. Ein Standard in der Konfiguration, überschreibbar pro Aufruf für die Aufgaben, die eine bestimmte Region brauchen. Was Sie vermeiden wollen ist, dass das Land als wörtlicher Wert innerhalb einzelner Scraper auftaucht.
Fazit
Die Proxy-Verdrahtung in Node ist klein und gut verstanden. Was darüber entscheidet, ob ein Projekt wartbar bleibt, ist die Anordnung darum herum: ein Modul, das Agents aus Konfiguration baut, Zugangsdaten, die in der Umgebung leben, Agents, die wiederverwendet statt neu gebaut werden, eine bewusst gewählte Session-Richtlinie, ein Verifizierungsskript, das existiert, bevor der Scraper existiert, und Fehlerbehandlung, die den Unterschied zwischen einem zu engen Filter und einem falschen Passwort kennt.
Richten Sie das einmal ein, und das Hinzufügen einer Client-Bibliothek oder einer neuen Region wird zu einer Konfigurationsänderung. Überspringen Sie es, und jede neue Anforderung wird zu einer Suche durch die Codebasis nach fest codierten Strings. Gateway-Details finden Sie auf der residential proxies page, mit Bandbreitentarifen auf der pricing page.