Resposta rápida
Como usar proxies residenciais rotativos em Python?
Passe http://USERNAME:[email protected]:7777 como proxy para ambos http e https em requests, como proxy= no httpx 0.26+, ou como proxy= por requisição em aiohttp. CrawlRelay proxies residenciais rotacionam em toda nova conexão e permanecem sticky com -session-<id> no nome de usuário. Custam US$ 1,50 por 1 GB e US$ 0,50/GB a partir de 50 GB. Pagamento em cripto, sem KYC.
Configuração Python em resumo
| requests | proxies={"http": url, "https": url}; tentativas via urllib3 Retry |
|---|---|
| httpx | httpx.Client(proxy=url); o antigo argumento proxies= foi removido no httpx 0.28 |
| aiohttp | session.get(url, proxy="http://USER:PASS@host:port"), credenciais na URL do proxy |
| SOCKS5 | pip install "requests[socks]" ou httpx[socks], depois socks5h:// na porta 7778 |
| IP novo a cada requisição | Desative keep-alive: Connection: close, max_keepalive_connections=0 ou force_close=True |
| Mesmo IP para um fluxo | -session-<id>-ttl-<minutes> no nome de usuário, até 30 minutos |
| Esquema da URL do proxy | Sempre http:// para a porta HTTP, mesmo para sites HTTPS |
Como usar proxy com requests, com tentativas que respeitam Retry-After?
Monte um HTTPAdapter com o Retry do urllib3 Retry sobre um Session. Ele tenta novamente erros de conexão e os status que você listar, espera com backoff exponencial e honra o cabeçalho Retry-After que sites enviam com 429 e 503. Defina trust_env = False para que uma variável HTTPS_PROXY remanescente não sobrescreva seu 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)Como construir URLs de proxy específicas de forma segura?
Gere o nome de usuário em uma função e valide-o. Um erro de digitação no código de país ou um caractere não codificado na senha gera um 407 que parece problema de credenciais. O assistente abaixo mantém a ordem documentada (USERNAME-country-{cc}-state-{state}-city-{city}-zip-{zip}-asn-{asn}-session-{id}-ttl-{minutes}), recusa chaves desconhecidas e codifica em URL ambas as credenciais.
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"))Como obter um novo IP em cada requisição com httpx?
Desative keep-alive. httpx mantém conexões agrupadas por padrão, e uma conexão reusada mantém seu IP de saída. Com max_keepalive_connections=0, cada requisição abre uma nova conexão com o gateway e normalmente sai por um IP residencial diferente. num_bytes_downloaded fornece os bytes recebidos no tráfego, que é o que a cobrança de tráfego mais se aproxima.
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")Como fazer scraping concorrente com asyncio sem sobrecarregar um site?
Controle a concorrência por host, não só globalmente. Um limite global de 50 permite que as 50 requisições acessem o mesmo site quando sua lista de URLs está ordenada por domínio. Um semáforo por hostname mantém cada site num nível educado enquanto o crawl como um todo permanece rápido. Comece com 2 a 4 por host e leia o guia de concorrência antes de aumentar.
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/"]))Como usar proxy com aiohttp?
Passe o gateway como proxy= em cada requisição, com as credenciais na URL do proxy. Isso funciona em todas as versões 3.x do aiohttp sem avisos; BasicAuth e o parâmetro proxy_auth= estão depreciados desde a versão 3.14 e serão removidos no 4.0. Um TCPConnector(force_close=True) fecha cada conexão após a resposta, então cada requisição normalmente recebe um IP de saída novo. Sem isso, aiohttp reutiliza conexões e você mantém o mesmo IP por algum tempo.
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())Como manter um IP para um fluxo com múltiplas etapas?
Gere um ID de sessão por fluxo e coloque-o no nome de usuário com um TTL maior que o fluxo. Keep-alive é ok aqui: você quer o mesmo IP de saída. Se o dispositivo residencial por trás da sessão sair do ar, o IP pode mudar antes do TTL terminar, então torne o fluxo reiniciável do último checkpoint.
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 tráfego um scraper Python usa por página?
Meça em 100 páginas reais antes de comprar volume. Registre num_bytes_downloaded no httpx, ou o tamanho do corpo comprimido no seu middleware, e multiplique pelo número de páginas. Requisições só em HTML costumam custar dezenas de kilobytes por página; o guia de largura de banda mostra números e custo por 1.000 páginas nos preços CrawlRelay.
Três hábitos cortam tráfego sem mudar seu resultado:
- Envie
Accept-Encoding: gzip, br(httpx e requests enviam gzip por padrão) para que as páginas viajem comprimidas. - Busque o endpoint JSON que a página carrega, quando o site documenta ou permite, em vez do HTML completo.
- Ignore páginas inalteradas com
If-None-MatchouIf-Modified-Since: um304 Not Modifiedcusta algumas centenas de bytes.
Python e proxies: perguntas
requests, httpx ou aiohttp: qual devo usar para scraping?
Use requests para scripts simples e crawls pequenos. Use httpx quando quiser a mesma API em código síncrono e assíncrono, e HTTP/2 via httpx[http2]. Use aiohttp para alta concorrência pura com asyncio. Para um crawl com agendamento, deduplicação e pipelines, use Scrapy ao invés de escrever essas partes você mesmo.
Por que recebo SSL: WRONG_VERSION_NUMBER com meu proxy em Python?
Provavelmente você escreveu https:// antes do endereço do proxy. A porta do gateway 7777 fala HTTP simples e abre um túnel CONNECT para sites HTTPS, então a URL do proxy deve começar com http:// tanto na chave http quanto na https. Veja a página de erro.
Como usar proxies SOCKS5 em Python?
Instale o extra (pip install "requests[socks]" ou pip install "httpx[socks]"), depois use socks5h://USERNAME:[email protected]:7778. O h envia nomes de host para o proxy, assim o DNS resolve na saída e você vê as mesmas respostas regionais que visitantes locais.
Por que meu proxy rotativo retorna o mesmo IP duas vezes?
Seu cliente reutilizou uma conexão persistente e a rotação acontece por conexão nova. Envie Connection: close no requests, defina max_keepalive_connections=0 no httpx, ou use force_close=True no aiohttp. Também verifique se o nome de usuário não tem uma parte session-, que mantém intencionalmente um IP fixo.
Como definir timeouts para requisições via proxy?
Defina explicitamente: requests não tem timeout padrão. Um timeout de conexão de cerca de 10 segundos e de leitura de 30 segundos serve para páginas HTML; saídas móveis e alvos lentos precisam de 45 a 60 segundos. Faça uma nova tentativa ao atingir timeout em uma conexão nova ao invés de esperar mais. Veja read timeouts.
Posso usar esses proxies no Jupyter ou com pandas?
Sim. Qualquer coisa que use requests ou httpx por baixo aceita as mesmas configurações de proxy. Para pandas.read_html ou read_csv em uma URL, baixe o conteúdo com uma chamada requests via proxy, depois passe o texto ou bytes para pandas.
Execute seu scraper Python via IPs residenciais
US$ 1,50 por 1 GB, US$ 0,50/GB a partir de 50 GB.