Comparativo entre HTTPX, Requests e AIOHTTP

Publicado:

12 min de leitura

Acar Diveroli
Autor: Acar Diveroli
Blocos de código dispostos em três colunas

Em Python, três bibliotecas se destacam para enviar requisições HTTP: Requests, HTTPX e AIOHTTP. As três fazem o mesmo trabalho básico, mas a diferença entre elas não aparece ao escrever um script pequeno, e sim quando você precisa gerenciar milhares de requisições ao mesmo tempo. Neste artigo comparamos as três bibliotecas em facilidade de uso, concorrência, suporte a proxy, tratamento de erros e ecossistema, damos exemplos funcionais de cada uma e mostramos o caminho para migrar do Requests para o assíncrono.

Os códigos dos exemplos foram testados com Python 3.13, Requests 2.32, HTTPX 0.28 e AIOHTTP 3.13.

As três bibliotecas em poucas palavras

  • Requests: a biblioteca HTTP mais usada em Python. Funciona de forma síncrona, é muito fácil de aprender e tem documentação e exemplos por toda parte. Lembre-se de que cada requisição faz o programa esperar naquela linha até terminar.
  • HTTPX: oferece uma interface muito parecida com a do Requests, tanto de forma síncrona quanto assíncrona. Também tem suporte a HTTP/2. Migrar um código Requests existente para assíncrono geralmente é menos trabalhoso com o HTTPX.
  • AIOHTTP: uma biblioteca desenhada como assíncrona desde o início. Além do cliente, também traz um servidor web embutido. É uma escolha madura e comum para trabalhos que exigem concorrência muito alta.

O que significa síncrono e assíncrono?

Como esse é o conceito central da comparação, vamos explicar rapidamente. Um cliente síncrono envia a requisição e espera até a resposta chegar; nesse meio-tempo, o programa não faz mais nada. Já um cliente assíncrono, depois de enviar a requisição, devolve o controle ao loop de eventos; enquanto espera a resposta, outras requisições podem sair.

A maior parte do tempo de uma requisição web se passa na rede, ou seja, esperando o servidor de destino responder. Em código síncrono, essa espera é desperdiçada; em código assíncrono, dezenas de outras requisições podem ser aguardadas nesse mesmo intervalo. A diferença não se sente ao buscar cem páginas; ao buscar dez mil páginas, ela vira a diferença entre horas e minutos.

O preço dessa vantagem é a complexidade: a sintaxe async/await, o loop de eventos e a incompatibilidade com bibliotecas não assíncronas. Por isso, dizer que "assíncrono é sempre melhor" está errado; sem necessidade real, código síncrono é mais legível e mais fácil de manter.

As diferenças principais em uma tabela

RecursoRequestsHTTPXAIOHTTP
Uso síncronoSimSimNão
Uso assíncronoNãoSimSim
HTTP/2NãoSim (com pacote extra)Não
Proxy (HTTP/HTTPS)NativoNativoNativo
Proxy SOCKSCom requests[socks]Com httpx[socks]Com pacote externo
Tempo limite padrãoNenhum (espera indefinidamente)5 segundos5 minutos (total)
Seguir redirecionamentosAtivado por padrãoDesativado por padrãoAtivado por padrão
Pool de conexõesCom SessionCom ClientCom ClientSession
Curva de aprendizadoMuito fácilFácilMédia
Trabalho mais adequadoScript simples, volume pequenoProjetos mistos, síncrono + assíncronoConcorrência alta

A linha "tempo limite padrão" da tabela é a diferença mais frequentemente esquecida entre as três bibliotecas. No Requests, se você não passar um tempo limite, a requisição pode esperar para sempre; por isso, adicionar timeout= a cada chamada do Requests deveria ser um hábito.

Requests: simples e síncrono

Instalação:

bash
pip install requests

Uma única requisição via proxy:

python
import requests

PROXY = "http://kullanici:parola@pr.proxynet.io:8000"
proxies = {"http": PROXY, "https": PROXY}

response = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=20)
print(response.status_code, response.json())

A chave https no dicionário proxies é o proxy usado ao acessar endereços HTTPS. É correto o valor começar com http://: o tráfego HTTPS é tunelado dentro da conexão estabelecida com o proxy.

Se você vai fazer mais de uma requisição, use Session; as conexões são reaproveitadas, os cookies são preservados e a configuração de proxy é feita uma única vez:

python
import requests

PROXY = "http://kullanici:parola@pr.proxynet.io:8000"

with requests.Session() as session:
    session.proxies.update({"http": PROXY, "https": PROXY})
    session.headers.update({"User-Agent": "bot-de-coleta/1.0"})
    for path in ["ip", "headers", "user-agent"]:
        r = session.get(f"https://httpbin.org/{path}", timeout=20)
        print(path, r.status_code)

O limite do Requests é a concorrência. Se você buscar 1.000 páginas em sequência, o tempo total se aproxima da soma da duração de cada requisição. É possível amenizar isso com concurrent.futures.ThreadPoolExecutor, mas em volumes muito altos uma biblioteca assíncrona funciona de forma mais eficiente.

HTTPX: o meio-termo entre os dois mundos

Instalação:

bash
pip install httpx

O uso síncrono se parece quase ponto a ponto com o Requests:

python
import httpx

PROXY = "http://kullanici:parola@pr.proxynet.io:8000"

with httpx.Client(proxy=PROXY, timeout=20) as client:
    response = client.get("https://httpbin.org/ip")
    print(response.json())

A versão assíncrona do mesmo código envia várias requisições ao mesmo tempo:

python
import asyncio
import httpx

PROXY = "http://kullanici:parola@pr.proxynet.io:8000"
URLS = ["https://httpbin.org/ip"] * 10

async def main():
    async with httpx.AsyncClient(proxy=PROXY, timeout=20) as client:
        responses = await asyncio.gather(*(client.get(url) for url in URLS))
        print([r.status_code for r in responses])

asyncio.run(main())

Aqui, as 10 requisições saem não em sequência, mas ao mesmo tempo. O tempo total se aproxima da duração da requisição mais lenta.

Para usar HTTP/2, basta instalar com pip install "httpx[http2]" e passar http2=True ao cliente. Como a multiplexação acontece em uma única conexão ao enviar muitas requisições para o mesmo servidor, o custo de estabelecer conexão cai. Ao usar proxy, o HTTP/2 funciona dentro do túnel CONNECT, entre você e o site de destino; o proxy não precisa suportar HTTP/2.

AIOHTTP: para concorrência alta

Instalação:

bash
pip install aiohttp
python
import asyncio
import aiohttp

PROXY = "http://pr.proxynet.io:8000"
AUTH = aiohttp.BasicAuth("kullanici", "parola")
URLS = ["https://httpbin.org/ip"] * 10

async def fetch(session, url):
    async with session.get(url, proxy=PROXY, proxy_auth=AUTH) as response:
        return response.status

async def main():
    async with aiohttp.ClientSession() as session:
        statuses = await asyncio.gather(*(fetch(session, url) for url in URLS))
        print(statuses)

asyncio.run(main())

No AIOHTTP, o proxy é passado pelo parâmetro proxy= em cada requisição, não na sessão. Você pode passar as credenciais separadamente com proxy_auth, ou embuti-las no próprio endereço (http://kullanici:parola@...). Esse desenho torna natural passar um proxy diferente em cada requisição; se você quer fazer a rotação da sua própria lista de IPs, o AIOHTTP facilita isso.

Outra diferença do AIOHTTP é que o corpo da resposta precisa ser lido dentro do gerenciador de contexto. Chamar await response.text() depois de sair do bloco async with gera erro; leia o corpo dentro do bloco e guarde em uma variável.

Limitando a concorrência

Ao usar assíncrono, não deixe a concorrência sem limite. Iniciar dez mil URLs de uma vez com asyncio.gather sobrecarrega tanto o limite de descritores de arquivo do seu próprio sistema quanto empurra o site de destino imediatamente para o limite de taxa. Usar asyncio.Semaphore para limitar quantas requisições ficam abertas ao mesmo tempo protege os dois lados:

python
import asyncio
import httpx

PROXY = "http://kullanici:parola@pr.proxynet.io:8000"
URLS = [f"https://httpbin.org/get?i={i}" for i in range(100)]
LIMIT = asyncio.Semaphore(10)

async def fetch(client, url):
    async with LIMIT:
        r = await client.get(url)
        return r.status_code

async def main():
    async with httpx.AsyncClient(proxy=PROXY, timeout=20) as client:
        results = await asyncio.gather(*(fetch(client, u) for u in URLS))
        print(sum(1 for s in results if s == 200), "com sucesso")

asyncio.run(main())

Nesse exemplo, cem requisições entram na fila, mas no máximo dez ficam abertas ao mesmo tempo. Ajuste o limite de acordo com a tolerância do site de destino e a cota de conexões simultâneas do seu pacote de proxy.

Tratamento de erros e nova tentativa

Nas três bibliotecas, erros de rede chegam como exceção; mas códigos de erro HTTP (404, 429, 500) não são exceção por padrão. Verificar a resposta é trabalho seu:

  • No Requests e no HTTPX, a chamada response.raise_for_status() lança exceção em respostas 4xx e 5xx.
  • No AIOHTTP, o mesmo é feito com response.raise_for_status() ou definindo raise_for_status=True ao abrir a sessão.

Ao trabalhar com proxy, os códigos mais comuns são:

CódigoSignificadoO que fazer?
407Falha na autenticação do proxyVerifique usuário, senha e a codificação de caracteres especiais
429O site de destino está aplicando limite de taxaReduza a concorrência, espere e tente de novo, aumente a distribuição de IP
403O site de destino está recusando a requisiçãoRevise o User-Agent e o tipo de IP
502 / 504O proxy não conseguiu alcançar o destinoTente de novo com um IP de saída diferente

Escreva a lógica de nova tentativa com espera exponencial: um segundo na primeira tentativa, depois dois, depois quatro. Insistir em intervalos fixos pode terminar com o bloqueio total de um IP que já recebeu 429.

Desempenho: qual é mais rápida?

Ao escolher a biblioteca, a pergunta "qual é mais rápida" geralmente olha para o lugar errado. A maior parte do tempo de uma requisição web se passa na rede; o tempo de processamento da própria biblioteca é pequeno perto disso.

O que é determinante é quantas requisições você consegue esperar ao mesmo tempo:

  • Um cliente síncrono espera cada requisição em sequência.
  • Um cliente assíncrono pode esperar centenas de requisições ao mesmo tempo.

Ou seja, para um trabalho de 10 páginas, o Requests é suficiente. Para um trabalho de 10.000 páginas, o cliente assíncrono do HTTPX ou o AIOHTTP reduzem o tempo de forma considerável. Dentro da mesma classe (entre o HTTPX assíncrono e o AIOHTTP), existem diferenças mensuráveis, e o AIOHTTP costuma estar à frente em desempenho bruto; mas essa diferença, na maioria dos projetos, fica ofuscada pelo tempo de resposta do site de destino e pela latência do proxy.

Como pensar proxy e concorrência juntos?

Bibliotecas assíncronas conseguem enviar muitas requisições ao mesmo tempo; mas se todas essas requisições saem de um único IP, o site de destino aplica limite de taxa rapidamente. Ao aumentar a concorrência, você também precisa pensar na distribuição de IP:

  • IP diferente a cada requisição: com o Proxies rotativos, você usa um único endereço de entrada e recebe um IP de saída diferente em cada requisição. Nenhuma alteração de código é necessária; todos os exemplos acima funcionam exatamente como estão.
  • Mesmo IP durante toda a sessão: em fluxos com login ou que mantêm estado, como carrinho de compras, o Proxies de sessão fixa mantém o mesmo IP por um determinado período.
  • Fazer você mesmo a rotação da sua lista: se você tem uma lista fixa de IPs, pode fazer a rotação no código; veja um exemplo passo a passo no artigo Como fazer a rotação de proxies em Python?.
  • Tipo de IP: em alvos protegidos, o tipo de IP é mais determinante do que a concorrência; explicamos o motivo no artigo Diferença entre proxy residencial e datacenter.

Migrando do Requests para o HTTPX

Se você quer migrar um projeto Requests existente para assíncrono, o HTTPX é o caminho mais curto. Diferenças a observar na migração:

RequestsHTTPXObservação
requests.get(url)httpx.get(url)Igual
proxies={"http": p, "https": p}proxy=pUm único parâmetro
timeout=None por padrãotimeout=5 por padrãoNo HTTPX o tempo limite vem ativado
Redirecionamento seguido por padrãoRequer follow_redirects=TrueO HTTPX não segue por padrão
Session()Client() / AsyncClient()Mesma lógica
response.json()response.json()Igual

A maior parte do código funciona sem mudanças; as diferenças se concentram nas cinco linhas acima. Migrar primeiro para o Client síncrono e rodar os testes, depois passar para o AsyncClient, reduz o risco.

Qual você deve escolher?

  • Escolha Requests para scripts pequenos, tarefas de automação, integrações de API e trabalhos em que a concorrência não é importante.
  • Escolha HTTPX se você pode migrar para assíncrono no futuro depois de começar síncrono hoje, se precisa de HTTP/2, ou se quer os dois usos juntos no mesmo projeto.
  • Escolha AIOHTTP para trabalhos de coleta de dados de volume muito alto, desenhados como assíncronos desde o início, e quando você precisa de cliente e servidor na mesma aplicação.

Se buscar as páginas apenas via HTTP não for suficiente, ou seja, se o conteúdo é carregado com JavaScript, essas bibliotecas sozinhas não bastam e você vai precisar de uma automação de navegador. Tratamos essa distinção no artigo Extração de dados: JavaScript ou Python?. Para automação de navegador do lado Python, veja nossos guias de Selenium e Proxy com o SeleniumBase.

Perguntas frequentes

É difícil migrar do Requests para o HTTPX?

Geralmente não. Trocar requests.get por httpx.get funciona diretamente na maioria dos códigos simples. As diferenças estão em detalhes como o tempo limite vir ativado por padrão, os redirecionamentos não serem seguidos por padrão e o nome do parâmetro de proxy. A tabela de migração acima lista essas diferenças.

Como uso proxy SOCKS5?

Instale pip install "requests[socks]" para o Requests e pip install "httpx[socks]" para o HTTPX, e use o esquema socks5:// no endereço do proxy. Se também quiser que a resolução de DNS seja feita do lado do proxy, use socks5h:// no Requests. As diferenças entre os protocolos estão no artigo Diferença entre SOCKS e HTTP proxy.

Código assíncrono é sempre melhor?

Não. Código assíncrono é mais complexo e mais difícil de depurar. Para um trabalho que não vai realmente se beneficiar da concorrência, código síncrono é mais legível e mais fácil de manter.

Usar threads com o Requests é uma alternativa ao assíncrono?

Para volumes médios, sim. É possível rodar dezenas de requisições em paralelo com ThreadPoolExecutor e manter o código síncrono. Em centenas de requisições simultâneas, o custo de memória das threads aumenta; nesse ponto, um cliente assíncrono é mais eficiente.

Sou obrigado a escrever as credenciais de proxy no código?

Não. As três bibliotecas leem as variáveis de ambiente HTTP_PROXY e HTTPS_PROXY (no HTTPX, trust_env vem ativado por padrão). Manter as credenciais em variável de ambiente reduz o risco de vazamento ao enviar o código para o repositório.

Quantas requisições devo abrir ao mesmo tempo?

Não existe um único número correto. Isso é determinado pela tolerância do site de destino, pela cota de conexões simultâneas do seu pacote de proxy e pelos limites da sua própria máquina. Começar com dez e aumentar sem ver resposta 429 é um método seguro.

Em resumo

As três bibliotecas fazem bem o seu trabalho; a escolha depende da escala do projeto. Para trabalhos simples, o Requests se destaca; para flexibilidade e preparo para o futuro, o HTTPX; para concorrência alta, o AIOHTTP. Seja qual for a biblioteca escolhida, defina tempo limite, limite a concorrência e escreva a nova tentativa com espera exponencial. Conforme o volume cresce, a estratégia de IP ganha tanta importância quanto a biblioteca. Para uma infraestrutura de coleta de dados em grande escala, veja nossas soluções de extração de dados.