Adaptive Proxy Rotation: Adjusting Proxies Based on Response Codes and Latency
July 17, 2026
Why Adaptive Proxy Rotation Matters
When you scrape or automate tasks at scale, a static rotation schedule can waste good proxies and keep using bad ones. By watching each request's outcome — HTTP status code, response time, and occasional error messages — you can decide in real time whether to keep using a proxy, retire it, or move it to a cool‑down pool.
Building the Health Score
A simple scoring algorithm works well for many use cases:
- Start each proxy with a score of 100.
- On a successful request (status 2xx) and latency under a target (e.g., 800 ms), add 2 points.
- On a retry‑worthy status (429, 502‑504) subtract 10 points.
- On a hard failure (timeout, connection error) subtract 20 points.
- If latency exceeds the target, subtract 1 point per 100 ms over the limit.
- Clamp the score between 0 and 200.
When the score falls below 30, move the proxy to a cool‑down list for a configurable period (e.g., 5 minutes). After the cool‑down, reset its score to 50 and return it to the active pool.
Implementing in Python
Below is a self‑contained example that uses the requests library and a round‑robin iterator over a list of proxies. The code shows how to update scores, enforce cool‑downs, and pick the next healthy proxy.
import time
import requests
from collections import deque
# ----- configuration -----
TARGET_LATENCY_MS = 800
SUCCESS_BONUS = 2
RETRY_PENALTY = 10
HARD_PENALTY = 20
LATENCY_PENALTY_PER_100MS = 1
COOLDOWN_SECONDS = 300
MIN_SCORE_TO_USE = 30
MAX_SCORE = 200
START_SCORE = 100
RESET_AFTER_COOLDOWN = 50
# Example proxy list – replace with your RoProxy credentials
PROXIES = [
'http://user:[email protected]:3128',
'http://user:[email protected]:3128',
'http://user:[email protected]:3128',
]
# ----- state -----
scores = {p: START_SCORE for p in PROXIES}
cooldown_until = {p: 0 for p in PROXIES}
proxy_queue = deque(PROXIES)
def latency_penalty(latency_ms):
if latency_ms <= TARGET_LATENCY_MS:
return 0
excess = latency_ms - TARGET_LATENCY_MS
return (excess // 100) * LATENCY_PENALTY_PER_100MS
def update_score(proxy, status_code, latency_ms, error=None):
now = time.time()
if error:
scores[proxy] = max(0, scores[proxy] - HARD_PENALTY)
return
if 200 <= status_code < 300:
scores[proxy] = min(MAX_SCORE, scores[proxy] + SUCCESS_BONUS)
elif status_code in (429, 502, 503, 504):
scores[proxy] = max(0, scores[proxy] - RETRY_PENALTY)
else:
# other 4xx/5xx treat as mild penalty
scores[proxy] = max(0, scores[proxy] - 5)
scores[proxy] -= latency_penalty(latency_ms)
scores[proxy] = max(0, min(MAX_SCORE, scores[proxy]))
if scores[proxy] < MIN_SCORE_TO_USE:
cooldown_until[proxy] = now + COOLDOWN_SECONDS
def is_available(proxy):
return time.time() >= cooldown_until[proxy]
def get_next_proxy():
while proxy_queue:
p = proxy_queue.popleft()
if is_available(p):
proxy_queue.append(p) # put back at end for round‑robin
return p
else:
# still cooling, put at end and try next
proxy_queue.append(p)
# fallback: return the proxy with highest score regardless of cooldown
return max(scores, key=scores.get)
def fetch(url):
start = time.time()
proxy = get_next_proxy()
proxies = {'http': proxy, 'https': proxy}
try:
resp = requests.get(url, proxies=proxies, timeout=10)
latency_ms = (time.time() - start) * 1000
update_score(proxy, resp.status_code, latency_ms)
return resp
except requests.RequestException as exc:
latency_ms = (time.time() - start) * 1000
update_score(proxy, 0, latency_ms, error=True)
# optional: retry with another proxy
return None
# ----- usage example -----
if __name__ == '__main__':
for i in range(20):
r = fetch('https://httpbin.org/ip')
if r:
print('Request', i, ':', r.status_code, 'via', r.headers.get('Via', 'unknown'))
else:
print('Request', i, ': failed')
time.sleep(0.5)
How the Script Works
- Score initialization – each proxy starts with a neutral score.
- Request execution – we pick the first available proxy from a rotating queue.
- Result handling – based on the HTTP status, latency, and any exceptions we adjust the score.
- Cool‑down enforcement – proxies that dip below the minimum score are shelved for a set period.
- Fallback – if every proxy is cooling, we fall back to the one with the highest score to avoid stalling.
Tuning the Parameters
- Target latency – adjust TARGET_LATENCY_MS to match your network expectations. For residential proxies you might allow 1.2 seconds; for datacenter you could tighten to 400 ms.
- Reward/penalty values – increase SUCCESS_BONUS if you want fast recovery of good proxies, or raise HARD_PENALTY to aggressively ban flaky nodes.
- Cool‑down duration – longer cool‑downs reduce churn but may leave you with fewer active proxies; short cool‑downs keep the pool fluid but risk re‑using a bad proxy too soon.
- Score limits – the MAX_SCORE prevents a single proxy from dominating forever; the MIN_SCORE_TO_USE defines the threshold for removal.
Real‑World Scenario: Price Monitoring Across Regions
Imagine you need to check product prices on an e‑commerce site every five minutes from three different countries. You purchase a mix of residential proxies from RoProxy targeting US, DE, and JP. Without adaptation, a single overloaded proxy might start returning 429 responses, causing you to miss price updates. With adaptive rotation:
- The first 429 triggers a ‑10 penalty; after a few occurrences the score drops below 30 and the proxy goes into cool‑down.
- While that proxy rests, the scheduler picks the next healthy proxy, preserving data quality.
- After the cool‑down expires, the proxy’s score is reset to a moderate value (50) giving it another chance, which often succeeds once the remote server’s rate limit window has passed.
This loop keeps your scraper running smoothly without manual intervention.
Extending to Other Languages
The same logic can be ported to Node.js, Go, or any language that lets you make HTTP requests and measure latency. Key steps remain:
- Maintain a map of proxy identifiers to scores and cooldown timestamps.
- Before each request, select a proxy whose cooldown has expired.
- After the request, update the score based on status code, latency, and errors.
- Apply cool‑down when score falls below threshold.
- Optionally, implement a jittered selection to avoid predictable patterns.
Best Practices & Gotchas
- Validate proxy format – ensure username:password@host:port is correctly URL‑encoded if special characters appear.
- Handle HTTPS – when using HTTP proxies for HTTPS traffic, the library will establish a CONNECT tunnel; most HTTP clients support this out of the box.
- Avoid over‑penalizing transient spikes – a single latency spike shouldn’t instantly ban a proxy; the linear latency penalty smooths out occasional delays.
- Log score changes – keep a simple CSV or time‑series store (e.g., Prometheus) of each proxy’s score to detect systemic issues (e.g., a particular subnet consistently getting blocked).
- Respect target site’s terms – adaptive rotation improves efficiency, but you should still obey rate limits and copyright rules; use proxies responsibly.
Conclusion
Adaptive proxy rotation turns a static list of IPs into a self‑healing pool that reacts to real‑world feedback. By scoring each proxy on success, failure, and latency, you automatically sideline troubled nodes and give healthy ones more work. The Python example above provides a ready‑to‑start foundation; you can adapt the scoring rules, cool‑down times, and selection strategy to match your specific workload—whether it’s SERP monitoring, ad verification, or large‑scale web scraping.
Implementing this pattern reduces manual proxy management, cuts down on bans, and makes your scraping jobs more reliable and cost‑effective.