O httpx é o cliente HTTP a que a maioria dos novos projetos em Python recorre quando ultrapassam as capacidades do requests, principalmente porque disponibiliza uma única API para código síncrono e assíncrono. Este guia mostra como integrar um proxy httpx tanto no Client como no AsyncClient, autenticar-se num ponto de extremidade residencial rotativo, fixar uma sessão persistente, adicionar o SOCKS5, ajustar os tempos de espera e as tentativas de repetição, e diagnosticar os três erros que irá realmente encontrar. No final, terá um cliente reutilizável que encaminha o tráfego através de um proxy e consegue comprovar o seu IP de saída.
Os exemplos destinam-se ao httpx 0.27 ou versões mais recentes. Se estiver a utilizar uma versão mais antiga, tenha em atenção que a palavra-chave proxies= foi renomeada para proxy= e mounts= no httpx 0.26; tudo o que se segue utiliza os nomes atuais.
Duas formas de configurar um proxy httpx: proxy e mounts
O httpx oferece uma opção simples e outra precisa. O proxy= aceita um único URL e encaminha todas as solicitações através dele. O mounts= mapeia padrões de URL para transportes, e cada transporte pode ter o seu próprio proxy, contagem de tentativas e limites de ligação.
| Opção | Âmbito | Quando utilizar |
|---|
proxy="http://..." | Todo o cliente, todos os esquemas | Um proxy para tudo, o que é o caso habitual com um gateway rotativo |
mounts={"https://": HTTPTransport(proxy=...)} | Por padrão de URL | Proxies diferentes por esquema ou anfitrião, ou contornar o proxy para serviços internos |
httpx.get(url, proxy=...) | Chamada única de nível superior | Verificações rápidas e scripts sem um cliente de longa duração |
A forma simples tem o seguinte aspeto:
import httpx
PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"
with httpx.Client(proxy=PROXY, timeout=20.0) as client:
r = client.get("https://api.ipify.org?format=json")
print(r.status_code, r.json())
A porta 9000 é o gateway rotativo HTTP(S), pelo que cada nova ligação recebe um IP residencial diferente. As credenciais constam do URL como autenticação básica, e o httpx transforma-as num cabeçalho Proxy-Authorization sem qualquer código adicional.
A armadilha do encaminhamento por esquema
Com mounts, é possível atribuir transportes separados aos destinos http:// e https://. A armadilha reside no próprio esquema da URL do proxy. Para um destino https://, o httpx envia CONNECT para o proxy e, em seguida, negocia o TLS com a origem dentro desse túnel. O salto para o proxy é em HTTP simples, pelo que o URL do proxy na montagem https:// deve, normalmente, começar por http://, e não por https://. Escrever https:// nesse local indica ao httpx para envolver o próprio salto do proxy em TLS, o que a maioria dos gateways não suporta, e obtém-se uma falha de handshake que parece não estar relacionada com proxies.
import httpx
PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"
mounts = {
"http://": httpx.HTTPTransport(proxy=PROXY),
"https://": httpx.HTTPTransport(proxy=PROXY), # http:// scheme is correct here
# internal services stay direct: mount a transport without a proxy
"all://*.internal.example": httpx.HTTPTransport(),
}
with httpx.Client(mounts=mounts) as client:
print(client.get("https://api.ipify.org?format=json").json())
Os padrões mais específicos prevalecem sobre os gerais, pelo que a montagem all://*.internal.example contorna o proxy para esse domínio, enquanto tudo o resto é encaminhado.
Autenticação na URL
As credenciais Geonode consistem num nome de utilizador e numa palavra-passe em texto simples, pelo que o formulário userinfo http://user:pass@host:port é tudo o que precisa. Duas notas práticas. Em primeiro lugar, se a palavra-passe contiver caracteres como @, : ou /, codifique-a com o código de percentagem urllib.parse.quote antes de construir a URL. Segundo, mantenha as informações confidenciais fora do controlo de código-fonte, lendo-as a partir do ambiente:
import os
from urllib.parse import quote
import httpx
user = os.environ["GEONODE_USER"]
password = quote(os.environ["GEONODE_PASS"], safe="")
proxy = f"http://{user}:{password}@premium-residential.geonode.com:9000"
client = httpx.Client(proxy=proxy)
Tudo o que se segue adiciona opções de segmentação e de sessão, estendendo o nome de utilizador, e não a URL, pelo que o mesmo padrão continua a funcionar.
Sincronização entre Client e AsyncClient
O cliente assíncrono utiliza a mesma palavra-chave proxy=. A única diferença surge quando se passa para mounts: AsyncClient requer httpx.AsyncHTTPTransport, e não httpx.HTTPTransport. Passar o transporte síncrono para um cliente assíncrono gera um erro na primeira solicitação.
import asyncio
import httpx
PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"
async def fetch(client: httpx.AsyncClient, url: str) -> str:
r = await client.get(url)
r.raise_for_status()
return r.json()["ip"]
async def main() -> None:
transport = httpx.AsyncHTTPTransport(proxy=PROXY, retries=2)
async with httpx.AsyncClient(
mounts={"all://": transport}, timeout=httpx.Timeout(10.0, connect=5.0)
) as client:
urls = ["https://api.ipify.org?format=json"] * 5
ips = await asyncio.gather(*(fetch(client, u) for u in urls))
print(ips)
asyncio.run(main())
Como a porta 9000 alterna por ligação, as cinco solicitações simultâneas costumam apresentar vários endereços IP diferentes. Esse é o comportamento esperado para um conjunto rotativo; se precisar que os endereços coincidam, utilize uma sessão fixa (abaixo).
SOCKS5 a httpx[socks]
O suporte a SOCKS numa configuração de proxy httpx é opcional. Instale-o com o pip install "httpx[socks]", que adiciona o pacote socksio, e, em seguida, altere o esquema. A porta rotativa do SOCKS5 do Geonode é 11000:
import httpx
PROXY = "socks5://USERNAME:PASSWORD@premium-residential.geonode.com:11000"
with httpx.Client(proxy=PROXY) as client:
print(client.get("https://api.ipify.org?format=json").json())
Sem o complemento instalado, o httpx gera um ImportError que menciona o socksio assim que deteta um URL de proxy socks5://. Para a extração de dados comum, as portas HTTP são mais simples de depurar, uma vez que as respostas 407 são legíveis.
Variáveis de ambiente e trust_env
Com o valor predefinido trust_env=True, o httpx lê HTTP_PROXY, HTTPS_PROXY, ALL_PROXY e NO_PROXY e aplica-os automaticamente. Isso é conveniente numa estação de trabalho, mas pode ser uma fonte de confusão em CI, onde um proxy corporativo no ambiente reescreve silenciosamente o seu encaminhamento. Se definir o proxy no código, considere desativar a pesquisa no ambiente para que o comportamento seja idêntico em todos os locais:
import httpx
client = httpx.Client(
proxy="http://USERNAME:PASSWORD@premium-residential.geonode.com:9000",
trust_env=False,
)
Um proxy= explícito já tem precedência sobre o ambiente; o trust_env=False também impede que o httpx leia o SSL_CERT_FILE, por isso defina-o deliberadamente.
Tempos de espera e novas tentativas ao nível do transporte
Um proxy adiciona um salto, pelo que o tempo de ligação aumenta e o tempo limite predefinido de 5 segundos pode, por vezes, ser curto para saídas residenciais. O httpx.Timeout permite definir um orçamento total com um limite de ligação separado, e o HTTPTransport(retries=...) repete as tentativas de ligação falhadas antes mesmo de o pedido ser enviado.
import httpx
PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"
transport = httpx.HTTPTransport(proxy=PROXY, retries=2)
timeout = httpx.Timeout(15.0, connect=8.0)
with httpx.Client(mounts={"all://": transport}, timeout=timeout) as client:
r = client.get("https://api.ipify.org?format=json")
print(r.json())
As tentativas de repetição de transporte abrangem o ConnectError e falhas semelhantes durante o estabelecimento da ligação. Não realizam novas tentativas com base em códigos de estado, nem após o envio de bytes; por isso, um 502 proveniente do gateway ou um 429 do site de destino ainda chega ao seu código. Trate esses casos num pequeno ciclo de controlo próprio, onde também pode decidir se deve alterar o ID da sessão.
Sessões fixas com a gramática de nomes de utilizador Geonode
A rotação a cada ligação não é adequada para fluxos com várias etapas, como o início de sessão, a paginação e o download. Nesses casos, o seu proxy httpx deve manter o mesmo IP de saída em todas as solicitações. O Geonode codifica esta informação no nome de utilizador e serve-a na porta fixa HTTP(S) 10000:
USER[-type-residential|datacenter|mix][-country-xx][-state-name|-city-name][-asn-N][-session-XXXXXXXX][-lifetime-SECONDS]
O ID da sessão é composto por 8 caracteres alfanuméricos gerados por si; o tempo de vida varia entre 180 e 86 400 segundos (24 horas). O estado e a cidade não podem ser combinados, e o asn requer a indicação de um país.
import secrets
import httpx
def sticky_client(country: str = "us", lifetime: int = 600) -> httpx.Client:
session_id = secrets.token_hex(4) # 8 hex characters
username = f"USERNAME-country-{country}-session-{session_id}-lifetime-{lifetime}"
proxy = f"http://{username}:PASSWORD@premium-residential.geonode.com:10000"
return httpx.Client(proxy=proxy, timeout=httpx.Timeout(15.0, connect=8.0))
with sticky_client() as client:
first = client.get("https://api.ipify.org?format=json").json()["ip"]
second = client.get("https://api.ipify.org?format=json").json()["ip"]
print(first == second, first)
Um novo session_id significa um novo IP, pelo que a rotação «manual» consiste simplesmente em criar um novo cliente. Mantenha os tempos de vida das portas fixas tão curtos quanto o trabalho exigir.
Verificar o IP de saída
Todos os exemplos acima acedem ao https://api.ipify.org?format=json, que devolve o {"ip": "..."}, independentemente do endereço a partir do qual foi acedido. Compare isso com uma chamada direta (sem proxy) uma vez e terá um teste rápido de duas linhas que comprova que o proxy está no caminho. Faça isto antes de depurar qualquer outra coisa: muitos relatos do tipo «o proxy httpx não está a funcionar» referem-se a pedidos que nunca utilizaram o proxy porque uma variável de ambiente ou um padrão de montagem assumiram o controlo.
Resolução dos erros 407 ProxyError e ConnectError
Três falhas são responsáveis por quase todos os problemas do proxy httpx. A tabela associa cada uma delas à sua causa e à respetiva solução.
| Sintoma | Exceção | Causa habitual | Solução |
|---|
| Estado da resposta 407 | httpx.ProxyError (gerada durante a configuração do túnel para destinos HTTPS) ou uma resposta 407 simples para destinos HTTP | Credenciais incorretas ou uma opção de nome de utilizador rejeitada pelo gateway | Verifique a sintaxe do nome de utilizador; codifique a palavra-passe em formato percentual |
| Túnel recusado, 502/503 do proxy | httpx.ProxyError | O gateway não conseguiu abrir o túnel ou a porta está incorreta para o protocolo | Confirme 9000/10000 para HTTP(S), 11000/12000 para SOCKS5 |
| Não é possível aceder ao anfitrião do proxy | httpx.ConnectError | DNS, firewall ou nome de anfitrião incorreto | Resolva o anfitrião manualmente; aumente o tempo de espera do connect |
| O pedido fica em espera e depois falha | httpx.ConnectTimeout / httpx.ReadTimeout | Saída residencial lenta ou destino bloqueado | Aumente o tempo de espera, tente novamente com um novo ID de sessão |
import httpx
PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"
def get_via_proxy(url: str) -> httpx.Response | None:
try:
with httpx.Client(proxy=PROXY, timeout=httpx.Timeout(15.0, connect=8.0)) as client:
r = client.get(url)
if r.status_code == 407:
raise RuntimeError("proxy rejected credentials (407)")
return r
except httpx.ProxyError as e:
print("proxy refused tunnel:", e)
except httpx.ConnectError as e:
print("cannot connect to proxy host:", e)
except httpx.TimeoutException as e:
print("timed out:", type(e).__name__)
return None
resp = get_via_proxy("https://api.ipify.org?format=json")
print(resp.json() if resp else "failed")
Todas as exceções httpx herdam do httpx.HTTPError, pelo que um único código genérico except serve para o registo; no entanto, separar o ProxyError do ConnectError permite determinar se deve verificar as credenciais ou a rede.
Erros comuns
Utilização de https:// no URL do proxy
Conforme referido acima, o esquema do URL do proxy descreve o salto até ao proxy. Utilize http:// para as portas HTTP(S), mesmo quando o destino for https://. Reserve https:// para gateways que anunciem explicitamente TLS ao proxy.
Misturar HTTPTransport num AsyncClient
O mounts no AsyncClient deve utilizar o AsyncHTTPTransport. O transporte de sincronização é aceite na fase de construção, mas falha na fase de pedido com um erro pouco esclarecedor; por isso, é fácil não detectar este problema em testes que nunca enviam um pedido.
Esperar um único IP da porta rotativa
A porta 9000 alterna por ligação. Se duas solicitações consecutivas tiverem de partilhar um endereço IP, utilize a porta 10000 com um ID -session- no nome de utilizador. A reutilização de um Client não fixa o endereço IP por si só, uma vez que as ligações no conjunto podem ser substituídas.
Permitir que o ambiente se sobreponha ao seu código
Um HTTPS_PROXY isolado numa imagem do Docker ou num executor de CI irá encaminhar tráfego que pensava ser direto, e o NO_PROXY pode excluir hosts que esperava que fossem encaminhados por proxy. Defina o trust_env=False quando o encaminhamento por proxy tiver de ser determinístico.
Próximo passo
Assim que o cliente acima devolver um IP residencial e uma sessão persistente for mantida entre os pedidos, a variável que resta é o conjunto de proxies por trás dele. Os proxies residenciais do Geonode expõem os pontos de extremidade utilizados neste guia: 2,5 milhões de IPs online, 30 a 50 milhões de IPs únicos em rotação mensal em mais de 195 países, com segmentação por país, estado, cidade e ASN através da sintaxe do nome de utilizador e sessões persistentes até 24 horas. Os preços começam nos 0,27 $/GB para volumes elevados, o plano básico é de 10 GB por 7,92 $/mês e um período de teste de 3 dias custa 5 $. Se estiver a desenvolver um rastreador com base nisto, o caso de utilização de web scraping descreve como combinar proxies com lógica de repetição de tentativas, e os proxies de centros de dados rotativos são a opção mais económica para alvos que não bloqueiam intervalos de endereços de centros de dados.
Perguntas frequentes
Como configuro um proxy httpx para todos os pedidos?
Passe o URL do proxy para o construtor do cliente: httpx.Client(proxy="http://user:pass@host:port"). Todos os pedidos efetuados através desse cliente, tanto para os destinos http:// como para https://, são encaminhados através do proxy. A mesma palavra-chave funciona no httpx.AsyncClient e em auxiliares de nível superior, como o httpx.get(). As credenciais na URL são enviadas automaticamente como Proxy-Authorization.
Qual é a diferença entre proxy e montagens no httpx?
O proxy é um atalho que encaminha tudo através de um único proxy. O mounts mapeia padrões de URL, como o "http://" ou o "https://", para transportes, cada um dos quais pode ter o seu próprio proxy, tentativas de repetição e limites. Utilize mounts quando precisar de proxies diferentes por esquema ou por anfitrião, ou se pretender contornar o proxy para alguns destinos, montando um transporte sem proxy.
Por que razão a minha montagem https:// falha quando a URL do proxy começa por https://?
O esquema na URL do proxy descreve a forma como o httpx comunica com o proxy, e não com o destino. Para destinos https://, o httpx abre um túnel CONNECT através de uma ligação HTTP simples ao proxy; por isso, a URL do proxy deve, normalmente, utilizar http://. A utilização de https:// solicita ao httpx que negocie TLS com o próprio proxy, algo que a maioria dos pontos finais de proxy não suporta.
O httpx suporta proxies SOCKS5?
Sim, mas apenas após a instalação da dependência opcional com pip install httpx[socks], que inclui o socksio. Em seguida, passe o proxy="socks5://user:pass@host:port" exatamente como faria com um proxy HTTP. Com o Geonode, a porta rotativa do SOCKS5 é 11000 e a porta fixa do SOCKS5 é 12000; as portas HTTP(S) não aceitam handshakes SOCKS.
O httpx pode repetir uma solicitação automaticamente quando a ligação ao proxy falha?
Apenas ao nível do transporte. O httpx.HTTPTransport(retries=2) repete tentativas de ligação falhadas, incluindo erros de ligação ao proxy, mas nunca repete uma solicitação que já tenha recebido uma resposta como 407 ou 502. As tentativas de repetição com base em códigos de estado HTTP devem ser feitas no seu próprio ciclo, onde pode alterar o ID da sessão ou recuar deliberadamente.
Existem limites para o que uma configuração de proxy do httpx pode fazer com IPs residenciais?
Um proxy altera o seu IP de saída; não altera o que um site permite. Respeite o ficheiro robots.txt, os termos de serviço e os limites de taxa, mantenha a simultaneidade moderada e evite recolher dados pessoais que não tenha fundamento para processar. As sessões residenciais também têm um limite de 24 horas, após o qual o IP é libertado e tem de abrir uma nova sessão.