Developer Tutorial10 min readPython 3.10+ & Playwright

How to Use Proxies in Python and Playwright

A hands-on engineering tutorial for routing HTTP, HTTPS, and SOCKS5 proxies in Python using requests, asynchronous httpx, and headless Playwright browser contexts—complete with authentication, session rotation, and bandwidth cost controls.

#1 Manually Curated Demo PickHTTP, HTTPS & SOCKS5

Featured Example Gateway: 2Captcha Proxy

Featured at #1 in our manually curated demonstration ranking, 2Captcha Proxy provides standard HTTP, HTTPS, and SOCKS5 gateway endpoints across 220+ countries with Country, Region, City, ZIP, and ASN targeting. It supports both Username / Password and IP Whitelisting authentication, per-request rotation or sticky sessions up to 120 minutes, and pay-as-you-go traffic starting at $3.99/GB (1 GB) down to $1.25/GB (10 TB).

Visit 2Captcha Proxy Docs ↗Read 2Captcha Proxy Review →Estimate Monthly Bandwidth Cost →

Step 1: Environment Setup & Proxy Credential Anatomy

Install requests (with SOCKS5 support via PySocks), httpx for async concurrency, and playwright for headless browser rendering:

pip install "requests[socks]" "httpx[socks]" playwright
playwright install chromium

Commercial proxy providers—including 2Captcha Proxy, Bright Data, Oxylabs, Decodo, SOAX, and Webshare—authenticate connections either by whitelisting your server's static egress IP or via Username / Password credentials embedded in the proxy URI.

When generating proxy strings in your provider dashboard (for example, the 2Captcha Proxy dashboard), geographic targeting (Country, Region, City, ZIP, ASN) and session behavior (per-request rotation vs. sticky session up to 120 minutes) are encoded directly in your generated proxy username or port assignment. Store your gateway parameters in environment variables so credentials are never hardcoded in source control:

export PROXY_HOST="gw.2captcha.com"      # Replace with the gateway host from your dashboard
export PROXY_PORT="8080"                 # HTTP/HTTPS or SOCKS5 port from your dashboard
export PROXY_USER="your_proxy_username"  # Includes geo/session flags configured in dashboard
export PROXY_PASS="your_proxy_password"

Step 2: Using HTTP/HTTPS and SOCKS5 Proxies with Python Requests

For synchronous REST API polling or lightweight HTML fetching, Python's requests.Session pools TCP connections and applies proxy credentials across both http:// and https:// schemes:

import os
from urllib.parse import quote_plus
import requests

def build_proxy_url(scheme: str = "http") -> str:
    host = os.environ["PROXY_HOST"]
    port = os.environ["PROXY_PORT"]
    user = quote_plus(os.environ["PROXY_USER"])
    password = quote_plus(os.environ["PROXY_PASS"])
    return f"{scheme}://{user}:{password}@{host}:{port}"

def verify_egress_ip() -> None:
    # Use "http" for standard HTTP/CONNECT tunneling or "socks5h" for remote DNS SOCKS5
    proxy_url = build_proxy_url(scheme="http")
    proxies = {
        "http": proxy_url,
        "https": proxy_url,
    }

    with requests.Session() as session:
        session.proxies.update(proxies)
        response = session.get("https://httpbin.org/ip", timeout=15)
        response.raise_for_status()
        print("Egress IP response:", response.json())

if __name__ == "__main__":
    verify_egress_ip()

Tip on SOCKS5 DNS Resolution: When routing through a SOCKS5 endpoint in requests, use the socks5h:// scheme rather than socks5:// so that DNS lookups are resolved on the remote proxy gateway rather than leaking your local machine's DNS resolver.

Step 3: High-Concurrency Async Requests with HTTPX & Retry Rotation

When scraping hundreds of pages concurrently over a Residential proxy backconnect gateway, transient peer disconnects or HTTP 429 responses are normal. Using httpx.AsyncClient with bounded concurrency and automatic retry logic ensures high throughput:

import asyncio
import os
from urllib.parse import quote_plus
import httpx

def get_proxy_url() -> str:
    host = os.environ["PROXY_HOST"]
    port = os.environ["PROXY_PORT"]
    user = quote_plus(os.environ["PROXY_USER"])
    password = quote_plus(os.environ["PROXY_PASS"])
    return f"http://{user}:{password}@{host}:{port}"

async def fetch_with_retry(
    client: httpx.AsyncClient,
    url: str,
    semaphore: asyncio.Semaphore,
    max_attempts: int = 3,
) -> dict:
    async with semaphore:
        for attempt in range(1, max_attempts + 1):
            try:
                resp = await client.get(url, timeout=15.0)
                resp.raise_for_status()
                return {"url": url, "status": resp.status_code, "data": resp.json()}
            except (httpx.HTTPError, httpx.TimeoutException) as exc:
                if attempt == max_attempts:
                    return {"url": url, "status": None, "error": str(exc)}
                await asyncio.sleep(1.5 * attempt)

async def main() -> None:
    proxy_url = get_proxy_url()
    semaphore = asyncio.Semaphore(10)  # Cap concurrent connections
    targets = ["https://httpbin.org/ip" for _ in range(5)]

    async with httpx.AsyncClient(proxy=proxy_url, http2=True) as client:
        tasks = [fetch_with_retry(client, url, semaphore) for url in targets]
        results = await asyncio.gather(*tasks)
        for item in results:
            print(item)

if __name__ == "__main__":
    asyncio.run(main())

Step 4: Configuring Proxies in Playwright (Per-Context Routing & Bandwidth Saving)

When automating Chromium, Firefox, or WebKit with Playwright, you can configure a global proxy on browser = await p.chromium.launch(...) or assign isolated proxies to each BrowserContext. Assigning proxies at the new_context() level lets a single Chromium process run multiple isolated sessions simultaneously—each with its own sticky IP, cookies, and geographic locale.

Additionally, because Residential and Mobile proxies are metered per gigabyte (for example, $3.99/GB PAYG on 2Captcha Proxy or $8.40/GB PAYG on Bright Data), aborting non-essential images, media, and font requests via page.route() can reduce bandwidth usage by 60–80%:

import asyncio
import os
from playwright.async_api import async_playwright, Route

BLOCKED_RESOURCE_TYPES = {"image", "media", "font"}

async def block_heavy_assets(route: Route) -> None:
    if route.request.resource_type in BLOCKED_RESOURCE_TYPES:
        await route.abort()
    else:
        await route.continue_()

async def run_playwright_with_proxy() -> None:
    proxy_server = f"http://{os.environ['PROXY_HOST']}:{os.environ['PROXY_PORT']}"
    proxy_config = {
        "server": proxy_server,
        "username": os.environ["PROXY_USER"],
        "password": os.environ["PROXY_PASS"],
    }

    async with async_playwright() as p:
        # Launch browser once; pass proxy at the context level for multi-session isolation
        browser = await p.chromium.launch(headless=True)
        context = await browser.new_context(
            proxy=proxy_config,
            viewport={"width": 1366, "height": 768},
        )

        page = await context.new_page()
        # Strip images, video, and web fonts to save per-GB proxy bandwidth
        await page.route("**/*", block_heavy_assets)

        await page.goto("https://httpbin.org/ip", wait_until="domcontentloaded", timeout=30000)
        body_text = await page.inner_text("body")
        print("Playwright Context Egress IP:", body_text.strip())

        await context.close()
        await browser.close()

if __name__ == "__main__":
    asyncio.run(run_playwright_with_proxy())

Step 5: Rotating vs. Sticky Sessions & Troubleshooting Checklist

When deploying Python and Playwright scrapers in production, keep these operational rules in mind:

  • Sticky Sessions for Multi-Page Flows: Never use per-request IP rotation when logging into an account or paginating with session-bound cookies. Configure a sticky session identifier in your provider dashboard (up to 120 minutes on 2Captcha Proxy, up to 30 minutes on Oxylabs, or up to 24 hours on Decodo) and bind one Playwright BrowserContext per sticky session.
  • HTTP 407 Proxy Authentication Required: Indicates either invalid credentials, URL-encoding issues with special characters in your password (use urllib.parse.quote_plus), or an un-whitelisted egress IP.
  • Monitor Bandwidth Consumption: Track response byte sizes and compare your monthly traffic in our Proxy Bandwidth & Cost Calculator to see when stepping up to a higher volume package (such as 2Captcha Proxy's 100 GB tier at $2.25/GB or 1 TB tier at $1.80/GB) lowers your unit cost.

Related Guides & Provider Comparisons

How to Choose a Proxy Provider →Proxy Types Comparison →2Captcha Proxy Review →Proxy Cost Calculator →Provider Rankings →