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
| Recurso | Requests | HTTPX | AIOHTTP |
|---|---|---|---|
| Uso síncrono | Sim | Sim | Não |
| Uso assíncrono | Não | Sim | Sim |
| HTTP/2 | Não | Sim (com pacote extra) | Não |
| Proxy (HTTP/HTTPS) | Nativo | Nativo | Nativo |
| Proxy SOCKS | Com requests[socks] | Com httpx[socks] | Com pacote externo |
| Tempo limite padrão | Nenhum (espera indefinidamente) | 5 segundos | 5 minutos (total) |
| Seguir redirecionamentos | Ativado por padrão | Desativado por padrão | Ativado por padrão |
| Pool de conexões | Com Session | Com Client | Com ClientSession |
| Curva de aprendizado | Muito fácil | Fácil | Média |
| Trabalho mais adequado | Script simples, volume pequeno | Projetos mistos, síncrono + assíncrono | Concorrê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:
pip install requestsUma única requisição via proxy:
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:
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:
pip install httpxO uso síncrono se parece quase ponto a ponto com o Requests:
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:
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:
pip install aiohttpimport 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:
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 definindoraise_for_status=Trueao abrir a sessão.
Ao trabalhar com proxy, os códigos mais comuns são:
| Código | Significado | O que fazer? |
|---|---|---|
| 407 | Falha na autenticação do proxy | Verifique usuário, senha e a codificação de caracteres especiais |
| 429 | O site de destino está aplicando limite de taxa | Reduza a concorrência, espere e tente de novo, aumente a distribuição de IP |
| 403 | O site de destino está recusando a requisição | Revise o User-Agent e o tipo de IP |
| 502 / 504 | O proxy não conseguiu alcançar o destino | Tente 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:
| Requests | HTTPX | Observação |
|---|---|---|
requests.get(url) | httpx.get(url) | Igual |
proxies={"http": p, "https": p} | proxy=p | Um único parâmetro |
timeout=None por padrão | timeout=5 por padrão | No HTTPX o tempo limite vem ativado |
| Redirecionamento seguido por padrão | Requer follow_redirects=True | O 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.




