If you scrape, crawl, or automate anything at scale in Python, sooner or later the target site notices that every request comes from one IP and shuts you down. Rotating proxies in Python spreads your traffic across many IP addresses so no single one trips a rate limit or earns a ban. This guide shows the exact code for requests, httpx, and aiohttp, plus how to pick between a rotating gateway and a managed IP pool.
Two cross-cutting decisions shape everything below. First: rotating vs sticky sessions — a new IP per request, or one IP held for a multi-step flow. Second: residential vs datacenter IPs — see the proxy-type decision matrix. Get those two right and the code is the easy part.
1. Gateway rotation (recommended). You connect to a single endpoint and the provider hands you a fresh exit IP on every new connection. No list to manage, no dead IPs to prune. JIBAO's dynamic residential pool works this way: same host and port, a new IP each request.
2. Self-managed IP list. You hold a list of host:port entries and pick one per request. More control, but you own the health-checking, retries, and ban-tracking. Useful with dedicated datacenter IPs when you need known, stable addresses.
The simplest case — route one request through a gateway that rotates for you. Use socks5h:// (the trailing h means DNS is resolved on the proxy side, which avoids leaking lookups):
import requests
# JIBAO rotating residential gateway: a new exit IP per connection
PROXY = "socks5h://USERNAME:[email protected]:10001"
proxies = {"http": PROXY, "https": PROXY}
for _ in range(5):
r = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=30)
print(r.json()["origin"]) # a different IP each loop
Need requests to speak SOCKS5? Install the extra: pip install "requests[socks]". If you prefer an HTTP proxy endpoint, swap the scheme to http:// — the rest is identical.
import random
import requests
PROXY_POOL = [
"socks5h://USERNAME-country-us:[email protected]:10001",
"socks5h://USERNAME-country-uk:[email protected]:10001",
"socks5h://USERNAME-country-de:[email protected]:10001",
]
def fetch(url):
proxy = random.choice(PROXY_POOL)
return requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=30)
resp = fetch("https://example.com")
print(resp.status_code)
Login flows, carts, and paginated results break if the IP changes mid-task. Pin an IP by adding a session token to the username; reuse the same token to keep the same exit IP for its lifetime:
import requests
SESSION_ID = "task-0001"
PROXY = f"socks5h://USERNAME-session-{SESSION_ID}:[email protected]:10001"
s = requests.Session()
s.proxies = {"http": PROXY, "https": PROXY}
s.post("https://example.com/login", data={"u": "me", "p": "secret"})
s.get("https://example.com/dashboard") # same IP, session intact
httpx gives you a modern client with HTTP/2 and native async. Note that newer versions take a single proxy= argument:
import httpx
PROXY = "socks5h://USERNAME:[email protected]:10001"
with httpx.Client(proxy=PROXY, timeout=30) as client:
for _ in range(5):
print(client.get("https://httpbin.org/ip").json()["origin"])
SOCKS support in httpx needs pip install "httpx[socks]". For HTTP/2, add http2=True to the client — it makes your traffic look more like a real browser, which pairs well with TLS fingerprint matching.
For high throughput, fire many requests at once. Each connection through the gateway gets its own exit IP, so concurrency and rotation come together:
import asyncio
import aiohttp
from aiohttp_socks import ProxyConnector
PROXY = "socks5://USERNAME:[email protected]:10001"
async def fetch(url):
connector = ProxyConnector.from_url(PROXY)
async with aiohttp.ClientSession(connector=connector) as session:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=30)) as r:
return await r.text()
async def main():
urls = ["https://httpbin.org/ip"] * 20
results = await asyncio.gather(*(fetch(u) for u in urls))
print(f"fetched {len(results)} pages")
asyncio.run(main())
Install the SOCKS connector with pip install aiohttp_socks. Build a fresh connector per task so each request opens a new proxied connection.
Rotation only pays off if you retry failures on a fresh IP. Back off exponentially and rotate the session token on each attempt:
import requests
from time import sleep
def fetch_with_retry(url, max_retries=4):
for attempt in range(max_retries):
proxy = f"socks5h://USERNAME-session-r{attempt}:[email protected]:10001"
try:
r = requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=30)
if r.status_code in (403, 429):
raise requests.HTTPError(f"blocked: {r.status_code}")
r.raise_for_status()
return r.text
except requests.RequestException:
sleep(2 ** attempt) # 1s, 2s, 4s, 8s
raise RuntimeError(f"failed after {max_retries} tries: {url}")
Before a big run, confirm the IP actually changes. Hit an echo endpoint a few times and check the IPs differ. To also confirm your TLS fingerprint is not giving you away, run the request against JIBAO's free JA3/TLS fingerprint checker — a rotating IP with a dead-giveaway Python fingerprint still gets blocked.
Once rotation is solid, the next wall you hit is usually fingerprinting, not IPs. If clean residential IPs still get 403s, read how to bypass TLS fingerprinting with curl_cffi and the full Cloudflare bypass recipe.
Get $5 free credit and a rotating residential gateway that hands you a new IP on every request.
Start Free TrialNew users get $5 USDT instantly, plus an extra first-deposit reward — limited-time offer.