HTTP com requests e httpx

1. Introdução às bibliotecas HTTP em Python

O protocolo HTTP é a espinha dorsal da comunicação na web moderna. Seja consumindo APIs REST, fazendo web scraping ou integrando serviços, dominar requisições HTTP é essencial para qualquer desenvolvedor Python. Duas bibliotecas se destacam nesse cenário: a clássica requests e a moderna httpx.

requests é a biblioteca mais popular para HTTP em Python, conhecida por sua API elegante e intuitiva. Já httpx surge como uma alternativa moderna, trazendo suporte nativo a async/await e mantendo compatibilidade com a interface do requests.

Instalação

# requests
pip install requests

# httpx
pip install httpx

Primeiros passos com ambas:

import requests
import httpx

# requests síncrono
response = requests.get('https://httpbin.org/get')
print(response.status_code)

# httpx síncrono
with httpx.Client() as client:
    response = client.get('https://httpbin.org/get')
    print(response.status_code)

2. Requisições básicas com requests

Métodos HTTP

import requests

# GET
response = requests.get('https://api.github.com/users/python')
print(response.json()['login'])

# POST
payload = {'title': 'Python', 'body': 'Linguagem poderosa'}
response = requests.post('https://jsonplaceholder.typicode.com/posts', json=payload)
print(response.status_code)  # 201 Created

# PUT
response = requests.put('https://jsonplaceholder.typicode.com/posts/1', json=payload)

# DELETE
response = requests.delete('https://jsonplaceholder.typicode.com/posts/1')

Parâmetros e cabeçalhos

# Parâmetros de URL
params = {'q': 'python', 'sort': 'stars', 'order': 'desc'}
response = requests.get('https://api.github.com/search/repositories', params=params)
print(response.url)  # URL com parâmetros

# Cabeçalhos personalizados
headers = {
    'User-Agent': 'MeuApp/1.0',
    'Accept': 'application/json',
    'Authorization': 'Bearer meu-token-aqui'
}
response = requests.get('https://api.github.com/user', headers=headers)

Tratamento de respostas

response = requests.get('https://httpbin.org/encoding/utf8')

print(response.status_code)          # 200
print(response.ok)                   # True
print(response.headers['Content-Type'])
print(response.encoding)             # utf-8
print(response.text)                 # conteúdo como string

# Verificar encoding automaticamente
response.encoding = response.apparent_encoding

3. Requisições avançadas com requests

Sessões e cookies

# Sessão mantém cookies entre requisições
session = requests.Session()

# Login
login_data = {'username': 'admin', 'password': '1234'}
session.post('https://httpbin.org/post', data=login_data)

# Próximas requisições mantêm o cookie de sessão
response = session.get('https://httpbin.org/cookies')
print(response.json())

Upload e download de arquivos

# Download
response = requests.get('https://httpbin.org/image/png', stream=True)
with open('imagem.png', 'wb') as f:
    for chunk in response.iter_content(chunk_size=8192):
        f.write(chunk)

# Upload
files = {'file': ('relatorio.pdf', open('relatorio.pdf', 'rb'), 'application/pdf')}
response = requests.post('https://httpbin.org/post', files=files)

Timeouts e redirecionamentos

# Timeout (segundos)
try:
    response = requests.get('https://httpbin.org/delay/5', timeout=3)
except requests.Timeout:
    print("Requisição excedeu o tempo limite")

# Controle de redirecionamentos
response = requests.get('https://httpbin.org/redirect/3', allow_redirects=True)
print(response.history)  # Lista de respostas intermediárias

# Desabilitar redirecionamentos
response = requests.get('https://httpbin.org/redirect/3', allow_redirects=False)
print(response.status_code)  # 302

4. httpx: a alternativa moderna

Diferenças fundamentais

import httpx

# httpx usa Client por padrão (recomendado)
with httpx.Client() as client:
    response = client.get('https://httpbin.org/get')

# Suporte nativo a HTTP/2
client = httpx.Client(http2=True)
response = client.get('https://http2.pro/api/v1')
print(response.http_version)  # HTTP/2

Interface assíncrona

import asyncio
import httpx

async def fetch_data(url):
    async with httpx.AsyncClient() as client:
        response = await client.get(url)
        return response.json()

async def main():
    # Requisições concorrentes
    urls = [
        'https://jsonplaceholder.typicode.com/posts/1',
        'https://jsonplaceholder.typicode.com/posts/2',
        'https://jsonplaceholder.typicode.com/posts/3'
    ]

    tasks = [fetch_data(url) for url in urls]
    results = await asyncio.gather(*tasks)

    for result in results:
        print(result['title'])

# Executar
asyncio.run(main())

Interface síncrona e assíncrona no mesmo pacote

import httpx

# Síncrono
def sync_example():
    with httpx.Client() as client:
        response = client.get('https://httpbin.org/get')
        return response.json()

# Assíncrono (mesmo pacote)
async def async_example():
    async with httpx.AsyncClient() as client:
        response = await client.get('https://httpbin.org/get')
        return response.json()

5. Trabalhando com autenticação e segurança

Autenticação básica e Bearer token

import requests
from requests.auth import HTTPBasicAuth

# Autenticação básica
response = requests.get('https://httpbin.org/basic-auth/user/pass',
                       auth=HTTPBasicAuth('user', 'pass'))

# Bearer token
headers = {'Authorization': 'Bearer seu-token-aqui'}
response = requests.get('https://api.github.com/user', headers=headers)

# httpx também suporta ambas
import httpx
auth = httpx.BasicAuth('user', 'pass')
response = httpx.get('https://httpbin.org/basic-auth/user/pass', auth=auth)

SSL/TLS e proxies

# Verificação SSL personalizada
response = requests.get('https://exemplo.com', verify='/caminho/certificado.pem')

# Desabilitar verificação (não recomendado em produção)
response = requests.get('https://exemplo.com', verify=False)

# Proxies
proxies = {
    'http': 'http://10.10.1.10:3128',
    'https': 'http://10.10.1.10:1080'
}
response = requests.get('https://api.github.com', proxies=proxies)

# httpx com proxy
with httpx.Client(proxies='http://10.10.1.10:3128') as client:
    response = client.get('https://api.github.com')

6. Tratamento de erros e exceções

Hierarquia de exceções

import requests
from requests.exceptions import RequestException, ConnectionError, Timeout, HTTPError

try:
    response = requests.get('https://httpbin.org/status/500')
    response.raise_for_status()  # Levanta HTTPError para 4xx/5xx
except HTTPError as e:
    print(f"Erro HTTP: {e.response.status_code}")
except ConnectionError:
    print("Falha na conexão")
except Timeout:
    print("Timeout excedido")
except RequestException as e:
    print(f"Erro na requisição: {e}")

# httpx tem hierarquia similar
import httpx
try:
    with httpx.Client() as client:
        response = client.get('https://httpbin.org/status/500')
        response.raise_for_status()
except httpx.HTTPStatusError as e:
    print(f"Erro HTTP: {e.response.status_code}")
except httpx.ConnectError:
    print("Falha na conexão")

Retry automático

from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

session = requests.Session()
retry_strategy = Retry(
    total=3,
    backoff_factor=1,
    status_forcelist=[500, 502, 503, 504],
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount('http://', adapter)
session.mount('https://', adapter)

response = session.get('https://httpbin.org/status/500')

Logging e debugging

import logging
import requests

# Habilitar logging detalhado
logging.basicConfig(level=logging.DEBUG)
requests_log = logging.getLogger("urllib3")
requests_log.setLevel(logging.DEBUG)

# httpx oferece eventos de debug
import httpx

def log_request(request):
    print(f"Requisição: {request.method} {request.url}")

def log_response(response):
    print(f"Resposta: {response.status_code}")

with httpx.Client(event_hooks={'request': [log_request], 'response': [log_response]}) as client:
    response = client.get('https://httpbin.org/get')

7. Casos de uso práticos

Consumo de API REST (JSONPlaceholder)

import requests
import json

# Criar um post
novo_post = {
    'title': 'Python HTTP',
    'body': 'Trabalhando com requests e httpx',
    'userId': 1
}

response = requests.post('https://jsonplaceholder.typicode.com/posts', json=novo_post)
post_criado = response.json()
print(f"Post criado: ID {post_criado['id']}")

# Listar todos os posts do usuário
response = requests.get('https://jsonplaceholder.typicode.com/posts', params={'userId': 1})
posts = response.json()
print(f"Total de posts: {len(posts)}")

Upload de múltiplos arquivos com progresso

import requests
from tqdm import tqdm

def upload_com_progresso(url, arquivos):
    total_size = sum(len(open(f, 'rb').read()) for f in arquivos)

    with tqdm(total=total_size, unit='B', unit_scale=True, desc="Upload") as pbar:
        def callback(monitor):
            pbar.update(monitor.bytes_sent - pbar.n)

        files = [('files', open(f, 'rb')) for f in arquivos]
        response = requests.post(url, files=files, hooks={'response': [callback]})

    return response

# Uso
arquivos = ['foto1.jpg', 'foto2.jpg', 'foto3.jpg']
response = upload_com_progresso('https://httpbin.org/post', arquivos)

Comparação de desempenho: requests vs httpx

import time
import asyncio
import requests
import httpx

# Teste com requests síncrono
def test_requests_sync(urls):
    start = time.time()
    for url in urls:
        response = requests.get(url)
    return time.time() - start

# Teste com httpx síncrono
def test_httpx_sync(urls):
    start = time.time()
    with httpx.Client() as client:
        for url in urls:
            response = client.get(url)
    return time.time() - start

# Teste com httpx assíncrono
async def test_httpx_async(urls):
    start = time.time()
    async with httpx.AsyncClient() as client:
        tasks = [client.get(url) for url in urls]
        await asyncio.gather(*tasks)
    return time.time() - start

# Executar testes
urls = ['https://httpbin.org/get'] * 10

print(f"requests síncrono: {test_requests_sync(urls):.2f}s")
print(f"httpx síncrono: {test_httpx_sync(urls):.2f}s")

async def run_async():
    result = await test_httpx_async(urls)
    print(f"httpx assíncrono: {result:.2f}s")

asyncio.run(run_async())

8. Conclusão

Tanto requests quanto httpx são bibliotecas excelentes para trabalhar com HTTP em Python. O requests continua sendo a escolha ideal para projetos que priorizam simplicidade e maturidade, enquanto o httpx oferece vantagens significativas em cenários que exigem alto desempenho com I/O concorrente ou suporte a HTTP/2. A escolha entre elas depende do seu caso de uso específico: para scripts simples, requests é imbatível; para aplicações modernas que precisam de performance e assincronicidade, httpx é a melhor opção.

Referências