Risposta rapida
Come uso i proxy residenziali rotanti in Python?
Passa http://USERNAME:[email protected]:7777 come proxy sia per http che per https in requests, come proxy= in httpx 0.26+ o come proxy= per richiesta in aiohttp. CrawlRelay proxy residenziali ruotano a ogni nuova connessione e rimangono sticky con -session-<id> nello username. Costano 1,50 USD per 1 GB e 0,50 USD/GB da 50 GB. Pagati in crypto, senza KYC.
Configurazione Python a colpo d'occhio
| requests | proxies={"http": url, "https": url}; retries tramite urllib3 Retry |
|---|---|
| httpx | httpx.Client(proxy=url); il vecchio parametro proxies= è stato rimosso in httpx 0.28 |
| aiohttp | session.get(url, proxy="http://USER:PASS@host:port"), credenziali nell'URL del proxy |
| SOCKS5 | pip install "requests[socks]" o httpx[socks], poi socks5h:// sulla porta 7778 |
| Nuovo IP per richiesta | Disabilita keep-alive: Connection: close, max_keepalive_connections=0 o force_close=True |
| Stesso IP per un flusso | -session-<id>-ttl-<minutes> nello username, fino a 30 minuti |
| Schema URL proxy | Sempre http:// per la porta HTTP, anche per siti HTTPS |
Come uso un proxy con requests, con retries che rispettano Retry-After?
Monta un HTTPAdapter con urllib3's Retry su un Session. Riprova su errori di connessione e stati indicati, attende con backoff esponenziale e rispetta l'intestazione Retry-After che i siti inviano con 429 e 503. Imposta trust_env = False così una variabile residua HTTPS_PROXY non può sovrascrivere il tuo proxy.
import os
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
USER = os.environ.get("CRAWLRELAY_USER", "USERNAME-country-us")
PASSWORD = os.environ.get("CRAWLRELAY_PASSWORD", "PASSWORD")
PROXY = "http://%s:%[email protected]:7777" % (USER, PASSWORD)
retry = Retry(
total=4,
connect=2,
backoff_factor=1.0, # waits of 0 s, 2 s, 4 s, 8 s (Retry-After wins on 429/503)
status_forcelist=(429, 502, 503, 504),
allowed_methods=frozenset({"GET", "HEAD"}),
respect_retry_after_header=True,
)
session = requests.Session()
session.trust_env = False # ignore HTTP(S)_PROXY from the environment
session.mount("http://", HTTPAdapter(max_retries=retry))
session.mount("https://", HTTPAdapter(max_retries=retry))
session.proxies = {"http": PROXY, "https": PROXY}
session.headers["User-Agent"] = "ExampleCrawler/1.0 (+https://example.com/crawler-info)"
session.headers["Connection"] = "close" # new connection per request, so usually a new exit IP
r = session.get("https://api.ipify.org", timeout=(10, 30))
print(r.status_code, r.text)Come costruisco in sicurezza URL proxy mirati?
Genera lo username in una funzione e convalidalo. Un errore di digitazione nel codice paese o un carattere non codificato nella password produce un 407 che sembra un problema di credenziali. L’helper qui sotto mantiene l’ordine documentato (USERNAME-country-{cc}-state-{state}-city-{city}-zip-{zip}-asn-{asn}-session-{id}-ttl-{minutes}), rifiuta chiavi sconosciute e codifica URL entrambe le credenziali.
import re
from urllib.parse import quote
ORDER = ("country", "state", "city", "zip", "asn", "session", "ttl")
VALUE = re.compile(r"^[a-z0-9_]+$")
def build_username(base, **params):
"""build_username("USERNAME", country="us", city="new_york", session="job1", ttl=30)"""
parts = [base]
for key in ORDER:
value = params.pop(key, None)
if value is None:
continue
value = str(value).lower()
if not VALUE.match(value):
raise ValueError("bad value for %s: %r" % (key, value))
if key == "ttl" and not 1 <= int(value) <= 30:
raise ValueError("ttl must be 1 to 30 minutes")
parts.append("%s-%s" % (key, value))
if params:
raise ValueError("unknown parameters: %s" % ", ".join(sorted(params)))
return "-".join(parts)
def proxy_url(username, password, socks=False):
scheme, port = ("socks5h", 7778) if socks else ("http", 7777)
return "%s://%s:%[email protected]:%d" % (
scheme, quote(username, safe=""), quote(password, safe=""), port)
print(proxy_url(build_username("USERNAME", country="us", state="ny", city="new_york", session="job1", ttl=20), "p@ss:word"))Come ottengo un nuovo IP a ogni richiesta con httpx?
Disattiva il keep-alive. httpx mantiene le connessioni in pool di default, e una connessione riutilizzata conserva l’IP di uscita. Con max_keepalive_connections=0, ogni richiesta apre una nuova connessione al gateway e solitamente esce con un IP residenziale diverso. num_bytes_downloaded fornisce i byte ricevuti in trasmissione, che è la misura più vicina alla fatturazione del traffico.
import httpx
PROXY = "http://USERNAME-country-gb:[email protected]:7777"
# No keep-alive: every request opens a new connection, so it usually gets a new exit IP
with httpx.Client(
proxy=PROXY,
timeout=httpx.Timeout(30.0, connect=10.0),
limits=httpx.Limits(max_keepalive_connections=0),
follow_redirects=True,
) as client:
for _ in range(3):
r = client.get("https://api.ipify.org")
print(r.text, r.num_bytes_downloaded, "bytes on the wire")Come faccio scraping concorrente con asyncio senza sovraccaricare un sito?
Limita la concorrenza per host, non solo globalmente. Un limite globale di 50 permette a tutte le 50 richieste di colpire lo stesso sito se la lista URL è ordinata per dominio. Un semaforo per hostname mantiene ogni sito a un livello cortese mentre la crawl nel complesso resta veloce. Parti da 2 a 4 per host e leggi la guida alla concorrenza prima di aumentarla.
import asyncio
from collections import defaultdict
from urllib.parse import urlsplit
import httpx
PROXY = "http://USERNAME-country-us:[email protected]:7777"
PER_HOST = 4 # parallel requests per target host
host_slots = defaultdict(lambda: asyncio.Semaphore(PER_HOST))
async def fetch(client, url):
async with host_slots[urlsplit(url).hostname]:
try:
r = await client.get(url)
return url, r.status_code, r.num_bytes_downloaded
except httpx.HTTPError as exc:
return url, type(exc).__name__, 0
async def main(urls):
limits = httpx.Limits(max_connections=50, max_keepalive_connections=0)
timeout = httpx.Timeout(30.0, connect=10.0)
async with httpx.AsyncClient(proxy=PROXY, limits=limits, timeout=timeout) as client:
for result in await asyncio.gather(*(fetch(client, u) for u in urls)):
print(*result)
asyncio.run(main(["https://example.com/", "https://example.org/", "https://example.net/"]))Come uso un proxy con aiohttp?
Passa il gateway come proxy= in ogni richiesta, con credenziali nell’URL proxy. Funziona con ogni release aiohttp 3.x senza avvisi; BasicAuth e il parametro proxy_auth= sono deprecati da aiohttp 3.14 e verranno rimossi in 4.0. Un TCPConnector(force_close=True) chiude ogni connessione dopo la risposta, quindi ogni richiesta ottiene di solito un nuovo IP di uscita. Senza di esso, aiohttp riutilizza le connessioni e mantieni lo stesso IP per un po’.
import asyncio
import aiohttp
# Credentials go in the proxy URL; percent-encode a password that contains @ or :
PROXY = "http://USERNAME-country-fr:[email protected]:7777"
async def main():
# force_close=True: one connection per request, so each request usually gets a new exit IP
connector = aiohttp.TCPConnector(limit=20, force_close=True)
timeout = aiohttp.ClientTimeout(total=60, sock_connect=10)
async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
for _ in range(3):
async with session.get("https://api.ipify.org", proxy=PROXY) as resp:
print(resp.status, await resp.text())
asyncio.run(main())Come mantengo un IP per un flusso a più step?
Genera un ID sessione per flusso e inseriscilo nello username con TTL più lungo del flusso. Keep-alive va bene qui: vuoi lo stesso IP di uscita. Se il dispositivo residenziale dietro la sessione va offline, l’IP può cambiare prima della scadenza del TTL, quindi rendi il flusso riavviabile dal checkpoint più recente.
import uuid
import requests
session_id = uuid.uuid4().hex[:10] # one ID per flow, lowercase letters and digits
user = "USERNAME-country-us-state-ca-session-%s-ttl-20" % session_id
proxy = "http://%s:[email protected]:7777" % user
with requests.Session() as s:
s.proxies = {"http": proxy, "https": proxy}
first = s.get("https://api.ipify.org", timeout=(10, 30)).text
second = s.get("https://api.ipify.org", timeout=(10, 30)).text
print(first, second, "same exit" if first == second else "exit changed")Quanto traffico consuma uno scraper Python per pagina?
Misuralo su 100 pagine reali prima di acquistare volume. Logga num_bytes_downloaded in httpx, oppure la lunghezza del corpo compresso nel tuo middleware, e moltiplica per il numero di pagine. Richieste solo HTML solitamente costano decine di kilobyte per pagina; la guida al traffico fornisce i numeri e il costo per 1.000 pagine ai prezzi CrawlRelay.
Tre abitudini riducono il traffico senza cambiare i risultati:
- Invia
Accept-Encoding: gzip, br(httpx e requests inviano gzip di default) così le pagine viaggiano compresse. - Recupera l’endpoint JSON che una pagina carica, se il sito lo documenta o permette, invece dell’HTML completo.
- Salta le pagine non cambiate con
If-None-MatchoIf-Modified-Since: un304 Not Modifiedcosta poche centinaia di byte.
Python e proxy: domande
requests, httpx o aiohttp: quale dovrei utilizzare per scraping?
Usa requests per script semplici e piccoli crawl. Usa httpx se vuoi la stessa API per codice sincrono e asincrono, e HTTP/2 tramite httpx[http2]. Usa aiohttp per una concorrenza molto alta in puro asyncio. Per un crawl con scheduling, deduplicazione e pipeline, usa Scrapy invece di scrivere quelle parti da solo.
Perché ottengo SSL: WRONG_VERSION_NUMBER con il mio proxy in Python?
Probabilmente hai scritto https:// davanti all'indirizzo del proxy. La porta del gateway 7777 usa HTTP semplice e apre un tunnel CONNECT per siti HTTPS, quindi l'URL del proxy deve iniziare con http:// sia nelle chiavi http che https. Vedi la pagina di errore.
Come uso i proxy SOCKS5 in Python?
Installa l'extra (pip install "requests[socks]" o pip install "httpx[socks]"), poi usa socks5h://USERNAME:[email protected]:7778. Il h invia i nomi host al proxy, quindi il DNS viene risolto all'uscita e ottieni le stesse risposte regionali dei visitatori locali.
Perché il mio proxy rotante restituisce due volte lo stesso IP?
Il tuo client ha riutilizzato una connessione keep-alive, e la rotazione avviene ad ogni nuova connessione. Invia Connection: close in requests, imposta max_keepalive_connections=0 in httpx, oppure usa force_close=True in aiohttp. Controlla anche che lo username non contenga la parte session-, che mantiene intenzionalmente un IP fisso.
Come imposto i timeout per le richieste proxate?
Impostali esplicitamente: requests non ha timeout di default. Un connect timeout di circa 10 secondi e un read timeout di 30 secondi vanno bene per pagine HTML; le uscite mobili e i target lenti richiedono 45-60 secondi. Riprova un timeout una volta su una nuova connessione invece di aspettare più a lungo. Vedi i timeout di lettura.
Posso usare questi proxy in Jupyter o con pandas?
Sì. Qualsiasi cosa che usa requests o httpx sotto funziona con le stesse impostazioni proxy. Per pandas.read_html o read_csv su un URL, scarica prima il contenuto con una chiamata requests proxyata, poi passa testo o bytes a pandas.
Esegui il tuo scraper Python tramite proxy residenziali
1,50 USD per 1 GB, 0,50 USD/GB da 50 GB.