Sie schreiben --proxy-server=http://user:pass@host:port in die Startargumente, so wie Sie einen Proxy für cURL angeben würden, und der erste page.goto() bricht mit net::ERR_NO_SUPPORTED_PROXIES ab. Sie nehmen die Zugangsdaten heraus, und der Fehler lautet nun net::ERR_INVALID_AUTH_CREDENTIALS. Puppeteer hat keine eigene Proxy-Option. Es startet Chrome, und Chrome entscheidet, wie ein Proxy verwendet wird; die meisten Antworten stecken deshalb in den Regeln von Chrome und nicht in der API von Puppeteer.
Dieser Beitrag behandelt das Start-Flag und page.authenticate(), einen Proxy pro Browser-Context, Sticky- und Rotating-Sitzungen, SOCKS5, die Bypass-Liste, die Prüfung der Ausgangs-IP, die typischen Fehler und ein vollständiges Skript mit Wiederholungen. Jedes Beispiel lief am 6. Oktober 2026 mit Puppeteer 25.12.0 und dem Chrome-154-Build, den es herunterlädt, gegen einen lokalen HTTP-Proxy mit Benutzername und Passwort sowie gegen einen lokalen SOCKS5-Server. Die zitierten Fehlermeldungen sind genau die, die wir erhalten haben.
Wie leitet Puppeteer den Datenverkehr über einen Proxy?
Puppeteer ist eine Node.js-Bibliothek, die einen echten Browser aus dem Code heraus steuert. Sie öffnet selbst keine Netzwerkverbindungen; das tut der Browser. Ein Proxy ist deshalb eine Chrome-Einstellung, die entweder als Kommandozeilen-Flag beim Browserstart oder als Option beim Anlegen eines Browser-Contexts übergeben wird. Ein Browser-Context ist eine isolierte Sitzung innerhalb eines Browsers mit eigenen Cookies, eigenem Cache und eigenem Speicher, ähnlich einem Inkognitofenster.
Die Anmeldung am Proxy ist ein eigener Schritt, weil Chrome keine Zugangsdaten aus der Proxy-Adresse liest. Unser Test-Proxy hat für eine HTTPS-Seite diesen Ablauf protokolliert:
- Chrome sendet
CONNECT httpbin.org:443und bittet den Proxy damit, einen Tunnel zur Website zu öffnen. Bei einerhttp://-Seite sendet es direkt die Anfrage selbst. - Die Anfrage enthält keine Zugangsdaten, also antwortet der Proxy mit
407 Proxy Authentication Required. RFC 9110 definiert 407 als Gegenstück zu 401 auf der Seite des Proxys. - Puppeteer fängt diese Aufforderung ab und antwortet mit dem Benutzernamen und dem Passwort, die
page.authenticate()übergeben wurden. - Chrome wiederholt die Anfrage mit einem
Proxy-Authorization-Header, der Proxy antwortet mit200, und die verschlüsselte Verbindung zur Website wird innerhalb des Tunnels aufgebaut. Der Proxy sieht den Hostnamen, nicht den Seiteninhalt. - Chrome legt die Zugangsdaten im Authentifizierungs-Cache des Contexts ab und sendet sie bei späteren Verbindungen sofort mit.
Wegen Schritt 5 gehören die Zugangsdaten am Ende zum Context, wie die Tests weiter unten zeigen.
Wie wird ein Proxy mit --proxy-server und page.authenticate() gesetzt?
npm i puppeteer installiert die Bibliothek und lädt ein passendes Chrome herunter; puppeteer-core ist dieselbe API ohne diesen Download. Die minimale Einrichtung:
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({
args: ["--proxy-server=http://pr.proxynet.io:8000"],
});
const page = await browser.newPage();
await page.authenticate({ username: "user", password: "pass" });
await page.goto("https://httpbin.org/ip");
console.log(await page.$eval("body", (el) => el.innerText)); // die Ausgangs-IP, die die Website sieht
await browser.close();Das http:// im Flag beschreibt die Verbindung zum Proxy, nicht die Seiten. Ein HTTP-Proxy transportiert auch https://-Websites durch den Tunnel, und ihr Inhalt bleibt zwischen Chrome und der Website verschlüsselt. Die Referenz zu page.authenticate() ergänzt, dass Puppeteer dafür im Hintergrund das Abfangen von Anfragen (Request Interception) einschaltet, was etwas Leistung kosten kann; mit null schalten Sie es wieder ab.
In unseren Tests gingen drei Dinge schief, und aus jedem folgt eine feste Regel:
- Rufen Sie
page.authenticate()vor dem erstengoto()auf. Erst danach aufgerufen, scheiterte die erste Navigation mitnet::ERR_INVALID_AUTH_CREDENTIALS; erst die nächste kam durch. - Lassen Sie die Zugangsdaten aus dem Flag heraus. Mit
user:pass@in der Adresse lehnte Chrome 154 den gesamten Wert mitnet::ERR_NO_SUPPORTED_PROXIESab. Ältere Anleitungen beschreiben an dieser Stelle einen407; heute erreicht die Anfrage den Proxy gar nicht. - Senden Sie
Proxy-Authorizationnicht selbst. Als wir den Header mitpage.setExtraHTTPHeaders()setzten, verweigerte Chrome die Navigation mitnet::ERR_INVALID_ARGUMENT.
Wert von --proxy-server | Ergebnis in Chrome 154 |
|---|---|
http://pr.proxynet.io:8000 | Funktioniert; HTTPS-Seiten laufen durch einen CONNECT-Tunnel |
pr.proxynet.io:8000 | Funktioniert; ohne Schema nimmt Chrome einen HTTP-Proxy an |
http://user:pass@pr.proxynet.io:8000 | net::ERR_NO_SUPPORTED_PROXIES |
socks5://host:port | Funktioniert nur, wenn der Server kein Passwort verlangt |
socks5h://host:port | net::ERR_NO_SUPPORTED_PROXIES; Chrome kennt dieses Schema nicht |
Wie bekommt jeder Browser-Context einen eigenen Proxy?
Puppeteer 22 hat createIncognitoBrowserContext(), den Namen aus vielen älteren Anleitungen, in browser.createBrowserContext() umbenannt. Dessen BrowserContextOptions enthalten zwei Proxy-Felder, proxyServer und proxyBypassList. Der Browser kann ohne Proxy starten, während jeder Context seinen eigenen bekommt:
import puppeteer from "puppeteer";
const PROXY = "http://pr.proxynet.io:8000";
// Eine Sticky-Sitzung pro Context: gleiche Sitzungs-ID -> gleiche Ausgangs-IP für bis zu 600 Sekunden
const sessions = ["a1b2c3", "d4e5f6"];
const browser = await puppeteer.launch(); // der Browser selbst startet ohne Proxy
for (const id of sessions) {
const context = await browser.createBrowserContext({ proxyServer: PROXY });
const page = await context.newPage();
await page.authenticate({ username: `user-session-${id}-ttl-600`, password: "pass" });
for (let i = 0; i < 2; i++) {
await page.goto("https://httpbin.org/ip");
console.log(id, await page.$eval("body", (el) => el.innerText));
}
await context.close(); // Cookies, Cache und Proxy-Einstellung verschwinden mit dem Context
}
await browser.close();Unser Test-Proxy vergibt die Ausgänge nach Sitzungs-ID, so wie es ein Gateway tut. Die beiden Contexts gingen über zwei verschiedene Adressen hinaus, und jeder behielt seine Adresse bei der zweiten Anfrage. Zwei weitere Ergebnisse sind für echte Aufgaben wichtig:
- Zugangsdaten gehören zum Context, nicht zur Seite. Eine zweite Seite im selben Context rief
page.authenticate()mit einem anderen Benutzernamen auf, und trotzdem trug jede ihrer Anfragen weiterhin den ersten Benutzernamen. Eine Seite, die die Methode nie aufgerufen hatte, kam mit denselben zwischengespeicherten Zugangsdaten durch. - Ein Context ohne
proxyServererbt den Proxy des Start-Flags, behält aber seine eigenen Zugangsdaten. Mit--proxy-serveram Browser gingen zwei Contexts mit eigenen Sitzungs-IDs trotzdem über zwei verschiedene Ausgänge hinaus.
Flag --proxy-server | createBrowserContext({ proxyServer }) | |
|---|---|---|
| Geltungsbereich | Der gesamte Browser | Nur dieser Context |
| Getrennte Ausgänge in einem Browser | Ja, ein Benutzername pro Context | Ja, sogar getrennte Proxy-Hosts |
| Proxy wechseln | Browser neu starten | Context schließen, einen neuen öffnen |
| Geeignet für | Ein Skript, ein Ausgang | Viele unabhängige Sitzungen parallel |
Die Arbeitsregel lautet: eine Sitzung, ein Context, ein Benutzername.
Rotating oder Sticky: Welcher Sitzungstyp passt zu einem Browser?
Ein Browser sendet nicht eine Anfrage pro Seite. Eine Produktseite lädt das HTML-Dokument, Skripte, Stylesheets, Bilder und API-Aufrufe im Hintergrund, oft von mehreren Hosts über mehrere Verbindungen. An einem rotierenden Gateway kann jede neue Verbindung eine neue Ausgangs-IP bekommen; in unserem Test ging sogar das Favicon einer Seite über eine andere Adresse hinaus als die Seite selbst. Für voneinander unabhängige Seiten ist das harmlos. Bei einem Login, einem Warenkorb oder einem mehrstufigen Formular sieht die Website dagegen einen Besucher, der zwischen Adressen springt, und beendet die Sitzung womöglich.
Das Gateway liest den Sitzungstyp aus dem Benutzernamen, den der Endpoint-Generator im Dashboard für Sie zusammensetzt. Seine Teile sind leicht zu lesen: -country-de wählt das Ausgangsland (die Stadt wählen Sie im selben Generator), -session-a1b2c3-ttl-600 hält für diese Sitzungs-ID einen Ausgang für die angegebene Zahl an Sekunden, irgendwo zwischen 1 und 60 Minuten, und ein Benutzername ohne Sitzungsteil rotiert.
Für unabhängige Seiten wie Kategorielisten oder Suchergebnisse, die in kurzlebigen Contexts geöffnet werden, eignet sich ein Rotierender Proxy.
Für alles, was einen Zustand hält, eignet sich ein Sticky-Proxy: ein Context, eine Sitzungs-ID, länger gehalten, als der Ablauf dauert. Wenn die Aufgabe endet, schließen Sie den Context, damit Cookies und IP gemeinsam enden.
Rotation verteilt unabhängige Arbeit auf verschiedene Ausgänge; sie ist kein Weg um die Limits einer Website herum. Ein 429 Too Many Requests oder 403 Forbidden fordert Sie auf, langsamer zu werden, und eine neue IP ändert an dieser Antwort nichts.
Funktioniert Puppeteer mit einem SOCKS5-Proxy?
Ja, aber ohne Benutzername und Passwort. Die Proxy-Dokumentation von Chromium hält fest, dass für SOCKSv5 kein Authentifizierungsverfahren unterstützt wird. Unser lokaler SOCKS5-Server hat das von der anderen Seite gesehen: Die Begrüßung von Chrome bot nur die Methode 0x00 an, „keine Authentifizierung“. Ein Server, der die Methode mit Benutzername und Passwort (0x02) verlangte, musste dieses Angebot ablehnen, und die Navigation scheiterte mit net::ERR_SOCKS_CONNECTION_FAILED. page.authenticate() änderte daran nichts, weil es bei SOCKS5 keine Aufforderung nach Art von 407 gibt, auf die Puppeteer antworten könnte.
Die Lösung besteht darin, sich über die IP-Adresse auszuweisen. Tragen Sie die öffentliche IP des Rechners, auf dem Chrome läuft, im Dashboard in die IP-Whitelist ein (bis zu 10 Adressen), nehmen Sie den SOCKS5-Port, den der Endpoint-Generator anzeigt, und starten Sie ohne Zugangsdaten:
import puppeteer from "puppeteer";
// Kein Benutzername, kein Passwort: Die öffentliche IP dieses Rechners muss auf der IP-Whitelist stehen.
// SOCKS5_PORT: der SOCKS5-Port, den der Endpoint-Generator anzeigt.
const { SOCKS5_PORT } = process.env;
const browser = await puppeteer.launch({
args: [`--proxy-server=socks5://pr.proxynet.io:${SOCKS5_PORT}`],
});
const page = await browser.newPage();
await page.goto("https://httpbin.org/ip");
console.log(await page.$eval("body", (el) => el.innerText));
await browser.close();Chrome schickte dem SOCKS5-Server Hostnamen, keine IP-Adressen. DNS wird also am Proxy aufgelöst, und die von cURL bekannte Gewohnheit socks5h:// ist hier nicht nötig. Für Webseiten ist ein HTTP-Proxy meist die einfachere Wahl, weil er mit page.authenticate() funktioniert.
Wie werden bestimmte Adressen vom Proxy ausgenommen?
Das Flag --proxy-bypass-list oder proxyBypassList als Array an einem Context listet Hosts auf, die Chrome direkt erreicht. Im Flag werden die Regeln durch Semikolons oder Kommas getrennt: --proxy-bypass-list=*.internal.example;192.168.0.0/16. In unserem Test umging ein gelisteter Host den Proxy vollständig und wurde auf dem lokalen Rechner aufgelöst.
Chrome hat außerdem implizite Regeln: localhost, *.localhost, 127.0.0.1/8 und [::1] nutzen nie den Proxy, auch bei leerer Liste. Wenn Sie gegen einen lokalen Server testen und das Proxy-Log stumm bleibt, liegt es daran. Die Sonderregel <-loopback> hebt diese impliziten Regeln auf; damit lief unsere Anfrage an 127.0.0.1 über den Proxy.
Wie wird die Ausgangs-IP geprüft?
Vergleichen Sie im selben Browser einen direkten Context mit einem Context, der über den Proxy läuft. Wenn sich die Adressen unterscheiden, geht der Datenverkehr über den Proxy:
import puppeteer from "puppeteer";
async function exitIp(context, credentials) {
const page = await context.newPage();
if (credentials) await page.authenticate(credentials);
await page.goto("https://httpbin.org/ip");
const { origin } = JSON.parse(await page.$eval("body", (el) => el.innerText));
await page.close();
return origin;
}
const browser = await puppeteer.launch();
const direct = await browser.createBrowserContext();
const proxied = await browser.createBrowserContext({ proxyServer: "http://pr.proxynet.io:8000" });
const own = await exitIp(direct);
const viaProxy = await exitIp(proxied, { username: "user", password: "pass" });
console.log({ own, viaProxy, proxyWorks: own !== viaProxy });
await browser.close();Wenn Sie ein bestimmtes Land ansteuern, schlagen Sie die Ausgangs-IP zusätzlich in einem Geolocation-Dienst nach. Schlägt die Prüfung fehl, nehmen Sie Chrome aus der Gleichung: Funktioniert mein Proxy? So testen Sie einen Proxy enthält die Kommandozeilentests, die ein Netzwerkproblem von einem Codeproblem unterscheiden.
Welche Fehler treten auf, und was bedeuten sie?
Jeden dieser Fehler haben wir im lokalen Testaufbau selbst erzeugt:
| Was Sie sehen | Was passiert ist | Was zu tun ist |
|---|---|---|
net::ERR_PROXY_CONNECTION_FAILED | Chrome konnte den Proxy nicht erreichen: falscher Host oder Port, oder ausgehender Datenverkehr wird blockiert | Adresse und Port prüfen; Wiederholen hilft nicht |
net::ERR_INVALID_AUTH_CREDENTIALS | Der Proxy verlangte eine Anmeldung, und Chrome hatte keine Zugangsdaten | page.authenticate() vor dem ersten goto() aufrufen |
goto() liefert Status 407 | Das Passwort war falsch; Puppeteer versuchte es einmal und gab auf | response.status() prüfen, Zugangsdaten korrigieren |
net::ERR_TUNNEL_CONNECTION_FAILED | Die Anmeldung klappte, aber der Proxy konnte den Tunnel zur Website nicht öffnen | Zieladresse prüfen |
net::ERR_NO_SUPPORTED_PROXIES | Chrome lehnte den Wert von --proxy-server ab | user:pass@ entfernen, http:// oder socks5:// verwenden |
net::ERR_SOCKS_CONNECTION_FAILED | Der SOCKS5-Server will ein Passwort, oder Ihre IP steht nicht auf der Whitelist | IP auf die Whitelist setzen oder den HTTP-Port nutzen |
| Seite lädt, aber die IP ist Ihre eigene | Eine Bypass-Regel oder ein Loopback-Host hat den Proxy umgangen | Bypass-Liste und <-loopback> prüfen |
Die Zeile mit dem falschen Passwort bleibt am längsten unentdeckt: Nichts wirft einen Fehler, und das Skript arbeitet mit der 407-Seite des Proxys weiter. In einem Desktop-Browser hat der Tunnelfehler mehr Ursachen, vom HTTPS-Scan eines Virenschutzprogramms bis zu einem veralteten System-Proxy; dazu mehr in err_tunnel_connection_failed in Chrome und Edge beheben. Während der Entwicklung zeigen zwei Listener den echten Fehler hinter einem Timeout:
page.on("requestfailed", (req) => console.log("FEHLGESCHLAGEN", req.url(), req.failure()?.errorText));
page.on("response", (res) => res.status() >= 400 && console.log(res.status(), res.url()));Vollständiges Beispiel: parallele Contexts, Sticky-Sitzungen und Wiederholungen
Das Skript öffnet sechs per JavaScript gerenderte Seiten mit höchstens drei Contexts gleichzeitig. Jeder Versuch bekommt einen frischen Context und eine frische Sticky-Sitzung, sodass Cookies und Ausgangs-IP gemeinsam wechseln. Bilder, Medien und Fonts werden blockiert, um Datenverkehr zu sparen; page.setRequestInterception() erledigt das, ohne page.authenticate() zu stören. Timeouts und Tunnelfehler werden mit exponentiellem Backoff wiederholt, während Konfigurationsfehler und ein 403 oder 429 der Website die Aufgabe beenden.
import puppeteer from "puppeteer";
const PROXY = "http://pr.proxynet.io:8000";
const USER = "user";
const PASS = "pass";
const URLS = Array.from({ length: 6 }, (_, i) => `https://quotes.toscrape.com/js/page/${i + 1}/`);
const CONCURRENCY = 3; // gleichzeitig offene Contexts
const ATTEMPTS = 3;
const BLOCKED = new Set(["image", "media", "font"]);
// Konfigurationsfehler: Warten und Wiederholen beheben sie nicht
const FATAL = ["ERR_PROXY_CONNECTION_FAILED", "ERR_NO_SUPPORTED_PROXIES", "ERR_INVALID_AUTH_CREDENTIALS"];
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function scrape(browser, url) {
for (let attempt = 1; attempt <= ATTEMPTS; attempt++) {
// Ein Context pro Versuch: eigene Cookies und eigene Sticky-Sitzung (eine Ausgangs-IP)
const context = await browser.createBrowserContext({ proxyServer: PROXY });
try {
const page = await context.newPage();
const session = crypto.randomUUID().slice(0, 8);
await page.authenticate({ username: `${USER}-session-${session}-ttl-300`, password: PASS });
await page.setRequestInterception(true);
page.on("request", (req) => (BLOCKED.has(req.resourceType()) ? req.abort() : req.continue()));
const res = await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30_000 });
const status = res ? res.status() : 0;
if (status === 403 || status === 429) {
// Die Website lehnt ab oder bremst Sie: Eine neue IP ist nicht die Antwort
throw Object.assign(new Error(`HTTP ${status}: anhalten und die Anfragerate senken`), { fatal: true });
}
if (status >= 400) throw new Error(`HTTP ${status}`);
await page.waitForSelector("div.quote span.text", { timeout: 10_000 }); // per JavaScript gerendert
return await page.$$eval("div.quote span.text", (els) => els.map((el) => el.textContent));
} catch (err) {
err.fatal ||= FATAL.some((code) => err.message.includes(code));
if (err.fatal || attempt === ATTEMPTS) throw err;
await sleep(2 ** attempt * 1000 + Math.random() * 1000); // vor dem nächsten Versuch warten
} finally {
await context.close();
}
}
}
const browser = await puppeteer.launch();
const queue = [...URLS];
const results = new Map();
await Promise.all(
Array.from({ length: CONCURRENCY }, async () => {
while (queue.length) {
const url = queue.shift();
try {
results.set(url, `${(await scrape(browser, url)).length} Zitate`);
} catch (err) {
results.set(url, `FEHLER ${err.message.split("\n")[0]}`);
if (err.fatal) queue.length = 0; // die ganze Aufgabe stoppen, nicht nur diese Seite
}
}
}),
);
await browser.close();
for (const url of URLS) console.log(url, results.get(url) ?? "übersprungen");Über den lokalen Test-Proxy kamen alle sechs Seiten in weniger als fünf Sekunden mit je zehn Zitaten zurück. Mit absichtlich falsch eingetipptem Proxy-Port stoppten die drei laufenden Seiten sofort mit net::ERR_PROXY_CONNECTION_FAILED, und die anderen drei wurden übersprungen; eine lokale Testseite, die mit 429 antwortete, beendete die Aufgabe auf dieselbe Weise. Beginnen Sie mit einem niedrigen Wert für CONCURRENCY: Jeder Context belegt Speicher, und auch die Zielseite hat Grenzen.
Einsatzbereiche
- Per JavaScript gerenderte Katalog- und Preisseiten, die ein einfacher HTTP-Client leer sieht.
- Prüfen, wie Ihre eigene Website, Ihre Preise oder Ihre Consent-Banner aus einem anderen Land aussehen, mit einem Context pro Land.
- Screenshots und PDFs öffentlicher Seiten, so wie ein Besucher in einem bestimmten Land sie sieht.
- Überwachung Ihrer eigenen Seiten und Checkout-Abläufe von außerhalb Ihres Netzwerks.
Für alle gilt derselbe Rahmen: Halten Sie sich an die robots.txt und die Nutzungsbedingungen der Website, nutzen Sie eine offizielle API, wo es eine gibt, und halten Sie die Anfragerate auf einem Niveau, das die Website tragen kann.
Häufige Fehler
user:pass@in--proxy-serverschreiben. Chrome lehnt den Wert mitnet::ERR_NO_SUPPORTED_PROXIESab.- Einen neuen Benutzernamen pro Seite erwarten. Chrome behält die ersten Zugangsdaten für den gesamten Context; nutzen Sie einen Context pro Sitzung.
- Ein Passwort für SOCKS5 versuchen. Chrome bietet nur „keine Authentifizierung“ an; nutzen Sie eine IP-Whitelist oder den HTTP-Port.
- Gegen
localhosttesten und dem Ergebnis trauen. Loopback-Adressen umgehen den Proxy, solange Sie nicht<-loopback>hinzufügen. - Eine
407-Antwort wie eine normale Seite behandeln. Ein falsches Passwort wirft keinen Fehler; prüfen Sieresponse.status(). - Nach einem
429oder403die IP rotieren. Werden Sie stattdessen langsamer; das Limit betrifft Ihr Verhalten, nicht Ihre Adresse. - Contexts offen lassen. Jeder belegt Speicher; schließen Sie sie in einem
finally-Block.
Entscheidungshilfe
| Bedarf | Empfehlung |
|---|---|
| Ein Skript, ein Ausgang | Flag --proxy-server plus page.authenticate() |
| Viele unabhängige Sitzungen in einem Browser | createBrowserContext({ proxyServer }), eine Sitzungs-ID pro Context |
| Viele Seiten, die nicht voneinander abhängen | Rotierender Benutzername, kurzlebige Contexts |
| Login, Warenkorb oder mehrstufiges Formular | Sticky-Sitzung, die länger hält als der Ablauf, ein Context |
| SOCKS5 ist Pflicht | IP-Whitelist, keine Zugangsdaten |
| Ein Werkzeug, das nur das Start-Flag annimmt | IP-Whitelist oder ein lokaler Forwarder wie proxy-chain |
| Interne Hosts müssen direkt erreichbar bleiben | --proxy-bypass-list oder proxyBypassList |
Häufige Fragen
Hat puppeteer.launch() eine Proxy-Option?
Nein. Der Proxy kommt als Chrome-Flag --proxy-server in args, die Anmeldung in page.authenticate(). Die eigene API von Puppeteer hat Proxy-Felder nur bei browser.createBrowserContext(): proxyServer und proxyBypassList.
Kann jede Seite ihren eigenen Proxy haben?
Nicht innerhalb eines Contexts. Proxy und zwischengespeicherte Zugangsdaten gehören zum Context, deshalb nutzte in unserem Test eine zweite Seite mit anderen Zugangsdaten trotzdem die ersten. Geben Sie jeder Seite, die einen eigenen Ausgang braucht, einen eigenen Context; ein Context ist weit günstiger als ein neuer Browser.
Was ist proxy-chain, und brauchen Sie es?
proxy-chain ist ein Open-Source-Paket für Node.js, dessen Funktion anonymizeProxy() einen lokalen Proxy ohne Passwort startet und den Datenverkehr an einen Upstream-Proxy mit Zugangsdaten weiterleitet, sodass Chrome eine einfache Adresse http://127.0.0.1:<port> bekommt. In unserem Test funktionierte das sowohl mit einem HTTP- als auch mit einem SOCKS5-Upstream, der ein Passwort verlangte. Mit page.authenticate() oder einer IP-Whitelist brauchen Sie es nicht.
Lässt sich der Proxy ohne Neustart des Browsers wechseln?
Ja, über Contexts. Schließen Sie den Context, öffnen Sie einen neuen mit einem anderen proxyServer oder einer anderen Sitzungs-ID, und rufen Sie page.authenticate() erneut auf. Nur das Flag --proxy-server ist für die gesamte Lebensdauer des Browsers festgelegt.
Warum zeigt das Proxy-Log Anfragen, die Sie nicht gestellt haben?
Der eigene Hintergrund-Traffic von Chrome, etwa Update-Prüfungen und Anfragen an Google-Dienste, geht an denselben Proxy. In unserem Log kam der Großteil davon ohne Zugangsdaten an und erhielt einen 407, weil page.authenticate() nur auf Aufforderungen aus dem Datenverkehr der Seiten antwortet. Ihre Seiten beeinflusst das nicht.
Verhindert ein Proxy CAPTCHAs in Puppeteer?
Nein. Ein Proxy ändert, woher der Datenverkehr kommt, während CAPTCHAs auch auf die Anfragerate, auf Browsersignale und auf widersprüchliche Sitzungen reagieren. Die Ursachen und die legitimen Wege, sie seltener zu machen, stehen in Puppeteer und CAPTCHA: warum, und wie seltener?.
Fazit
In Puppeteer ist der Proxy eine Chrome-Einstellung: das Flag --proxy-server für den gesamten Browser oder proxyServer für einen einzelnen Context, mit der Anmeldung über page.authenticate() vor der ersten Navigation. Zugangsdaten werden pro Context zwischengespeichert; bauen Sie deshalb jede Sitzung als eigenen Context, wählen Sie Sticky oder Rotating über den Benutzernamen und nutzen Sie für SOCKS5 eine IP-Whitelist. Prüfen Sie response.status() und halten Sie die Anfragerate höflich. Für Seiten, die sich über gewöhnliche Heimanschlüsse öffnen sollen, liefert ein Residential-Proxy die Ausgänge.




