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.







