v1.10.82-f67ee7d
Skip to main content
Back to Hex Proxies

How to Set Up Rotating Proxies in Python

Last updated: April 2026

By Hex Proxies Engineering Team

A hands-on Python tutorial covering rotating proxy setup with four major libraries: requests, aiohttp, httpx, and Scrapy. Includes complete, runnable code with error handling, retry logic, async rotation, and performance benchmarks.

intermediate12 minutesdeveloper-tutorial

How do I use rotating proxies in Python?

To use rotating proxies in Python, configure your HTTP client to route requests through a proxy gateway like gate.hexproxies.com:8080 with your credentials. For the requests library, pass a proxies dict. For aiohttp, use ProxyConnector. For httpx, use the proxies parameter. Each request through the gateway automatically gets a new IP. For sticky sessions, append -session-ID to your username. Error handling should catch 407 (auth failure), 429 (rate limit), and connection timeouts with exponential backoff.

How to Set Up Rotating Proxies in Python: Complete Tutorial

Python is the most popular language for web scraping and automation, and rotating proxies are essential for any serious data collection project. This tutorial covers four Python HTTP libraries — requests, aiohttp, httpx, and Scrapy — with complete, runnable code examples that include authentication, rotation configuration, error handling, retry logic, and session management. Every example uses the Hex Proxies gateway, but the patterns apply to any proxy service that supports username/password authentication.

By the end of this guide, you will have production-ready proxy integration code for whichever Python library your project uses.


Prerequisites

Before starting, you need:

  • Python 3.9 or later installed
  • A Hex Proxies account with username and password credentials
  • The proxy gateway address: gate.hexproxies.com (port 8080 for HTTP/HTTPS, port 1080 for SOCKS5)

Install the libraries you plan to use:

# Choose one or more
pip install requests
pip install aiohttp
pip install httpx
pip install scrapy

1. Rotating Proxies with Requests

The requests library is Python's most widely used HTTP client. Proxy configuration is straightforward through the proxies parameter.

Basic Rotating Proxy Setup

import requests

PROXY_USER = "YOUR_USERNAME" PROXY_PASS = "YOUR_PASSWORD" PROXY_HOST = "gate.hexproxies.com" PROXY_PORT = 8080

proxy_url = f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}" proxies = { "http": proxy_url, "https": proxy_url, }

Each request automatically gets a new IP response = requests.get( "https://httpbin.org/ip", proxies=proxies, timeout=15, ) print(response.json()) # Output: {"origin": "203.0.113.42"} (a different IP each time) ```

Requests with Retry Logic and Error Handling

Production code needs robust error handling. Proxy requests can fail due to authentication errors (407), rate limiting (429), connection timeouts, and temporary gateway errors (502/503).

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import time

PROXY_USER = "YOUR_USERNAME" PROXY_PASS = "YOUR_PASSWORD" PROXY_HOST = "gate.hexproxies.com" PROXY_PORT = 8080

proxy_url = f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}" proxies = { "http": proxy_url, "https": proxy_url, }

def create_session_with_retries( max_retries: int = 3, backoff_factor: float = 1.0, status_forcelist: tuple = (429, 500, 502, 503, 504), ) -> requests.Session: """Create a requests session with automatic retry on failure.""" session = requests.Session() session.proxies = proxies

retry_strategy = Retry( total=max_retries, backoff_factor=backoff_factor, status_forcelist=status_forcelist, allowed_methods=["GET", "HEAD", "OPTIONS"], ) adapter = HTTPAdapter(max_retries=retry_strategy) session.mount("http://", adapter) session.mount("https://", adapter) return session

def fetch_with_proxy(url: str, session: requests.Session) -> dict: """Fetch a URL through the proxy with comprehensive error handling.""" try: response = session.get(url, timeout=15) response.raise_for_status() return {"url": url, "status": response.status_code, "data": response.text} except requests.exceptions.ProxyError as e: return {"url": url, "status": 407, "error": f"Proxy auth failed: {e}"} except requests.exceptions.ConnectTimeout: return {"url": url, "status": 0, "error": "Connection timed out"} except requests.exceptions.ReadTimeout: return {"url": url, "status": 0, "error": "Read timed out"} except requests.exceptions.HTTPError as e: return {"url": url, "status": e.response.status_code, "error": str(e)} except requests.exceptions.RequestException as e: return {"url": url, "status": 0, "error": f"Request failed: {e}"}

Usage session = create_session_with_retries() urls = [f"https://example.com/product/{i}" for i in range(1, 11)]

for url in urls: result = fetch_with_proxy(url, session) print(f"{result['url']} -> {result.get('status', 'error')}") time.sleep(1) # polite delay between requests ```

Sticky Sessions with Requests

For workflows requiring the same IP across multiple requests (login flows, checkout processes), append a session ID to your username:

import requests
import uuid

def create_sticky_proxy_session(session_name: str = "") -> requests.Session: """Create a requests session that uses the same proxy IP for all requests.""" sid = session_name or uuid.uuid4().hex[:12] sticky_proxy = ( f"http://{PROXY_USER}-session-{sid}:{PROXY_PASS}" f"@{PROXY_HOST}:{PROXY_PORT}" ) session = requests.Session() session.proxies = {"http": sticky_proxy, "https": sticky_proxy} session.timeout = 30 return session

All requests in this session use the same IP session = create_sticky_proxy_session("login-flow-001") session.get("https://example.com/login") session.post("https://example.com/login", data={"user": "me", "pass": "secret"}) session.get("https://example.com/dashboard") # Same IP as login ```


2. Async Rotating Proxies with aiohttp

For high-throughput scraping, aiohttp provides async HTTP requests that dramatically increase concurrency. Instead of waiting for each response sequentially, you can run dozens of requests simultaneously.

import aiohttp
import asyncio
from aiohttp import BasicAuth

PROXY_USER = "YOUR_USERNAME" PROXY_PASS = "YOUR_PASSWORD" PROXY_URL = "http://gate.hexproxies.com:8080" PROXY_AUTH = BasicAuth(PROXY_USER, PROXY_PASS)

async def fetch( session: aiohttp.ClientSession, url: str, max_retries: int = 3, ) -> dict: """Fetch a URL through the rotating proxy with retry logic.""" for attempt in range(1, max_retries + 1): try: async with session.get( url, proxy=PROXY_URL, proxy_auth=PROXY_AUTH, timeout=aiohttp.ClientTimeout(total=15), ) as response: if response.status == 429: wait = 2 attempt await asyncio.sleep(wait) continue text = await response.text() return {"url": url, "status": response.status, "data": text} except aiohttp.ClientProxyConnectionError: return {"url": url, "status": 407, "error": "Proxy connection failed"} except asyncio.TimeoutError: if attempt < max_retries: await asyncio.sleep(2 attempt) continue return {"url": url, "status": 0, "error": "Timed out after retries"} except aiohttp.ClientError as e: return {"url": url, "status": 0, "error": str(e)} return {"url": url, "status": 429, "error": "Rate limited after retries"}

async def scrape_urls(urls: list[str], concurrency: int = 10) -> list[dict]: """Scrape multiple URLs concurrently through rotating proxies.""" semaphore = asyncio.Semaphore(concurrency) results = []

async def bounded_fetch(url: str) -> dict: async with semaphore: result = await fetch(session, url) await asyncio.sleep(0.5) # polite delay return result

async with aiohttp.ClientSession() as session: tasks = [bounded_fetch(url) for url in urls] results = await asyncio.gather(*tasks) return list(results)

Usage urls = [f"https://example.com/page/{i}" for i in range(1, 101)] results = asyncio.run(scrape_urls(urls, concurrency=10))

success = sum(1 for r in results if r.get("status") == 200) print(f"Success: {success}/{len(results)}") ```

Key aiohttp Advantages

  • Concurrency: 10--50 simultaneous requests vs sequential processing with requests.
  • Speed: Scrape 1,000 URLs in the time requests handles 50--100.
  • Memory: Lower memory footprint per connection than threading alternatives.

3. Rotating Proxies with httpx

httpx is a modern Python HTTP client that supports both sync and async modes, HTTP/2, and first-class proxy support. It is increasingly popular as a replacement for requests.

import httpx
import time

PROXY_USER = "YOUR_USERNAME" PROXY_PASS = "YOUR_PASSWORD" PROXY_URL = f"http://{PROXY_USER}:{PROXY_PASS}@gate.hexproxies.com:8080"

def scrape_with_httpx(urls: list[str]) -> list[dict]: """Scrape URLs using httpx with rotating proxy and retry logic.""" results = [] transport = httpx.HTTPTransport(retries=3)

with httpx.Client( proxy=PROXY_URL, transport=transport, timeout=httpx.Timeout(15.0, connect=10.0), follow_redirects=True, ) as client: for url in urls: try: response = client.get(url) results.append({ "url": url, "status": response.status_code, "data": response.text[:200], }) except httpx.ProxyError as e: results.append({"url": url, "status": 407, "error": str(e)}) except httpx.TimeoutException: results.append({"url": url, "status": 0, "error": "Timeout"}) except httpx.HTTPError as e: results.append({"url": url, "status": 0, "error": str(e)}) time.sleep(1)

return results

Async version async def scrape_async_httpx(urls: list[str]) -> list[dict]: """Async scraping with httpx and rotating proxy.""" results = [] transport = httpx.AsyncHTTPTransport(retries=3)

async with httpx.AsyncClient( proxy=PROXY_URL, transport=transport, timeout=httpx.Timeout(15.0, connect=10.0), follow_redirects=True, ) as client: for url in urls: try: response = await client.get(url) results.append({ "url": url, "status": response.status_code, "data": response.text[:200], }) except httpx.HTTPError as e: results.append({"url": url, "status": 0, "error": str(e)})

return results

Usage urls = [f"https://example.com/item/{i}" for i in range(1, 21)] results = scrape_with_httpx(urls) ```

httpx vs requests vs aiohttp

Featurerequestsaiohttphttpx
Sync supportYesNoYes
Async supportNoYesYes
HTTP/2NoNoYes
Proxy authVia URLBasicAuth objectVia URL
Built-in retryVia adapterManualVia transport
Best forSimple scriptsHigh concurrencyModern projects

4. Scrapy Proxy Middleware

Scrapy is Python's leading web scraping framework. Proxy integration works through custom middleware that injects proxy settings into every request.

Basic Proxy Middleware

# myproject/middlewares.py

import logging from scrapy import signals

logger = logging.getLogger(__name__)

class HexProxyMiddleware: """Scrapy middleware that routes all requests through Hex Proxies."""

PROXY_USER = "YOUR_USERNAME" PROXY_PASS = "YOUR_PASSWORD" PROXY_HOST = "gate.hexproxies.com" PROXY_PORT = 8080

@classmethod def from_crawler(cls, crawler): middleware = cls() return middleware

def process_request(self, request, spider): proxy_url = ( f"http://{self.PROXY_USER}:{self.PROXY_PASS}" f"@{self.PROXY_HOST}:{self.PROXY_PORT}" ) request.meta["proxy"] = proxy_url

def process_response(self, request, response, spider): if response.status == 407: logger.error("Proxy authentication failed for %s", request.url) if response.status == 429: logger.warning("Rate limited on %s, will retry", request.url) return request.replace(dont_filter=True) return response

def process_exception(self, request, exception, spider): logger.error("Proxy error on %s: %s", request.url, exception) return request.replace(dont_filter=True) ```

Enable the Middleware in settings.py

# myproject/settings.py

DOWNLOADER_MIDDLEWARES = { "myproject.middlewares.HexProxyMiddleware": 350, # Disable the default HTTP proxy middleware "scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware": None, }

Recommended settings for proxy-based scraping CONCURRENT_REQUESTS = 16 DOWNLOAD_DELAY = 1 DOWNLOAD_TIMEOUT = 15 RETRY_TIMES = 3 RETRY_HTTP_CODES = [429, 500, 502, 503, 504] ```

Sticky Session Middleware for Scrapy

For crawls that require session continuity (paginated results, authenticated scraping):

# myproject/middlewares.py

import uuid

class HexStickySessionMiddleware: """Scrapy middleware with sticky proxy sessions per spider."""

PROXY_USER = "YOUR_USERNAME" PROXY_PASS = "YOUR_PASSWORD" PROXY_HOST = "gate.hexproxies.com" PROXY_PORT = 8080

def __init__(self): self.session_id = uuid.uuid4().hex[:12]

def process_request(self, request, spider): # Use a per-spider sticky session session_id = getattr(spider, "proxy_session_id", self.session_id) proxy_url = ( f"http://{self.PROXY_USER}-session-{session_id}:{self.PROXY_PASS}" f"@{self.PROXY_HOST}:{self.PROXY_PORT}" ) request.meta["proxy"] = proxy_url ```


Error Handling Reference

Every proxy integration should handle these failure modes:

Status CodeMeaningAction
407Proxy authentication failedCheck credentials, verify username/password format
429Rate limited by targetWait with exponential backoff, then retry
403Blocked by targetRotate IP (change session ID), add delay
502Proxy gateway errorRetry after 2--5 seconds
503Proxy service unavailableRetry after 5--10 seconds
Connection timeoutProxy or target unreachableRetry with longer timeout, verify proxy is responsive
Read timeoutResponse too slowIncrease timeout, retry once

Performance Comparison: Libraries Under Proxy Rotation

Benchmarked scraping 1,000 URLs through Hex Proxies rotating gateway:

LibraryModeConcurrencyTime (1K URLs)Success RateMemory
requestsSync118 min96.2%45 MB
aiohttpAsync201.5 min97.1%38 MB
httpx (sync)Sync117 min96.5%52 MB
httpx (async)Async201.8 min96.8%48 MB
ScrapyAsync162.1 min97.4%65 MB

Takeaway: For high-volume scraping, use aiohttp or Scrapy. For simple scripts and prototyping, requests or httpx (sync) are easier to debug and maintain.


How Hex Proxies Works with Python

Hex Proxies provides a single gateway endpoint that handles all rotation logic server-side:

  • Gateway: gate.hexproxies.com:8080 (HTTP/HTTPS) or gate.hexproxies.com:1080 (SOCKS5)
  • Authentication: Username/password via proxy URL or Basic Auth header
  • Rotation: Automatic per-request rotation by default. Add -session-ID to username for sticky sessions.
  • Geo-targeting: Add -country-US or -city-london to username for location-specific IPs.
  • Pool: 10M+ residential IPs across 195 countries.
  • Format: All standard Python proxy URL formats are supported. No SDK or custom library required.

Frequently Asked Questions

Frequently Asked Questions

How do I set up a rotating proxy in Python?

Install the requests library (pip install requests), then pass a proxies dictionary to your requests.get() call with the proxy URL in the format http://USERNAME:PASSWORD@gate.hexproxies.com:8080. Each request through this gateway automatically gets a new IP address. No additional configuration is needed for basic rotation.

Which Python library is best for proxy rotation?

For simple scripts, use requests — it is the easiest to set up and debug. For high-volume scraping (1,000+ URLs), use aiohttp for its async concurrency, which is 10--12x faster than sequential requests. For full scraping projects with crawling logic, Scrapy provides built-in retry, rate limiting, and middleware support. httpx is a good middle ground with both sync and async modes.

How do I handle proxy authentication errors (407) in Python?

A 407 status means your proxy credentials are rejected. Check that your username and password are correct, that special characters in your password are URL-encoded, and that your account is active. In code, catch requests.exceptions.ProxyError or aiohttp.ClientProxyConnectionError and log the error details.

How do I make the same IP persist across multiple requests in Python?

Append a session ID to your proxy username: YOUR_USERNAME-session-abc123. All requests using this modified username will route through the same IP. To get a new IP, change the session ID. This works with all Python HTTP libraries — just modify the proxy URL string.

How do I add proxy support to Scrapy?

Create a custom downloader middleware that sets request.meta["proxy"] to your proxy URL in the process_request method. Enable it in settings.py by adding it to DOWNLOADER_MIDDLEWARES with a priority of 350 and disabling the default HttpProxyMiddleware. See the complete middleware code in this guide.

What is the fastest way to scrape with proxies in Python?

Use aiohttp with a semaphore to control concurrency. With 20 concurrent connections through rotating proxies, you can scrape 1,000 URLs in under 2 minutes compared to 18 minutes with sequential requests. The key is balancing concurrency with polite delays to avoid triggering rate limits on the target site.

How do I use SOCKS5 proxies in Python?

Install the PySocks library (pip install pysocks) for requests, or use aiohttp-socks for aiohttp. The proxy URL format changes to socks5://USERNAME:PASSWORD@gate.hexproxies.com:1080. Note the different port (1080 for SOCKS5 vs 8080 for HTTP).

How do I handle rate limiting (429) when scraping with proxies?

Implement exponential backoff: on a 429 response, wait 2 seconds before retrying, then 4 seconds, then 8 seconds. In requests, use HTTPAdapter with Retry(status_forcelist=[429]). In aiohttp, check response.status and await asyncio.sleep(2 ** attempt). Also add a base delay of 0.5--2 seconds between all requests.

Can I use httpx as a drop-in replacement for requests with proxies?

Nearly. httpx uses a proxies parameter similar to requests but with slightly different syntax. The main differences are: httpx uses httpx.Client context manager instead of requests.Session, proxy errors raise httpx.ProxyError instead of requests.exceptions.ProxyError, and httpx supports HTTP/2 which requests does not. The proxy URL format is identical.

How do I geo-target my proxy requests in Python?

Modify your proxy username to include a country or city code: YOUR_USERNAME-country-us or YOUR_USERNAME-city-london. The proxy gateway will select an IP from that location. This works with all rotation modes (per-request, timed, and session-based) and all Python libraries.

Related Reading

Ready to Get Started?

Put this guide into practice with Hex Proxies.