Proxy Rotation for Web Scraping
During industrial web scraping, a single IP address quickly gets blocked: sites detect high request frequency and return 403 or 429. Data loss reaches 40%. Manual proxy switching is inefficient; an automatic solution is needed. We build a system that distributes requests across a pool of addresses, quarantines blocked ones, and weighted-selects the most reliable proxies. Our experience with over 15 projects shows: proper rotation increases data collection to 99.5%.
How Weighted Proxy Selection Works
Each proxy in the pool stores statistics of successful and failed requests. The rotator calculates success_rate as the ratio of successful to total requests. When selecting an address, weighted randomness is used: a proxy with a coefficient of 0.92 is chosen 7 times more often than one with 0.5. If a proxy returns an error (403, 429, timeout), it is placed in quarantine for 15 minutes (configurable). This prevents retrying through a known-blocked IP.
Request → Proxy Selector
↓
[Proxy Pool]
192.168.1.1:8080 ← status: OK, 245 requests, score 0.92
10.0.0.5:3128 ← status: OK, 198 requests, score 0.88
172.16.0.2:8080 ← status: BLOCKED, in quarantine
↓
Target Site
↓
Response Checker
(status 200 → success / 403/429 → fail)
How to Integrate the Rotator with aiohttp
Integration relies on asyncio and aiohttp. The fetch function gets a proxy from the rotator, executes the request, and reports success. On error, the proxy goes into quarantine, and the next request uses a different address. This enables processing up to 1000 requests per minute without blocking. Use our Python scraping code for quick setup.
import asyncio
import aiohttp
from dataclasses import dataclass, field
from datetime import datetime, timedelta
import random
@dataclass
class ProxyEntry:
url: str
success: int = 0
fail: int = 0
blocked_until: datetime = field(default_factory=lambda: datetime.min)
@property
def is_available(self):
return datetime.now() > self.blocked_until
@property
def success_rate(self):
total = self.success + self.fail
return self.success / total if total > 0 else 0.5
class ProxyRotator:
def __init__(self, proxies: list[str], quarantine_minutes=15):
self.pool = [ProxyEntry(url=p) for p in proxies]
self.quarantine = timedelta(minutes=quarantine_minutes)
self._lock = asyncio.Lock()
async def get(self) -> ProxyEntry:
async with self._lock:
available = [p for p in self.pool if p.is_available]
if not available:
raise RuntimeError("All proxies are blocked")
weights = [p.success_rate for p in available]
return random.choices(available, weights=weights)[0]
async def report(self, proxy: ProxyEntry, success: bool):
async with self._lock:
if success:
proxy.success += 1
else:
proxy.fail += 1
proxy.blocked_until = datetime.now() + self.quarantine
async def fetch(session, url, rotator):
proxy = await rotator.get()
try:
async with session.get(url, proxy=proxy.url, timeout=10) as resp:
if resp.status in (403, 429, 503):
await rotator.report(proxy, success=False)
return None
await rotator.report(proxy, success=True)
return await resp.text()
except Exception:
await rotator.report(proxy, success=False)
return None
Why Weighted Selection is Better Than Random
Random selection overloads the system with errors: bad addresses receive as many requests as good ones. A weighted approach based on success_rate is similar to exponential smoothing—the longer a proxy works stably, the more often it is used. This reduces retries by 30% and speeds up data collection. Additionally, we implement proxy monitoring: every 5 minutes we check pool availability and automatically exclude long-blocked addresses.
Comparison of Proxy Types: Residential vs ISP vs Datacenter
| Type | Speed | Anonymity | Cost per IP/month | Blocking Resistance |
|---|---|---|---|---|
| Residential | Low | High | $50-$200 | Very high |
| ISP | Medium | Medium | $20-$50 | High |
| Datacenter | High | Low | $2-$10 | Low |
Residential proxies (e.g., Bright Data) provide the best bypass but are more expensive—budget savings come from fewer retries. ISP proxies (Oxylabs) are a sweet spot for scraping online stores. Datacenter proxies only suit quick prototypes where temporary blocks are acceptable.
What If All Proxies Are in Quarantine?
The algorithm checks blocked_until for each address. If all are unavailable, an exception is raised—a signal for the operator. We recommend a reserve pool of 5–10 proxies with increased delay between requests. With a pool of 20 residential proxies and default settings, the parser runs without downtime in 99% of cases. In complex scenarios, we automatically increase quarantine_minutes to 30 to reduce pool load.
Stages of Proxy Rotation Implementation
- Analysis of target sites: determine targeting, request limits, block types.
- Selection of proxy provider: choose optimal pool (residential/ISP/mix) for budget and tasks.
- Development of rotator: implement weighted selection, quarantine, and monitoring.
- Integration with parser: connect aiohttp, error handling, logging.
- Load testing: run 10,000 requests, check stability under peak loads.
Proxy Provider Comparison by Target Task
| Task | Recommended Provider | Minimum Pool |
|---|---|---|
| Scraping online stores (CIS) | Oxylabs ISP | 10 IPs |
| Data collection from social networks | Bright Data Resident | 20 IPs |
| Fast prototyping | Smartproxy Datacenter | 5 IPs |
For most tasks, 10–15 residential proxies from Smartproxy with adequate delays suffice. If global coverage is needed, choose Bright Data—their pool includes 72 million IPs.
What's Included in the Result
- Architecture of the rotation system with documentation
- Source code in Python with asyncio and aiohttp integration
- Deployment instructions (Docker, monitoring setup)
- Two weeks of support guarantee after deployment
We have 5+ years of experience developing scraping systems and have implemented 15+ projects with proxy rotation. All solutions undergo load testing. Over 15 clients trust our guaranteed result: data loss below 1% and average savings of $200/month on proxy costs. Get a consultation—we'll send an estimate in 1 day. Order implementation—we'll deploy the system in 2–3 days.
Timeline
Basic system: 2–3 working days. Turnkey with adaptation to your stack: up to 5 days.
Frequently Asked Questions
- How does weighted proxy selection work?
- Each proxy gets a weight based on its success rate. The higher the success rate, the more often it's chosen. This improves scraping stability and reduces retries by up to 30%.
- What if all proxies are in quarantine?
- The algorithm checks each proxy's block time. If all are unavailable, an exception is raised. In practice, keep a reserve pool of 5–10 proxies and increase the delay between requests. We configure quarantine per provider. With 20 residential proxies, downtime is under 1%.
- Which proxy providers do you recommend?
- For production scraping we use paid services: Bright Data (72M+ IP, global coverage) from $500/month, Oxylabs (good CIS coverage, ISP proxies) from $200/month, and Smartproxy (mobile and residential, fair pricing) from $50/month. Free lists are unstable and cause data loss.
- How to integrate the rotator with aiohttp?
- We provide ready Python code with asyncio and aiohttp. The fetch function gets a proxy from the rotator, executes the request, and reports success or failure. An example is in the article.
- How long does implementation take?
- Basic proxy rotation with monitoring takes 2–3 working days. For complex projects with custom logic and multiple proxy sources, up to a week. We offer a 2-week support guarantee after deployment.







