Geonode logo
Geonode Team

Geonode Team

Atualizado: 16 de outubro de 2026

Publicado: 16 de outubro de 2026

Configuração do proxy aiohttp: sessões, autenticação e limites de ligação

O aiohttp é o cliente a escolher quando um scraper necessita de centenas de pedidos em curso e se pretende ter controlo total sobre o ciclo de eventos. Encaminhá-lo através de um proxy requer apenas alguns argumentos-chave, mas os valores predefinidos relativos à autenticação, limites de ligação e tempos de espera são diferentes dos do `requests` e do httpx, e é aí que a maioria das configurações de proxy do aiohttp falha. No final deste guia, terá um `ClientSession` que se autentica num gateway residencial rotativo, executa pedidos paralelos limitados, atinge o tempo limite de forma previsível e reporta erros de proxy por tipo. Os exemplos destinam-se ao aiohttp 3.14 no Python 3.11 ou mais recente. Nos casos em que o comportamento mudou numa versão recente, a versão é indicada.

O aiohttp é o cliente a escolher quando um scraper necessita de centenas de pedidos em curso e se pretende ter controlo total sobre o ciclo de eventos. Encaminhá-lo através de um proxy requer apenas alguns argumentos-chave, mas os valores predefinidos relativos à autenticação, limites de ligação e tempos de espera são diferentes dos do requests e do httpx, e é aí que a maioria das configurações de proxy do aiohttp falha. No final deste guia, terá um ClientSession que se autentica num gateway residencial rotativo, executa pedidos paralelos limitados, atinge o tempo limite de forma previsível e reporta erros de proxy por tipo.

Os exemplos destinam-se ao aiohttp 3.14 no Python 3.11 ou mais recente. Nos casos em que o comportamento mudou numa versão recente, a versão é indicada.

Configurar um proxy aiohttp na sessão

A palavra-chave proxy é aceite tanto por pedido como ao nível da sessão. A configuração «por pedido» corresponde à API original e continua a funcionar; a configuração ao nível da sessão é a que se deve utilizar para um scraper, pois elimina a possibilidade de uma única chamada escapar ao proxy.

import asyncio
import aiohttp

PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"

async def main() -> None:
    async with aiohttp.ClientSession(proxy=PROXY) as session:
        async with session.get("https://api.ipify.org?format=json") as resp:
            print(resp.status, await resp.json())

asyncio.run(main())

A porta 9000 é o gateway rotativo HTTP(S) do Geonode, pelo que cada nova ligação é estabelecida a partir de um endereço IP residencial diferente. Uma chamada individual ainda pode substituir o valor predefinido com o seu próprio argumento proxy=, o que é útil quando uma solicitação precisa de ser direta, por exemplo, para um ponto final de métricas interno.

Formato por pedido

async with aiohttp.ClientSession() as session:
    async with session.get("https://api.ipify.org?format=json", proxy=PROXY) as resp:
        print(await resp.json())

Utilize isto quando o proxy varia de pedido para pedido, como quando um gestor de pool atribui uma sessão fixa diferente por tarefa.

Autenticação: credenciais de URL ou proxy_headers

Existem duas formas suportadas de enviar credenciais de proxy e uma forma obsoleta que ainda poderá encontrar em tutoriais mais antigos.

MétodoExemploEstado
Credenciais na URL do proxyproxy="http://user:pass@host:9000"Suportado
proxy_headers com encode_basic_authproxy_headers={"Proxy-Authorization": aiohttp.encode_basic_auth("user", "pass")}Suportado, recomendado
proxy_auth=aiohttp.BasicAuth("user", "pass")Obsoleta desde a versão 3.14, removida na versão 4.0

A forma de URL é a mais curta. A forma de cabeçalho mantém o segredo fora da URL, o que é importante se as URLs do proxy acabarem por aparecer em registos ou mensagens de exceção. aiohttp.encode_basic_auth devolve a cadeia de caracteres Basic ... já pronta:

import asyncio
import os
import aiohttp

PROXY = "http://premium-residential.geonode.com:9000"

async def main() -> None:
    proxy_headers = {
        "Proxy-Authorization": aiohttp.encode_basic_auth(
            os.environ["GEONODE_USER"], os.environ["GEONODE_PASS"]
        )
    }
    async with aiohttp.ClientSession(proxy=PROXY, proxy_headers=proxy_headers) as session:
        async with session.get("https://api.ipify.org?format=json") as resp:
            print(await resp.json())

asyncio.run(main())

Se a sua base de código ainda utiliza proxy_auth=aiohttp.BasicAuth(...), esta funciona na versão 3.14 com um DeprecationWarning. Faça a migração agora: o parâmetro e a classe serão removidos no aiohttp 4.0, e a substituição consiste numa alteração de uma única linha.

Pedidos paralelos com asyncio.gather e um Semaphore

A razão para utilizar o aiohttp é a concorrência, e a razão pela qual a concorrência causa falhas nos proxies é que, por predefinição, não há nada que a limite. Um asyncio.gather com mais de mil corrotinas tentará abrir mil ligações; o conector limita esse número a 100, e o restante fica em fila no aiohttp, o que é melhor do que nada, mas não lhe dá qualquer controlo sobre o ritmo. Um Semaphore torna o limite explícito e permite ajustá-lo independentemente do conector.

import asyncio
import aiohttp

PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"
CONCURRENCY = 20

async def fetch(session: aiohttp.ClientSession, sem: asyncio.Semaphore, url: str) -> tuple[str, int]:
    async with sem:
        async with session.get(url) as resp:
            await resp.read()
            return url, resp.status

async def main() -> None:
    urls = [f"https://httpbin.org/get?i={i}" for i in range(100)]
    sem = asyncio.Semaphore(CONCURRENCY)
    connector = aiohttp.TCPConnector(limit=CONCURRENCY, ttl_dns_cache=300)
    timeout = aiohttp.ClientTimeout(total=30, connect=10, sock_read=20)
    async with aiohttp.ClientSession(
        proxy=PROXY, connector=connector, timeout=timeout
    ) as session:
        results = await asyncio.gather(
            *(fetch(session, sem, u) for u in urls), return_exceptions=True
        )
    ok = sum(1 for r in results if not isinstance(r, Exception))
    print(f"{ok}/{len(urls)} succeeded")

asyncio.run(main())

O return_exceptions=True evita que um pedido falhado cancele o lote. Faça corresponder o semáforo ao limite do conector, para que nenhum dos lados coloque silenciosamente na fila tarefas que o outro não consiga processar.

TCPConnector: limit e limit_per_host

O conector é responsável pelo conjunto de sockets. Dois parâmetros são importantes para o tráfego do proxy aiohttp.

limit é o número total de ligações simultâneas, com o valor predefinido de 100. O limit_per_host limita as ligações a um triplo (host, port, is_ssl); o valor predefinido é 0, o que significa ilimitado. Através de um proxy, todos os pedidos para todos os destinos partilham o anfitrião e a porta do proxy, pelo que o limite por anfitrião se aplica ao gateway e não aos sites de destino. Para uma carga de trabalho exclusivamente de proxy, defina limit para a simultaneidade pretendida e deixe limit_per_host em 0; defina limit_per_host apenas quando a mesma sessão também comunicar diretamente com as origens e pretender protegê-las.

connector = aiohttp.TCPConnector(limit=30, limit_per_host=10, ttl_dns_cache=300)

O ttl_dns_cache=300 mantém a resposta DNS do gateway durante cinco minutos, em vez dos dez segundos predefinidos, o que elimina uma ida e volta do resolvedor na maioria das novas ligações. A sessão assume a posse do conector e encerra-o ao sair; defina o connector_owner=False apenas se pretender deliberadamente partilhar um conector entre sessões.

ClientTimeout em vez do valor predefinido de 5 minutos

O tempo limite total predefinido do aiohttp é de 300 segundos. Esse valor é generoso para pedidos diretos, mas demasiado longo para o tráfego via proxy, onde uma saída residencial bloqueada deve falhar rapidamente para que se possa tentar novamente com outro IP.

timeout = aiohttp.ClientTimeout(total=30, connect=10, sock_read=20)

O total limita toda a solicitação, incluindo redirecionamentos e o download do corpo da resposta. O connect limita a aquisição de uma ligação a partir do conjunto, além do handshake TCP e do proxy. O sock_read limita o intervalo entre bytes consecutivos, o que deteta alvos que aceitam a ligação e, em seguida, enviam dados aos poucos. Passe o objeto para a sessão como valor predefinido ou para uma solicitação única para o substituir. A expiração gera o asyncio.TimeoutError, discutido abaixo.

trust_env e proxies de ambiente

Ao contrário do requests, o aiohttp ignora o HTTP_PROXY, o HTTPS_PROXY e o NO_PROXY, a menos que se opte por ativá-los com o trust_env=True. Quando ativado, utiliza o urllib.request.getproxies(), respeita o no_proxy e também lê as credenciais de proxy do ~/.netrc, caso estejam presentes.

async with aiohttp.ClientSession(trust_env=True) as session:
    async with session.get("https://api.ipify.org?format=json") as resp:
        print(await resp.json())

Para um scraper que tenha de utilizar sempre um gateway específico, desative o trust_env e defina explicitamente o proxy=. Dessa forma, um proxy corporativo num executor CI não pode desviar o encaminhamento, e o no_proxy não pode isentar discretamente um alvo.

SOCKS5 com aiohttp-socks

O aiohttp não tem suporte integrado para SOCKS, pelo que um proxy aiohttp através do SOCKS5 necessita de um conector de terceiros. O pacote aiohttp-socks fornece um ProxyConnector que substitui o TCPConnector e gere o handshake SOCKS. Instale-o com o pip install aiohttp-socks. A porta rotativa do SOCKS5 do Geonode é 11000:

import asyncio
import aiohttp
from aiohttp_socks import ProxyConnector

SOCKS_URL = "socks5://USERNAME:PASSWORD@premium-residential.geonode.com:11000"

async def main() -> None:
    connector = ProxyConnector.from_url(SOCKS_URL)
    async with aiohttp.ClientSession(connector=connector) as session:
        async with session.get("https://api.ipify.org?format=json") as resp:
            print(await resp.json())

asyncio.run(main())

Não combine o ProxyConnector com o proxy= na sessão; o conector já encaminha tudo. O ProxyConnector aceita as mesmas palavras-chave limit e limit_per_host que o TCPConnector.

A limitação do TLS-in-TLS

O aiohttp comunica com proxies HTTP e estabelece túneis para os destinos https:// através deles utilizando o comando CONNECT. O que não faz de forma fiável é ligar-se ao próprio proxy através de TLS, ou seja, a um URL de proxy do tipo https://, porque o TLS-in-TLS depende do suporte ao transporte asyncio, que tem variado ao longo das versões do Python. A documentação é explícita ao indicar que isto não deve ser dado como certo. Na prática, isto não tem importância para o Geonode: o gateway é acedido através de HTTP simples nas portas 9000/10000 e o seu tráfego https:// continua a ser encriptado de ponta a ponta dentro do túnel CONNECT; por isso, mantenha o esquema de URL do proxy como http://.

Sessões fixas para fluxos com várias etapas

A porta rotativa atribui um novo endereço IP por ligação. Para fluxos de início de sessão ou listagens paginadas, é necessário manter o mesmo endereço IP durante alguns minutos, o que o Geonode disponibiliza na porta fixa HTTP(S) 10000 através do nome de utilizador:

import secrets
import aiohttp

def sticky_proxy(country: str = "us", lifetime: int = 600) -> str:
    session_id = secrets.token_hex(4)  # 8 alphanumeric characters
    user = f"USERNAME-country-{country}-session-{session_id}-lifetime-{lifetime}"
    return f"http://{user}:PASSWORD@premium-residential.geonode.com:10000"

session = aiohttp.ClientSession(proxy=sticky_proxy())

O tempo de vida varia entre 180 e 86 400 segundos. A segmentação por estado e cidade não pode ser combinada, e o asn requer a indicação de um país. A geração de um novo ID de sessão permite a rotação sob demanda.

Diagnóstico: o que significa cada exceção

ExceçãoSignificadoSolução típica
aiohttp.ClientProxyConnectionErrorNão foi possível abrir uma ligação TCP ao anfitrião proxyVerifique o nome do anfitrião, a porta e a firewall; aumente o tempo de espera da connect
aiohttp.ClientHttpProxyError com .status == 407O proxy rejeitou as credenciais durante o CONNECTCorrija o nome de utilizador ou a palavra-passe; verifique a sintaxe da opção do nome de utilizador
aiohttp.ClientHttpProxyError com 502/503O gateway não conseguiu estabelecer o túnelRepita a tentativa com um novo ID de sessão; confirme se a porta corresponde ao protocolo
asyncio.TimeoutErrorUm campo do ClientTimeout expirouTente novamente com um IP novo; ajuste o sock_read
aiohttp.ClientConnectorErrorFalha genérica de ligação (DNS, recusada)Verifique o .os_error

O ClientProxyConnectionError é uma subclasse do ClientConnectorError e o ClientHttpProxyError é uma subclasse do ClientResponseError; por isso, detete primeiro os casos específicos.

import asyncio
import aiohttp

PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"

async def fetch_ip(session: aiohttp.ClientSession) -> str | None:
    try:
        async with session.get("https://api.ipify.org?format=json") as resp:
            resp.raise_for_status()
            return (await resp.json())["ip"]
    except aiohttp.ClientHttpProxyError as e:
        if e.status == 407:
            print("proxy auth failed: check credentials")
        else:
            print("proxy returned", e.status)
    except aiohttp.ClientProxyConnectionError as e:
        print("cannot reach proxy:", e)
    except asyncio.TimeoutError:
        print("timed out through proxy")
    return None

async def main() -> None:
    timeout = aiohttp.ClientTimeout(total=30, connect=10, sock_read=20)
    async with aiohttp.ClientSession(proxy=PROXY, timeout=timeout) as session:
        print(await fetch_ip(session))
    await asyncio.sleep(0.25)  # let SSL transports close before the loop stops

asyncio.run(main())

Note-se que, para um alvo http:// simples, o código 407 do proxy surge como uma resposta normal e não como uma exceção; por isso, verifique também o resp.status nesse caso.

Encerramento suave

Quando uma sessão é encerrada, o asyncio pode ainda estar a desativar os transportes SSL subjacentes. Se o ciclo de eventos parar primeiro, obtém-se o código de erro ResourceWarning: unclosed transport. A correção documentada consiste numa breve pausa após o bloco async with da sessão: await asyncio.sleep(0) para sessões HTTP simples e await asyncio.sleep(0.25) quando a sessão efetuou pedidos HTTPS, o que, através de um túnel CONNECT, acontece quase sempre. O exemplo acima inclui essa correção.

Erros comuns

Criar uma sessão por pedido

O ClientSession contém o conjunto de ligações e o conjunto de cookies. Abrir uma sessão por pedido anula a funcionalidade «keep-alive» para o gateway, multiplica os handshakes do proxy e aciona o aviso de encerramento em cada chamada. Crie uma sessão por trabalhador e passe-a de um para o outro.

Esquecer-se de que o proxy é o único «host»

Com o limit_per_host definido para um valor baixo, cada pedido encaminhado pelo proxy conta para a única entrada (host, port) do gateway; assim, um limite de 5 por anfitrião restringe todo o seu scraper a 5 ligações. Defina o limit em vez disso, ou deixe o limit_per_host em 0.

Confiar no tempo de espera predefinido

Cinco minutos é tempo suficiente para que uma saída residencial bloqueada ocupe um slot de semáforo durante todo o intervalo. Passe sempre um ClientTimeout com valores para connect e sock_read.

Continuar a passar o proxy_auth

Atualmente, funciona com um aviso, mas deixa de funcionar na versão 4.0. Substitua-o pelo proxy_headers={"Proxy-Authorization": aiohttp.encode_basic_auth(user, pw)} ou coloque as credenciais no URL do proxy.

Próximo passo

Com a sessão acima, já autenticou, vinculou e tornou observável o tráfego do proxy aiohttp; o que resta é o conjunto de proxies por onde este passa. Os proxies residenciais Geonode servem os endpoints aqui utilizados: 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 do nome de utilizador e sessões persistentes até 24 horas via HTTP, HTTPS e SOCKS5. Os preços começam em 0,27 $/GB em volume, o plano básico é de 10 GB por 7,92 $/mês e um período de teste de 3 dias custa 5 $, sem contrato. Os casos de utilização de web scraping e análise de preços mostram como as equipas combinam esta configuração do conector com lógicas de repetição de tentativas e agendamento.

Perguntas frequentes

Como configuro um proxy aiohttp para toda uma sessão?

Passe proxy= ao construtor de ClientSession: aiohttp.ClientSession(proxy="http://user:pass@host:port"). Todos os pedidos dessa sessão utilizam-no, a menos que a chamada substitua proxy= por outro valor. O proxy ao nível da sessão está disponível desde o aiohttp 3.11; em versões mais antigas, deve passar proxy= em cada chamada a get() ou post().

Por que razão o proxy_auth está obsoleto no aiohttp e o que o substitui?

Desde o aiohttp 3.14 que o parâmetro proxy_auth e a classe aiohttp.BasicAuth estão obsoletos e com remoção prevista para a versão 4.0. O substituto é um cabeçalho simples: proxy_headers={"Proxy-Authorization": aiohttp.encode_basic_auth("user", "pass")}. Este realiza a mesma codificação Base64 sem uma classe auxiliar e funciona tanto por pedido como ao nível da sessão.

O aiohttp suporta proxies HTTPS ou SOCKS5?

O aiohttp comunica nativamente com proxies HTTP e utiliza CONNECT para encaminhar destinos https:// através deles. A ligação ao próprio proxy através de TLS (TLS-in-TLS) depende do suporte a asyncio na sua compilação do Python e não deve ser considerada fiável. O SOCKS4 e o SOCKS5 requerem o pacote de terceiros aiohttp-socks e o seu ProxyConnector.

Quantas solicitações simultâneas posso executar através de um proxy aiohttp?

O TCPConnector permite 100 ligações simultâneas por predefinição, sem limite por anfitrião. Através de um proxy, todas as solicitações partilham o mesmo anfitrião e porta; por isso, defina explicitamente o limit, adicione o limit_per_host se também efetuar chamadas diretas e envolva as corrotinas de solicitação num asyncio.Semaphore para que as tarefas em fila não abram todas as ligações de rede ao mesmo tempo.

Por que razão a minha solicitação aiohttp através de um proxy fica bloqueada em vez de falhar?

O ClientSession tem um tempo limite total de 5 minutos por predefinição, o que parece um bloqueio quando uma saída residencial fica paralisada. Passe o aiohttp.ClientTimeout(total=30, connect=10, sock_read=20) para a sessão ou para a solicitação, para que os bloqueios sejam rapidamente identificados como asyncio.TimeoutError; em seguida, tente novamente com um ID de sessão novo, em vez de esperar.

Quais são os limites práticos do encaminhamento de scrapers através de proxies residenciais?

Um proxy apenas altera o IP de origem. Não concede permissão para ignorar o ficheiro robots.txt, os termos do site ou os limites de taxa, nem torna a recolha de dados pessoais legal. Mantenha a simultaneidade dentro de limites razoáveis, reduza a frequência em caso de erros 429 e lembre-se de que as sessões «sticky» do Geonode duram, no máximo, 24 horas antes de o IP ser libertado.