All articles
playwrightweb scrapingresidential proxies

Setting Up Proxies with Playwright: Complete Tutorial 2026

JL
James Liu
Lead Engineer @ ProxyLabs
January 28, 2026
9 min read
Share

Setting Up Proxies with Playwright: Complete Tutorial 2026

Playwright is the best browser automation framework for web scraping in 2026. Combined with residential proxies, it gives you undetectable automation at scale.

This tutorial covers everything from basic proxy setup to advanced rotation strategies and anti-detection techniques.

Why Playwright + Proxies?

Playwright Advantages Over Selenium

  • Better anti-detection: Fewer browser fingerprints
  • Faster execution: Auto-waiting eliminates explicit sleeps
  • Network interception: Modify requests/responses on the fly
  • Modern browser support: Chromium, Firefox, WebKit
  • Easier async: Native async/await support

When You Need Proxies with Playwright

  • Scraping sites with aggressive bot detection
  • Accessing geo-restricted content
  • Managing multiple accounts
  • High-volume scraping operations
  • Ticketing, sneakers, or limited releases
  • Social media automation

Installation and Setup

Python Installation

pip install playwright
playwright install chromium

pip install playwright-stealth

JavaScript/TypeScript Installation

npm install playwright
npx playwright install chromium

Basic Proxy Configuration

Python: Simple Proxy Setup

from playwright.sync_api import sync_playwright

proxy_config = {
    "server": "http://gate.proxylabs.net:8080",
    "username": "your-username",
    "password": "your-password"
}

with sync_playwright() as p:
    browser = p.chromium.launch(
        headless=True,
        proxy=proxy_config
    )
    
    page = browser.new_page()
    
    page.goto('https://httpbin.org/ip')
    print(page.content())
    
    browser.close()

JavaScript: Simple Proxy Setup

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({
    headless: true,
    proxy: {
      server: 'http://gate.proxylabs.net:8080',
      username: 'your-username',
      password: 'your-password'
    }
  });

  const page = await browser.newPage();
  await page.goto('https://httpbin.org/ip');
  
  console.log(await page.content());
  
  await browser.close();
})();

ProxyLabs-Specific Configuration

ProxyLabs supports session control via username formatting.

Sticky Sessions (Same IP for Duration)

proxy_config = {
    "server": "http://gate.proxylabs.net:8080",
    "username": "your-username-session_time-30",
    "password": "your-password"
}

Format: username-session_time-{minutes}

Use cases:

  • Checkout flows (maintain IP through payment)
  • Queue systems (Ticketmaster, AXS)
  • Multi-page scraping workflows
  • Account creation and login

Country/City Targeting

uk_proxy = {
    "server": "http://gate.proxylabs.net:8080",
    "username": "your-username-country-GB",
    "password": "your-password"
}

london_proxy = {
    "server": "http://gate.proxylabs.net:8080",
    "username": "your-username-country-GB-city-London",
    "password": "your-password"
}

Format:

  • Country: username-country-{ISO_CODE}
  • City: username-country-{ISO_CODE}-city-{CITY_NAME}

Advanced Playwright Configuration

Full Anti-Detection Setup

from playwright.sync_api import sync_playwright
import random

def create_stealth_browser(proxy_config):
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=True,
            proxy=proxy_config,
            args=[
                '--disable-blink-features=AutomationControlled',
                '--disable-dev-shm-usage',
                '--disable-setuid-sandbox',
                '--no-sandbox'
            ]
        )
        
        context = browser.new_context(
            viewport={'width': 1920, 'height': 1080},
            user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
            locale='en-US',
            timezone_id='America/New_York',
            permissions=['geolocation'],
            geolocation={'latitude': 40.7128, 'longitude': -74.0060},
            color_scheme='light',
            device_scale_factor=1,
            has_touch=False,
            is_mobile=False,
            java_script_enabled=True
        )
        
        page = context.new_page()
        
        page.add_init_script("""
            Object.defineProperty(navigator, 'webdriver', {
                get: () => undefined
            });
            
            Object.defineProperty(navigator, 'plugins', {
                get: () => [1, 2, 3, 4, 5]
            });
            
            Object.defineProperty(navigator, 'languages', {
                get: () => ['en-US', 'en']
            });
            
            window.chrome = { runtime: {} };
        """)
        
        return browser, page

proxy_config = {
    "server": "http://gate.proxylabs.net:8080",
    "username": "user-session_time-20",
    "password": "your-password"
}

browser, page = create_stealth_browser(proxy_config)

page.goto('https://www.amazon.com')

browser.close()

What this does:

  • Disables automation flags
  • Sets realistic viewport (1920x1080)
  • Configures timezone and locale
  • Injects anti-detection scripts
  • Removes webdriver property
  • Adds fake plugin array

Human-Like Behavior Simulation

import random
import time

async def human_like_interaction(page):
    await page.mouse.move(
        random.randint(100, 800),
        random.randint(100, 600),
        steps=random.randint(5, 15)
    )
    
    await page.wait_for_timeout(random.randint(1000, 3000))
    
    await page.mouse.wheel(0, random.randint(100, 400))
    
    await page.wait_for_timeout(random.randint(500, 2000))

async def scrape_with_behavior(url, proxy_config):
    async with async_playwright() as p:
        browser = await p.chromium.launch(proxy=proxy_config)
        page = await browser.new_page()
        
        await page.goto(url, wait_until='networkidle')
        
        await human_like_interaction(page)
        
        content = await page.content()
        
        await browser.close()
        
        return content

Proxy Rotation Strategies

Strategy 1: One Proxy Per Browser Instance

from playwright.sync_api import sync_playwright
import concurrent.futures

proxies = [
    {"server": "http://gate.proxylabs.net:8080", "username": f"user-session-{i}", "password": "pass"}
    for i in range(10)
]

def scrape_with_proxy(url, proxy):
    with sync_playwright() as p:
        browser = p.chromium.launch(proxy=proxy)
        page = browser.new_page()
        page.goto(url)
        content = page.content()
        browser.close()
        return content

urls = ['https://example.com/page1', 'https://example.com/page2']

with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    results = list(executor.map(
        scrape_with_proxy,
        urls,
        [proxies[i % len(proxies)] for i in range(len(urls))]
    ))

Use case: Scraping different pages in parallel with different IPs.

Strategy 2: Rotating Proxy Per Request

For ProxyLabs, omit the session parameter to get rotating IPs:

rotating_proxy = {
    "server": "http://gate.proxylabs.net:8080",
    "username": "your-username",
    "password": "your-password"
}

with sync_playwright() as p:
    browser = p.chromium.launch(proxy=rotating_proxy)
    
    for url in urls:
        page = browser.new_page()
        page.goto(url)
        print(f"Scraped {url} with rotating IP")
        page.close()
    
    browser.close()

Use case: Each new page context gets a new IP automatically.

Strategy 3: Sticky Session with Periodic Rotation

import time

def scrape_with_session_rotation(urls, session_duration=10):
    session_id = 0
    session_start = time.time()
    
    with sync_playwright() as p:
        for i, url in enumerate(urls):
            if time.time() - session_start > session_duration * 60:
                session_id += 1
                session_start = time.time()
                print(f"Rotating to new session: {session_id}")
            
            proxy = {
                "server": "http://gate.proxylabs.net:8080",
                "username": f"user-session-{session_id}",
                "password": "your-password"
            }
            
            browser = p.chromium.launch(proxy=proxy)
            page = browser.new_page()
            page.goto(url)
            
            page.close()
            browser.close()
            
            time.sleep(random.uniform(2, 5))

Use case: Maintain same IP for 10 minutes, then rotate. Balances continuity with IP freshness.

Error Handling and Retry Logic

Robust Proxy Error Handling

from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeout
import time

def scrape_with_retry(url, proxy_config, max_retries=3):
    for attempt in range(max_retries):
        try:
            with sync_playwright() as p:
                browser = p.chromium.launch(
                    proxy=proxy_config,
                    timeout=30000
                )
                
                page = browser.new_page()
                
                response = page.goto(
                    url,
                    wait_until='networkidle',
                    timeout=30000
                )
                
                if response.status in [403, 429]:
                    print(f"Blocked (status {response.status}), rotating proxy...")
                    browser.close()
                    time.sleep(2 ** attempt)
                    continue
                
                if response.status == 200:
                    content = page.content()
                    browser.close()
                    return content
                
        except PlaywrightTimeout:
            print(f"Timeout on attempt {attempt + 1}, retrying...")
            time.sleep(2 ** attempt)
            
        except Exception as e:
            print(f"Error: {e}")
            time.sleep(2 ** attempt)
    
    raise Exception(f"Failed to scrape {url} after {max_retries} attempts")

Detecting and Handling Blocks

async def check_for_captcha(page):
    captcha_selectors = [
        'iframe[src*="recaptcha"]',
        'iframe[src*="hcaptcha"]',
        '.g-recaptcha',
        '#captcha'
    ]
    
    for selector in captcha_selectors:
        if await page.locator(selector).count() > 0:
            return True
    return False

async def scrape_with_block_detection(url, proxy_config):
    async with async_playwright() as p:
        browser = await p.chromium.launch(proxy=proxy_config)
        page = await browser.new_page()
        
        await page.goto(url)
        
        if await check_for_captcha(page):
            print("CAPTCHA detected, rotating IP...")
            await browser.close()
            return None
        
        if "access denied" in (await page.content()).lower():
            print("Access denied, rotating IP...")
            await browser.close()
            return None
        
        content = await page.content()
        await browser.close()
        
        return content

Real-World Examples

Example 1: E-Commerce Price Scraping

from playwright.sync_api import sync_playwright
from bs4 import BeautifulSoup
import json

class AmazonScraper:
    def __init__(self, proxy_config):
        self.proxy = proxy_config
    
    def scrape_product(self, asin):
        url = f"https://www.amazon.com/dp/{asin}"
        
        with sync_playwright() as p:
            browser = p.chromium.launch(
                headless=True,
                proxy=self.proxy
            )
            
            context = browser.new_context(
                viewport={'width': 1920, 'height': 1080},
                user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
                locale='en-US'
            )
            
            page = context.new_page()
            
            page.goto(url, wait_until='networkidle')
            
            page.wait_for_timeout(2000)
            
            soup = BeautifulSoup(page.content(), 'html.parser')
            
            product = {
                'asin': asin,
                'title': soup.select_one('#productTitle').text.strip(),
                'price': soup.select_one('.a-price-whole').text.strip(),
                'rating': soup.select_one('.a-icon-star').text.strip(),
                'availability': soup.select_one('#availability').text.strip()
            }
            
            browser.close()
            
            return product

proxy = {
    "server": "http://gate.proxylabs.net:8080",
    "username": "user-country-US-session_time-15",
    "password": "your-password"
}

scraper = AmazonScraper(proxy)
product_data = scraper.scrape_product('B08N5WRWNW')
print(json.dumps(product_data, indent=2))

Example 2: Ticketing with Queue Systems

from playwright.sync_api import sync_playwright
import time

def monitor_ticketing_queue(event_url, proxy_config):
    with sync_playwright() as p:
        browser = p.chromium.launch(proxy=proxy_config)
        
        context = browser.new_context(
            viewport={'width': 1920, 'height': 1080},
            user_agent='Mozilla/5.0...'
        )
        
        page = context.new_page()
        
        page.goto(event_url)
        
        print("Waiting in queue...")
        
        while True:
            if "Select Tickets" in page.content():
                print("Queue cleared! Selecting tickets...")
                page.click('button:has-text("Select Tickets")')
                break
            
            time.sleep(5)
            page.reload()
        
        browser.close()

proxy = {
    "server": "http://gate.proxylabs.net:8080",
    "username": "user-session_time-30",
    "password": "your-password"
}

monitor_ticketing_queue('https://ticketmaster.com/event/XYZ', proxy)

Critical: Use sticky sessions (30 min) for queue systems. IP changes = lose your position.

Example 3: Social Media Scraping

from playwright.sync_api import sync_playwright

async def scrape_twitter_profile(username, proxy_config):
    async with async_playwright() as p:
        browser = await p.chromium.launch(
            headless=True,
            proxy=proxy_config
        )
        
        context = await browser.new_context(
            viewport={'width': 1920, 'height': 1080},
            user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        )
        
        page = await context.new_page()
        
        await page.goto(f'https://twitter.com/{username}')
        
        await page.wait_for_selector('[data-testid="tweet"]')
        
        tweets = await page.locator('[data-testid="tweet"]').all()
        
        tweet_data = []
        for tweet in tweets[:10]:
            text = await tweet.inner_text()
            tweet_data.append(text)
        
        await browser.close()
        
        return tweet_data

proxy = {
    "server": "http://gate.proxylabs.net:8080",
    "username": "user-session_time-20",
    "password": "your-password"
}

tweets = await scrape_twitter_profile('elonmusk', proxy)

Performance Optimization

Parallel Scraping with Playwright

from playwright.sync_api import sync_playwright
import concurrent.futures

def scrape_single_page(args):
    url, proxy = args
    
    with sync_playwright() as p:
        browser = p.chromium.launch(proxy=proxy)
        page = browser.new_page()
        page.goto(url)
        content = page.content()
        browser.close()
        return content

urls = [f'https://example.com/page{i}' for i in range(100)]

proxies = [
    {"server": "http://gate.proxylabs.net:8080", "username": f"user-session-{i}", "password": "pass"}
    for i in range(10)
]

proxy_cycle = [proxies[i % len(proxies)] for i in range(len(urls))]

with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
    results = list(executor.map(scrape_single_page, zip(urls, proxy_cycle)))

print(f"Scraped {len(results)} pages")

Performance tips:

  • Use ThreadPoolExecutor for parallel scraping
  • Limit workers to 5-10 (more = diminishing returns + higher resource use)
  • Each worker should have its own proxy session
  • Monitor memory usage (Playwright uses ~100-200MB per browser)

Debugging and Testing

Verify Proxy is Working

def test_proxy(proxy_config):
    with sync_playwright() as p:
        browser = p.chromium.launch(proxy=proxy_config)
        page = browser.new_page()
        
        page.goto('https://httpbin.org/ip')
        ip_response = page.inner_text('pre')
        print(f"Your IP: {ip_response}")
        
        page.goto('https://api.ipify.org?format=json')
        ipify_response = page.inner_text('pre')
        print(f"IPify check: {ipify_response}")
        
        browser.close()

test_proxy({
    "server": "http://gate.proxylabs.net:8080",
    "username": "your-username",
    "password": "your-password"
})

Visual Debugging (Screenshots)

def debug_scrape(url, proxy_config):
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=False,
            proxy=proxy_config
        )
        
        page = browser.new_page()
        page.goto(url)
        
        page.screenshot(path='debug_screenshot.png')
        
        with open('debug_content.html', 'w') as f:
            f.write(page.content())
        
        input("Press Enter to close browser...")
        
        browser.close()

Debugging checklist:

  • Take screenshots to see what the page looks like
  • Save HTML to check if JavaScript rendered
  • Run with headless=False to watch browser behavior
  • Check network tab for failed requests

Common Issues and Solutions

Issue: "ERR_TUNNEL_CONNECTION_FAILED"

Cause: Proxy authentication failed or proxy server unreachable.

Solution:

import requests

response = requests.get(
    'https://httpbin.org/ip',
    proxies={'http': 'http://user:[email protected]:8080'}
)
print(response.text)

Issue: Playwright hangs indefinitely

Cause: Page never reaches 'networkidle' state.

Solution: Use timeout and different wait strategy:

page.goto(url, wait_until='domcontentloaded', timeout=15000)

Issue: Getting blocked despite using proxies

Solutions:

  1. Check if using residential (not datacenter) proxies
  2. Add human-like delays and behavior
  3. Rotate User-Agent per session
  4. Enable JavaScript execution
  5. Check for CAPTCHA triggers

Best Practices Summary

  1. Always use residential proxies for bot-protected sites
  2. Use sticky sessions for multi-page workflows (10-30 min)
  3. Implement proper error handling and retry logic
  4. Add human-like behavior (mouse movements, delays)
  5. Monitor success rates and adjust strategies
  6. Rotate User-Agents but keep consistent per session
  7. Set realistic viewport sizes (1920x1080, 1366x768)
  8. Use appropriate wait strategies (networkidle for SPAs)
  9. Test proxies before production deployment
  10. Scale gradually - start with low volume, increase slowly

Ready to try the fastest residential proxies?

Join developers and businesses who trust ProxyLabs for mission-critical proxy infrastructure.

~200ms responseBest anti-bot bypass£2.50/GB
Start Building NowNo subscription required

Playwright + residential proxies = undetectable web scraping. Follow this guide, test thoroughly, and scale responsibly.

playwrightweb scrapingresidential proxiesbrowser automationpythonjavascript
JL
James Liu
Lead Engineer @ ProxyLabs

Building proxy infrastructure since 2019. Previously failed at many things, now failing slightly less.

Found this helpful? Share it with others.

Share