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=Falseto 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:
- Check if using residential (not datacenter) proxies
- Add human-like delays and behavior
- Rotate User-Agent per session
- Enable JavaScript execution
- Check for CAPTCHA triggers
Best Practices Summary
- Always use residential proxies for bot-protected sites
- Use sticky sessions for multi-page workflows (10-30 min)
- Implement proper error handling and retry logic
- Add human-like behavior (mouse movements, delays)
- Monitor success rates and adjust strategies
- Rotate User-Agents but keep consistent per session
- Set realistic viewport sizes (1920x1080, 1366x768)
- Use appropriate wait strategies (networkidle for SPAs)
- Test proxies before production deployment
- 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.
Playwright + residential proxies = undetectable web scraping. Follow this guide, test thoroughly, and scale responsibly.
Building proxy infrastructure since 2019. Previously failed at many things, now failing slightly less.
Related Articles
Best Residential Proxies 2026: Top Providers Compared
Compare the best residential proxy providers in 2026. In-depth analysis of pricing, features, pool sizes, and performance to help you choose the right proxy service for web scraping, automation, and data collection.
10 min readPrivate vs Shared Proxy Pools: Which Should You Choose?
Understand the critical differences between private and shared proxy pools. Learn how pool sharing affects IP reputation, block rates, pricing, and success rates for web scraping and automation.
11 min read