Launch-level proxy#
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(proxy={
"server": "http://res.proxshift.com:9000",
"username": "USER-cc-us-sid-run1-ttl-30m",
"password": "PASS",
})
page = browser.new_page()
page.goto("https://api.ipify.org")
print(page.inner_text("body"))
browser.close()import { chromium } from "playwright";
const browser = await chromium.launch({
proxy: { server: "http://res.proxshift.com:9000", username: "USER-cc-us-sid-run1-ttl-30m", password: "PASS" },
});
const page = await browser.newPage();
await page.goto("https://api.ipify.org");
console.log(await page.innerText("body"));
await browser.close();A browser keeps connections alive, so use a sticky session id at launch: the whole browser then behaves like one visitor from one household for the lifetime you chose.
Per-context proxies#
To run several countries or sessions in one browser, launch with a placeholder proxy and give each context its own. Chromium needs the launch-level proxy for per-context proxies to be honoured.
browser = p.chromium.launch(proxy={"server": "http://per-context"})
de = browser.new_context(proxy={"server": "http://res.proxshift.com:9000", "username": "USER-cc-de-city-berlin-sid-de1-ttl-30m", "password": "PASS"})
us = browser.new_context(proxy={"server": "http://res.proxshift.com:9000", "username": "USER-cc-us-state-tx-sid-us1-ttl-30m", "password": "PASS"})const browser = await chromium.launch({ proxy: { server: "http://per-context" } });
const de = await browser.newContext({ proxy: { server: "http://res.proxshift.com:9000", username: "USER-cc-de-city-berlin-sid-de1-ttl-30m", password: "PASS" } });
const us = await browser.newContext({ proxy: { server: "http://res.proxshift.com:9000", username: "USER-cc-us-state-tx-sid-us1-ttl-30m", password: "PASS" } });Match the context to the exit#
Give each context the locale and time zone of its country, so IP, language and clock agree: locale=\"de-DE\", timezone_id=\"Europe/Berlin\" for the Berlin context. Mismatches are a stronger bot signal than the IP itself.
Save gigabytes: block what you do not need#
def block(route):
if route.request.resource_type in {"image", "media", "font", "stylesheet"}:
return route.abort()
return route.continue_()
page.route("**/*", block)await page.route("**/*", (route) => {
const t = route.request().resourceType();
return ["image", "media", "font", "stylesheet"].includes(t) ? route.abort() : route.continue();
});A rendered page can weigh 3 to 5 MB; its HTML and API calls a few hundred kilobytes. On a per-GB network this single rule cuts the bill by 80 to 90 %.
Verify and troubleshoot#
- Open an IP-echo service first and check the country; then look at a geolocation lookup for the city.
- A
net::ERR_TUNNEL_CONNECTION_FAILEDorERR_PROXY_CONNECTION_FAILEDat launch means credentials or a parameter are wrong; test the same URL with cURL to read the gateway code. - WebRTC is disabled by default in Playwright contexts only if you block it; add
--disable-webrtcstyle hardening or block STUN when privacy matters.
Questions people ask#
Can each tab have a different proxy?
Each context can; tabs (pages) inside a context share its proxy. Create one context per session or country.
Does Playwright support SOCKS5 proxies with authentication?
Chromium does not accept SOCKS5 credentials; use the HTTP proxy on port 9000 with username and password, or whitelist your server IP and use socks5://res.proxshift.com:9001 without credentials.