Why Rotate Proxies in Selenium?
When web scraping with Selenium, using a single IP address quickly leads to detection and blocking. Websites implement rate limiting, CAPTCHAs, and IP bans to prevent automated access. Proxy rotation solves this by distributing requests across multiple IP addresses, making your scraping activity appear as organic traffic from different users.
The main benefits of proxy rotation include:
- Avoiding IP bans - Spreading requests across multiple IPs prevents any single address from being flagged
- Bypassing rate limits - Each proxy gets its own rate limit allocation
- Geographic diversity - Access geo-restricted content from different locations
- Improved reliability - If one proxy fails, others continue working
- Better anonymity - Harder to trace scraping activity back to a single source
Setting Up selenium-wire for Proxy Rotation
The selenium-wire library extends Selenium with the ability to inspect and modify HTTP requests, including easy proxy configuration. It is the most popular choice for proxy rotation in Python Selenium projects.
Installation
Install selenium-wire and its dependencies:
pip install selenium-wire
pip install webdriver-manager
Basic Proxy Configuration
Here is how to configure a single proxy with selenium-wire:
from seleniumwire import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service
proxy_options = { 'proxy': { 'http': 'http://username:password@proxy.example.com:8080', 'https': 'https://username:password@proxy.example.com:8080', 'no_proxy': 'localhost,127.0.0.1' } }
service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service, seleniumwire_options=proxy_options)
driver.get('https://httpbin.org/ip') print(driver.page_source) driver.quit()
Implementing Proxy Rotation with a Proxy List
For basic proxy rotation, maintain a list of proxies and rotate through them. Here is a complete implementation:
import random from seleniumwire import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options
class ProxyRotator: def __init__(self, proxy_list): self.proxies = proxy_list self.current_index = 0
def get_next_proxy(self): proxy = self.proxies[self.current_index] self.current_index = (self.current_index + 1) % len(self.proxies) return proxy
def get_random_proxy(self): return random.choice(self.proxies)
def remove_proxy(self, proxy): if proxy in self.proxies: self.proxies.remove(proxy)
proxy_list = [ 'http://user:pass@proxy1.example.com:8080', 'http://user:pass@proxy2.example.com:8080', 'http://user:pass@proxy3.example.com:8080', 'http://user:pass@proxy4.example.com:8080', 'http://user:pass@proxy5.example.com:8080' ]
rotator = ProxyRotator(proxy_list)
def create_driver_with_proxy(proxy_url): chrome_options = Options() chrome_options.add_argument('--headless') chrome_options.add_argument('--no-sandbox') chrome_options.add_argument('--disable-dev-shm-usage')
proxy_options = { 'proxy': { 'http': proxy_url, 'https': proxy_url } }
service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome( service=service, options=chrome_options, seleniumwire_options=proxy_options ) return driver
urls_to_scrape = ['https://example.com/page1', 'https://example.com/page2']
for url in urls_to_scrape: proxy = rotator.get_next_proxy() driver = create_driver_with_proxy(proxy) try: driver.get(url) # Process page content print(f"Scraped {url} using {proxy}") finally: driver.quit()
Integrating with Bright Data API
Bright Data (formerly Luminati) provides rotating residential proxies with automatic IP rotation. Their gateway handles rotation for you, simplifying implementation:
from seleniumwire import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service
Bright Data configuration
BRIGHT_DATA_HOST = 'brd.superproxy.io' BRIGHT_DATA_PORT = 22225 BRIGHT_DATA_USER = 'brd-customer-CUSTOMER_ID-zone-ZONE_NAME' BRIGHT_DATA_PASS = 'YOUR_PASSWORD'
proxy_url = f'http://{BRIGHT_DATA_USER}:{BRIGHT_DATA_PASS}@{BRIGHT_DATA_HOST}:{BRIGHT_DATA_PORT}'
proxy_options = { 'proxy': { 'http': proxy_url, 'https': proxy_url } }
service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service, seleniumwire_options=proxy_options)
Each request automatically uses a different IP
for i in range(10): driver.get('https://httpbin.org/ip') print(f"Request {i+1}: {driver.find_element('tag name', 'pre').text}")
driver.quit()
Bright Data also supports session persistence for when you need the same IP across multiple requests:
# Add session ID for sticky sessions import random session_id = random.randint(1000000, 9999999) BRIGHT_DATA_USER = f'brd-customer-CUSTOMER_ID-zone-ZONE_NAME-session-{session_id}'Integrating with Smartproxy API
Smartproxy offers similar rotating proxy functionality with their own gateway:
from seleniumwire import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service
Smartproxy configuration
SMARTPROXY_HOST = 'gate.smartproxy.com' SMARTPROXY_PORT = 7000 SMARTPROXY_USER = 'YOUR_USERNAME' SMARTPROXY_PASS = 'YOUR_PASSWORD'
proxy_url = f'http://{SMARTPROXY_USER}:{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}'
proxy_options = { 'proxy': { 'http': proxy_url, 'https': proxy_url } }
service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service, seleniumwire_options=proxy_options)
driver.get('https://ip.smartproxy.com/json') print(driver.page_source) driver.quit()
Dynamic Proxy Switching Mid-Session
Sometimes you need to change proxies during an active browser session. selenium-wire allows this through the driver.proxy property:
from seleniumwire import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service
service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service)
Initial request without proxy
driver.get('https://httpbin.org/ip') print("Original IP:", driver.find_element('tag name', 'pre').text)
Switch to proxy
driver.proxy = { 'http': 'http://user:pass@proxy1.example.com:8080', 'https': 'http://user:pass@proxy1.example.com:8080' }
driver.get('https://httpbin.org/ip') print("Proxy 1 IP:", driver.find_element('tag name', 'pre').text)
Switch to different proxy
driver.proxy = { 'http': 'http://user:pass@proxy2.example.com:8080', 'https': 'http://user:pass@proxy2.example.com:8080' }
driver.get('https://httpbin.org/ip') print("Proxy 2 IP:", driver.find_element('tag name', 'pre').text)
driver.quit()
Error Handling and Retry Logic
Robust proxy rotation requires proper error handling. Proxies fail, get banned, or timeout frequently. Here is a production-ready implementation:
import time import random from seleniumwire import webdriver from selenium.common.exceptions import WebDriverException, TimeoutException from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options
class RobustProxyRotator: def __init__(self, proxy_list, max_retries=3): self.proxies = proxy_list.copy() self.failed_proxies = [] self.max_retries = max_retries
def get_working_proxy(self): if not self.proxies: # Restore failed proxies and try again self.proxies = self.failed_proxies.copy() self.failed_proxies = [] return random.choice(self.proxies) if self.proxies else None
def mark_failed(self, proxy): if proxy in self.proxies: self.proxies.remove(proxy) self.failed_proxies.append(proxy)
def scrape_with_retry(self, url, process_func): for attempt in range(self.max_retries): proxy = self.get_working_proxy() if not proxy: raise Exception("No proxies available")
driver = None try: driver = self._create_driver(proxy) driver.set_page_load_timeout(30) driver.get(url)
# Check for blocking indicators if self._is_blocked(driver): raise Exception("Proxy blocked")
result = process_func(driver) return result
except (WebDriverException, TimeoutException, Exception) as e: print(f"Attempt {attempt + 1} failed with {proxy}: {e}") self.mark_failed(proxy) time.sleep(2 ** attempt) # Exponential backoff
finally: if driver: driver.quit()
raise Exception(f"Failed to scrape {url} after {self.max_retries} attempts")
def _create_driver(self, proxy): chrome_options = Options() chrome_options.add_argument('--headless') chrome_options.add_argument('--no-sandbox')
proxy_options = { 'proxy': {'http': proxy, 'https': proxy} }
service = Service(ChromeDriverManager().install()) return webdriver.Chrome( service=service, options=chrome_options, seleniumwire_options=proxy_options )
def _is_blocked(self, driver): blocked_indicators = [ 'access denied', 'blocked', 'captcha', 'rate limit', 'too many requests' ] page_text = driver.page_source.lower() return any(indicator in page_text for indicator in blocked_indicators)
Usage
proxies = [ 'http://user:pass@proxy1.com:8080', 'http://user:pass@proxy2.com:8080', 'http://user:pass@proxy3.com:8080' ]
rotator = RobustProxyRotator(proxies)
def extract_data(driver): return driver.find_element('tag name', 'body').text
result = rotator.scrape_with_retry('https://example.com', extract_data) print(result)
Proxy Authentication Methods
Different proxy providers use different authentication methods. Here is how to handle each:
| Method | Format | Example |
|---|---|---|
| Username/Password | http://user:pass@host:port | http://john:secret@proxy.com:8080 |
| IP Whitelisting | http://host:port | http://proxy.com:8080 |
| API Key in URL | http://apikey@host:port | http://abc123@proxy.com:8080 |
Best Practices for Proxy Rotation
Follow these guidelines for effective and ethical proxy rotation:
Rate Limiting
- Add random delays between requests (2-5 seconds minimum)
- Implement exponential backoff on failures
- Respect robots.txt and website terms of service
Proxy Pool Management
- Regularly test and validate proxies
- Remove consistently failing proxies
- Maintain geographic diversity for global scraping
- Use residential proxies for sites with strict detection
Browser Fingerprinting
- Rotate user agents alongside proxies
- Randomize viewport sizes and screen resolutions
- Clear cookies and cache between sessions
- Use undetected-chromedriver for strict anti-bot sites
import random
user_agents = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36...', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36...' ]
chrome_options = Options() chrome_options.add_argument(f'--user-agent={random.choice(user_agents)}')
Session Management
- Use sticky sessions for multi-page workflows (login, checkout)
- Create new browser instances for unrelated requests
- Implement proper cleanup to avoid resource leaks
Common Issues and Solutions
| Issue | Cause | Solution |
|---|---|---|
| SSL Certificate Errors | Proxy intercepting HTTPS | Add verify_ssl=False to options or use proper certificates |
| Connection Timeouts | Slow or dead proxy | Implement timeout and retry logic |
| Authentication Failures | Wrong credentials format | URL encode special characters in password |
| Memory Leaks | Not closing drivers | Always use try/finally to quit driver |
Conclusion
Proxy rotation in Selenium Python is essential for reliable web scraping at scale. Using selenium-wire provides the flexibility to implement custom rotation logic or integrate with commercial proxy providers like Bright Data and Smartproxy. The key to success is combining proper rotation strategies with robust error handling, respecting rate limits, and implementing anti-detection measures. Start with a simple rotation implementation and gradually add sophistication based on your specific scraping requirements and the websites you target.