urllib.error no Python: trate falhas HTTP

Publicado em: 21/08/2026
Tempo de leitura: 6 minutos
Código de erro sobre dados binários representando falhas tratadas com urllib.error no Python

O módulo urllib.error reúne as exceções usadas por urllib.request quando uma operação de rede falha. Saber distinguir um erro HTTP de uma falha de DNS, timeout, certificado inválido ou download incompleto permite criar clientes mais confiáveis, logs mais úteis e políticas de retry que não pioram o problema.

Este guia parte do cliente da biblioteca padrão. Para montar requisições, headers, proxies e downloads com limites, consulte urllib.request no Python. Para decompor e validar componentes de URLs, veja urllib.parse no Python.

A hierarquia básica

A exceção-base é URLError, que deriva de OSError. HTTPError deriva de URLError. Isso significa que a ordem dos blocos except importa: capture HTTPError antes de URLError, ou o bloco mais geral esconderá o tratamento específico.

from urllib.error import HTTPError, URLError
from urllib.request import urlopen

try:
    with urlopen("https://example.com/recurso", timeout=10) as resposta:
        dados = resposta.read(100_000)
except HTTPError as erro:
    print("Status HTTP:", erro.code)
except URLError as erro:
    print("Falha de transporte:", erro.reason)

Um status 404 ou 500 significa que houve uma resposta HTTP válida, mesmo que indique falha. Já um URLError puro pode representar resolução de nome, conexão recusada, timeout, TLS ou outra etapa anterior à resposta.

Entendendo URLError

O atributo reason pode ser uma string ou outra exceção. Por isso, não dependa apenas de comparar mensagens. Inspecione o tipo quando precisar de uma decisão operacional.

import socket
from urllib.error import URLError
from urllib.request import urlopen

try:
    urlopen("https://example.invalid", timeout=5)
except URLError as erro:
    if isinstance(erro.reason, socket.timeout):
        print("A conexão expirou")
    else:
        print(type(erro.reason).__name__, erro.reason)

Em algumas plataformas e versões, timeouts podem aparecer como TimeoutError, socket.timeout ou dentro de outra exceção. Testes devem refletir o ambiente real, sem transformar mensagens textuais instáveis em contratos rígidos.

HTTPError é exceção e resposta

HTTPError contém url, code, reason, headers e um objeto semelhante a arquivo em fp. O próprio erro também pode ser lido como a resposta. Isso permite extrair um corpo JSON ou texto enviado pelo servidor.

import json
from urllib.error import HTTPError
from urllib.request import Request, urlopen

req = Request("https://api.example.com/items/999")

try:
    with urlopen(req, timeout=10) as resposta:
        payload = json.load(resposta)
except HTTPError as erro:
    corpo = erro.read(64_000)
    tipo = erro.headers.get_content_type()
    if tipo == "application/json":
        detalhe = json.loads(corpo.decode("utf-8"))
    else:
        detalhe = corpo.decode("utf-8", errors="replace")
    print(erro.code, detalhe)

Sempre limite a leitura do corpo de erro. Um servidor remoto não confiável pode devolver megabytes ou manter a conexão aberta. O status não torna o conteúdo seguro para logs ou HTML.

Trate status por categoria

Uma política simples pode agrupar status:

  • 400–499 normalmente indicam problema na requisição, autenticação, autorização ou recurso.
  • 500–599 normalmente indicam falha temporária ou interna no servidor.
  • 429 pede redução de ritmo e pode incluir Retry-After.
  • 401 e 403 não devem ser resolvidos repetindo a mesma credencial indefinidamente.

Evite retry automático para todos os status. Repetir um POST não idempotente pode duplicar pedidos, cobranças ou registros.

Retries seletivos com backoff

import time
from urllib.error import HTTPError, URLError
from urllib.request import urlopen

REPETIVEIS = {429, 500, 502, 503, 504}

def baixar(url: str, tentativas: int = 3) -> bytes:
    for indice in range(tentativas):
        try:
            with urlopen(url, timeout=10) as resposta:
                return resposta.read(1_000_000)
        except HTTPError as erro:
            if erro.code not in REPETIVEIS or indice == tentativas - 1:
                raise
        except URLError:
            if indice == tentativas - 1:
                raise
        time.sleep(2 ** indice)
    raise RuntimeError("fluxo impossível")

Em produção, adicione jitter aleatório para evitar que muitos clientes repitam ao mesmo tempo. Respeite Retry-After quando fizer sentido e imponha um orçamento total de tempo. Não deixe cada tentativa usar um timeout completo sem considerar o prazo global.

Timeout não é um único valor universal

O parâmetro timeout limita operações bloqueantes, mas um cliente robusto também precisa limitar tamanho, número de redirects e tempo total. Ler o corpo até o fim sem limite pode durar muito mesmo depois de a conexão ter sido estabelecida.

Ao buscar URLs fornecidas pelo usuário, aplique controles contra SSRF: esquemas permitidos, portas, resolução DNS, endereços privados, redirects e tamanho. O tratamento de exceções não substitui validação de destino.

Falhas de TLS

Problemas de certificado normalmente chegam por meio de URLError com uma exceção SSL em reason. Não resolva desativando a validação.

import ssl
from urllib.error import URLError

try:
    # chamada HTTPS
    pass
except URLError as erro:
    if isinstance(erro.reason, ssl.SSLCertVerificationError):
        print("Certificado não pôde ser validado")
        raise

Corrija relógio, cadeia de confiança, hostname ou CA. O guia de ssl no Python explica contextos TLS e validação correta.

ContentTooShortError

ContentTooShortError é associado a urlretrieve() quando o conteúdo recebido é menor que o valor esperado em Content-Length. O atributo content preserva os dados baixados, mas eles devem ser considerados incompletos.

from urllib.error import ContentTooShortError
from urllib.request import urlretrieve

try:
    caminho, headers = urlretrieve(
        "https://example.com/arquivo.zip",
        "arquivo.zip",
    )
except ContentTooShortError as erro:
    print("Download truncado:", len(erro.content))
    raise

Não processe silenciosamente um arquivo parcial. Remova o destino temporário, repita conforme a política e verifique hash ou assinatura quando disponível. Para arquivos temporários seguros, use tempfile no Python.

Corpo de erro com encoding desconhecido

O servidor pode declarar charset, omiti-lo ou mentir. Para mensagens de diagnóstico, use o charset informado quando confiável e fallback com errors="replace". Não decodifique bytes arbitrários como UTF-8 estrito dentro do próprio tratamento de erro, pois uma segunda exceção pode esconder a primeira.

def ler_erro(erro: HTTPError, limite: int = 64_000) -> str:
    dados = erro.read(limite)
    charset = erro.headers.get_content_charset() or "utf-8"
    return dados.decode(charset, errors="replace")

Logs sem vazar segredos

Registre método, host, caminho normalizado, status, duração e um identificador de correlação. Não registre tokens, cabeçalhos Authorization, cookies, senhas na URL ou corpos completos com dados pessoais.

O atributo url de HTTPError pode incluir query strings sensíveis. Antes de gravá-lo, remova ou masque parâmetros. Também limite a mensagem de reason, pois conteúdo externo pode alcançar logs.

Separando erro esperado de bug

Capture apenas as exceções que você consegue tratar. Um bloco except Exception em torno de toda a função pode transformar erros de programação em supostas falhas de rede. Mantenha a região do try pequena.

try:
    resposta = urlopen(req, timeout=10)
except (HTTPError, URLError) as erro:
    tratar_rede(erro)
else:
    with resposta:
        processar(resposta)

Erros durante processar() não serão classificados incorretamente como falhas de transporte.

Exceções em APIs de linha de comando

Em uma CLI, converta falhas conhecidas em mensagens curtas e códigos de saída consistentes. Preserve detalhes em modo verboso. Em uma biblioteca, prefira propagar a exceção original ou criar uma exceção de domínio usando raise MinhaFalha(...) from erro, mantendo a causa.

Testando sem depender da internet

Use um servidor HTTP local para produzir 404, 429, 500, redirects, respostas lentas e corpos truncados. O guia de socketserver no Python pode ajudar em cenários controlados. Mockar tudo é rápido, mas testes locais de integração revelam diferenças de protocolo.

Inclua testes para ordem dos except, limite do corpo, retry apenas em operações idempotentes, preservação da causa e remoção de segredos nos logs.

Erros comuns

Os erros mais frequentes são capturar URLError antes de HTTPError, repetir qualquer status, desativar TLS, ler corpos sem limite, confiar em mensagens textuais, registrar credenciais, ignorar arquivos truncados, fazer retry de POST sem chave de idempotência e capturar exceções demais.

Conclusão

urllib.error permite separar respostas HTTP de falhas de transporte e downloads incompletos. Essa distinção melhora mensagens, métricas e decisões de recuperação. Use HTTPError para examinar status, headers e corpo; use URLError para investigar a causa subjacente; e trate ContentTooShortError como conteúdo não confiável.

Consulte a documentação oficial de urllib.error e a especificação HTTP Semantics. Uma política segura combina timeouts, limites, validação de destino, retries seletivos e logs sem segredos.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Teclas com a palavra HTML representando entidades HTML no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    html.entities: converta entidades HTML

    Aprenda html.entities no Python para consultar entidades HTML, converter nomes e code points e evitar confundir decoding com sanitização.

    Ler mais

    Tempo de leitura: 7 minutos
    21/08/2026
    Pasta com arquivos representando tipos MIME identificados com mimetypes no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    mimetypes: detecte tipos MIME de arquivos

    Aprenda mimetypes no Python para identificar tipos de arquivos, validar uploads e gerar headers HTTP com mais segurança.

    Ler mais

    Tempo de leitura: 7 minutos
    20/08/2026
    Código HTML em uma tela representando análise com html.parser no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    html.parser no Python: analise HTML

    Aprenda html.parser no Python para extrair texto, links e metadados, processar HTML em blocos e evitar confundir parsing com sanitização.

    Ler mais

    Tempo de leitura: 6 minutos
    20/08/2026
    Pessoa usando laptop em uma sessão web representando cookies com http.cookiejar no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    http.cookiejar no Python: gerencie cookies

    Aprenda http.cookiejar no Python para manter sessões, aplicar políticas, persistir cookies com segurança e integrar com urllib.request.

    Ler mais

    Tempo de leitura: 6 minutos
    20/08/2026
    Rack de servidores representando conexões HTTP de baixo nível com http.client no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    http.client no Python: HTTP de baixo nível

    Aprenda http.client no Python para controlar conexões HTTP e HTTPS, streaming, headers, TLS, reutilização, limites e erros de protocolo.

    Ler mais

    Tempo de leitura: 5 minutos
    20/08/2026
    Código HTML em uma tela representando crawling responsável com robotparser no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    robotparser no Python: leia robots.txt

    Aprenda urllib.robotparser no Python para respeitar robots.txt, crawl-delay, request-rate, sitemaps, cache e limites de crawling.

    Ler mais

    Tempo de leitura: 5 minutos
    20/08/2026