Web scraping proxies
Web scraping proxies on real household IPs in 195+ countries and regions: rotating exits or sticky sessions by major city, from US$1.50/GB, HTTP and SOCKS5.
View options and pricingTell a wrong login from a dead connection in the error text, then wrap the call so only the failures worth repeating are repeated.
ProxyError is a subclass of ConnectionError, so it is raised before the target site answers. The text at the end of the message tells you which case it is. In our local test a refused login ended with "Tunnel connection failed: 407 Proxy Authentication Required" and a closed port ended with "Connection refused".
Cause 1: wrong host, port or protocol. The Proxy302 gateway uses port 2222 for HTTP and 3333 for SOCKS5. A SOCKS5 address in requests also needs pip install "requests[socks]". Cause 2: the login is refused (407) because of a typo, a missing special character or a password with @ that was not percent-encoded in the URL. Cause 3: the asset has ended: a rented IP past its end date, or a traffic balance that is used up.
Not every failure is a ProxyError. A response with status 429 or 503 is a normal reply from the site or gateway, and a timeout is its own exception. They need different handling, as the table shows.
| Item | Retry? | What to do |
|---|---|---|
| ProxyError ending in 407 | No | Fix the login and stop; every retry is refused again |
| ProxyError, Connection refused | No | Check host, port, protocol and whether the asset has ended |
| ConnectTimeout | Yes, a few times | The Requests source says requests with this error are safe to retry |
| ReadTimeout | Yes for GET, careful with POST | The request may already have reached the site |
| 429 Too Many Requests | Yes, after waiting | Wait for the Retry-After time if it is sent, then slow down |
| 502, 503, 504 | Yes, a few times | Usually temporary on the gateway or site side |
| 403, 404 | No | The answer will not change; change the request or the target |
Based on the Requests source, the urllib3 Retry docs, MDN and our local test of 13 November 2026. The "yes" rows are advice for a small number of attempts, not a promise that a retry succeeds.
Print str(exc) once. 407 means the login, Connection refused or a timeout means the address, port or protocol, or an ended asset.
Run curl -x http://LOGIN:PASSWORD@HOST:2222 https://ipinfo.io/json with the values from My assets. If curl works and Python does not, check the proxies dictionary and the environment proxy variables, which can override session.proxies.
Without a timeout a request never gives up. Use a tuple such as timeout=(3.05, 20): connect and read are set separately.
For plain status codes mount a Retry adapter (first code block). To also handle ProxyError and the 407 stop rule, use the loop in the second code block.
Three or four attempts are enough for a temporary fault. After that, stop and record the last error text so you can see the real cause.
import requests
from requests.adapters import HTTPAdapter
from urllib3.util import Retry
PROXY = "http://LOGIN:PASSWORD@HOST:2222"
PROXIES = {"http": PROXY, "https": PROXY}
retries = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 502, 503, 504],
allowed_methods={"GET"},
)
session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=retries))
session.mount("http://", HTTPAdapter(max_retries=retries))
response = session.get("https://ipinfo.io/json", proxies=PROXIES, timeout=(3.05, 20))
print(response.status_code, response.text)Example only: replace LOGIN, PASSWORD and HOST with the values from My assets; percent-encode special characters such as @ in the login. The sleep between retries is backoff_factor * 2 ** previous retries.
import random
import time
import requests
PROXY = "http://LOGIN:PASSWORD@HOST:2222"
PROXIES = {"http": PROXY, "https": PROXY}
RETRY_STATUS = {429, 502, 503, 504}
MAX_ATTEMPTS = 4
def fetch(url):
last_error = None
for attempt in range(1, MAX_ATTEMPTS + 1):
delay = min(2 ** attempt, 30) # 2, 4, 8, 16 seconds
try:
# a new Session opens a new connection, so a rotating line can give a new IP
with requests.Session() as session:
response = session.get(url, proxies=PROXIES, timeout=(3.05, 20))
if response.status_code not in RETRY_STATUS:
return response
retry_after = response.headers.get("Retry-After", "")
if retry_after.isdigit():
delay = min(int(retry_after), 60) # the server said how long to wait
last_error = RuntimeError(f"HTTP {response.status_code}")
except requests.exceptions.ProxyError as exc:
if "407" in str(exc):
raise # wrong login: retrying will not help
last_error = exc
except (requests.exceptions.ConnectTimeout, requests.exceptions.ReadTimeout) as exc:
last_error = exc
if attempt < MAX_ATTEMPTS:
time.sleep(delay + random.uniform(0, 1))
raise RuntimeError(f"gave up after {MAX_ATTEMPTS} attempts: {last_error}")
if __name__ == "__main__":
print(fetch("https://ipinfo.io/json").text)Example only, with placeholder login. Each attempt opens a new Session, and therefore a new connection; on a rotating line a new connection gets a new IP. The 407 check reads the error text, which our local test confirmed on requests 2.32.5.
On rotating residential every new connection gets a new IP, but a requests.Session keeps its connection alive, so many calls on one Session usually share one IP. That is why the loop above opens a fresh Session per attempt. Static ISP and datacenter IPs do not rotate during the lease, so a retry through them uses the same IP; they cost from US$0.25 (datacenter) and US$0.50 (static ISP) for 1 day.
Keep the number of attempts small. On per-GB lines every attempt that reaches the site uses traffic. There is no free trial; the smallest purchase is 1 GB of rotating residential traffic or one IP for one day.
A connection problem with the proxy: it is a subclass of ConnectionError. The end of the message says whether the login was refused (407) or the connection could not be opened.
No. The documentation says Requests does not retry failed connections by default. You add retries with urllib3.util.Retry through HTTPAdapter.
Not by default. allowed_methods defaults to idempotent methods: DELETE, GET, HEAD, OPTIONS, PUT and TRACE. Add POST only if repeating it is safe.
The wait grows by doubling with every retry, up to backoff_max (120 seconds by default). The urllib3 docs show 0.0, 0.2, 0.4 and 0.8 seconds for a factor of 0.1, so the first retry comes with no wait. The Retry-After header is honoured by default for 413, 429 and 503.
An occasional connection failure is normal enough to retry two or three times. If it appears on every call, look for a fixed cause such as the login, the port, an ended asset or an environment proxy variable.
Official documentation · Confirmed on:
Read on 13 November 2026: ProxyError subclasses ConnectionError; ConnectTimeout subclasses ConnectionError and Timeout and its docstring says requests that produced this error are safe to retry; ReadTimeout subclasses Timeout only; RetryError is "Custom retries logic failed".
Official documentation · Confirmed on:
Read on 13 November 2026: "By default, Requests does not retry failed connections"; retries use urllib3.util.Retry mounted through HTTPAdapter(max_retries=...); by default requests do not time out unless a timeout is set, and a tuple sets connect and read separately; a proxy URL can be written as http://user:password@host; session.proxies may be overridden by environment proxies; keep-alive is automatic within a session.
Official documentation · Confirmed on:
Read on 13 November 2026: backoff_factor is off by default (0); sleep is backoff_factor * (2 ** number of previous retries) up to backoff_max (default 120 s); status_forcelist is off by default; allowed_methods defaults to the idempotent methods DELETE, GET, HEAD, OPTIONS, PUT, TRACE; Retry-After is honoured by default for 413, 429 and 503; raise_on_status controls whether exhausted status retries raise.
Official documentation · Confirmed on:
Read on 13 November 2026: 429 means the client sent too many requests in a given time; a Retry-After header may say how long to wait before the next request.
Local example · Confirmed on:
Ran on 13 November 2026 with requests 2.32.5 and urllib3 2.6.3 against a local test server acting as a proxy: a CONNECT refused with 407 raised ProxyError ending in "Tunnel connection failed: 407 Proxy Authentication Required"; a closed port raised ProxyError ending in "Connection refused"; a proxy that always answered 503 made Retry raise RetryError; the fetch() function on this page raised at once on the 407 and gave up after the set attempts on 503.
Product observation · Confirmed on:
Product facts file of the site (prices in US$, 1 PTC = US$1, protocols and ports, lease terms; protocol tests of 4 November 2026 and the rotating datacenter line of 10 November 2026). Checked on 13 November 2026.
Create an account, top up once, then pick rotating residential, static ISP or datacenter IPs and have one running in your browser within minutes.