urllib.error en Python: maneja errores HTTP

Publicado el: 21/08/2026
Tempo de leitura: 5 minutos
Código de error sobre datos binarios que representa fallos manejados con urllib.error en Python

El módulo urllib.error reúne las excepciones utilizadas por urllib.request cuando falla una operación de red. Distinguir un error HTTP de un problema de DNS, timeout, certificado inválido, conexión rechazada o descarga incompleta permite construir clientes más confiables, logs más útiles y políticas de retry que no empeoran una incidencia.

Esta guía se concentra en el tratamiento de excepciones del cliente estándar. Para crear requests, headers, proxies y descargas con límites, consulta urllib.request en Python. Para separar y validar componentes de URLs, revisa urllib.parse en Python.

La jerarquía básica

URLError es la excepción base y deriva de OSError. HTTPError deriva de URLError. Por eso importa el orden de los bloques except: captura HTTPError antes de URLError, o el manejador más general ocultará el caso específico.

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

try:
    with urlopen("https://example.com/recurso", timeout=10) as respuesta:
        datos = respuesta.read(100_000)
except HTTPError as error:
    print("Estado HTTP:", error.code)
except URLError as error:
    print("Fallo de transporte:", error.reason)

Un estado 404 o 500 significa que llegó una respuesta HTTP válida, aunque informe un fallo. Un URLError puro puede representar resolución de nombre, conexión rechazada, timeout, TLS u otra etapa anterior a la respuesta.

Comprender URLError

El atributo reason puede ser una cadena u otra excepción. Evita tomar decisiones comparando únicamente mensajes. Inspecciona el tipo subyacente cuando la diferencia sea relevante.

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

try:
    urlopen("https://example.invalid", timeout=5)
except URLError as error:
    if isinstance(error.reason, socket.timeout):
        print("La conexión agotó el tiempo")
    else:
        print(type(error.reason).__name__, error.reason)

Según la plataforma y el camino interno, un timeout puede aparecer como TimeoutError, socket.timeout o una excepción anidada. Las pruebas deben reflejar el entorno real, sin convertir textos inestables en un contrato rígido.

HTTPError es excepción y respuesta

HTTPError expone url, code, reason, headers y un objeto similar a archivo en fp. El propio error también puede leerse como la respuesta. Así es posible extraer un cuerpo JSON o texto enviado por el servidor.

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

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

try:
    with urlopen(request, timeout=10) as respuesta:
        payload = json.load(respuesta)
except HTTPError as error:
    cuerpo = error.read(64_000)
    tipo = error.headers.get_content_type()
    if tipo == "application/json":
        detalle = json.loads(cuerpo.decode("utf-8"))
    else:
        detalle = cuerpo.decode("utf-8", errors="replace")
    print(error.code, detalle)

Limita siempre el cuerpo de error. Un servidor remoto no confiable puede devolver megabytes o mantener la conexión ocupada. Un estado de error no hace que el contenido sea seguro para logs o HTML.

Clasificar estados

Una política práctica puede agrupar respuestas:

  • 400–499 suelen indicar problemas de request, autenticación, autorización o recurso.
  • 500–599 suelen indicar un fallo temporal o interno del servidor.
  • 429 pide reducir el ritmo y puede incluir Retry-After.
  • 401 y 403 no deben provocar repeticiones infinitas con las mismas credenciales.

No repitas automáticamente cualquier estado. Reenviar un POST no idempotente puede duplicar pedidos, cobros, mensajes o registros.

Retries selectivos con backoff

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

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

def descargar(url: str, intentos: int = 3) -> bytes:
    for indice in range(intentos):
        try:
            with urlopen(url, timeout=10) as respuesta:
                return respuesta.read(1_000_000)
        except HTTPError as error:
            if error.code not in REPETIBLES or indice == intentos - 1:
                raise
        except URLError:
            if indice == intentos - 1:
                raise
        time.sleep(2 ** indice)
    raise RuntimeError("flujo imposible")

En producción, añade jitter aleatorio para evitar que muchos clientes repitan al mismo tiempo. Respeta Retry-After cuando corresponda e impone un presupuesto total de tiempo. Varias tentativas no deberían consumir cada una todo el plazo de usuario.

Un timeout no es un límite completo

El argumento timeout limita operaciones bloqueantes, pero un cliente robusto también restringe tamaño, redirects y duración total. Leer hasta el final sin límite puede tardar mucho después de establecer la conexión.

Cuando el usuario controla la URL, añade defensas contra SSRF: esquemas y puertos permitidos, validación DNS e IP, control de redirects y límites de respuesta. El manejo de excepciones no valida el destino.

Fallos de TLS

Los problemas de certificado suelen llegar mediante URLError con una excepción SSL en reason. No los soluciones desactivando la validación.

import ssl
from urllib.error import URLError

try:
    # llamada HTTPS
    pass
except URLError as error:
    if isinstance(error.reason, ssl.SSLCertVerificationError):
        print("No fue posible validar el certificado")
        raise

Corrige el reloj, la cadena de confianza, el hostname o la CA. La guía de ssl en Python explica contextos seguros y validación de certificados.

ContentTooShortError

ContentTooShortError se relaciona con urlretrieve() cuando el contenido recibido es menor que el Content-Length esperado. Su atributo content conserva los datos descargados, pero deben considerarse incompletos.

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

try:
    ruta, headers = urlretrieve(
        "https://example.com/archivo.zip",
        "archivo.zip",
    )
except ContentTooShortError as error:
    print("Descarga truncada:", len(error.content))
    raise

No proceses silenciosamente un archivo parcial. Elimina o aísla el destino temporal, repite según la política y verifica hash o firma cuando exista. Para ubicaciones temporales seguras, usa tempfile en Python.

Cuerpos con encoding desconocido

El servidor puede declarar charset, omitirlo o informar uno incorrecto. Para diagnóstico, usa el charset declarado cuando sea adecuado y un fallback con reemplazo. Una decodificación UTF-8 estricta dentro del manejador puede lanzar una segunda excepción y ocultar el problema original.

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

Logs sin filtrar secretos

Registra método, host, ruta normalizada, estado, duración e identificador de correlación. No registres tokens, headers Authorization, cookies, contraseñas en URLs ni cuerpos completos con datos personales.

El atributo url puede incluir una query sensible. Enmascara parámetros antes del log. Limita también el texto de reason, porque contenido influido externamente puede alcanzar sistemas operativos.

Fallo esperado frente a bug

Captura únicamente las excepciones que puedes tratar. Un except Exception alrededor de todo el flujo puede transformar un defecto de programación en una advertencia de red. Mantén pequeña la región del try.

try:
    respuesta = urlopen(request, timeout=10)
except (HTTPError, URLError) as error:
    manejar_fallo_red(error)
else:
    with respuesta:
        procesar(respuesta)

Los errores de procesar() ya no quedan clasificados incorrectamente como fallos de transporte.

APIs de CLI y bibliotecas

Una herramienta de línea de comandos puede convertir fallos conocidos en mensajes breves y códigos de salida estables, conservando detalles en modo verbose. Una biblioteca reutilizable normalmente debe propagar el error original o envolverlo con raise ErrorDominio(...) from error para mantener la causa.

Probar sin depender de internet

Ejecuta un servidor HTTP local que produzca 404, 429, 500, redirects, respuestas lentas y cuerpos truncados. La guía de socketserver en Python ayuda a preparar integraciones controladas. Los mocks son rápidos, pero una prueba local de protocolo descubre errores de framing y lectura.

Incluye pruebas para el orden de except, límites de cuerpo, retry solo en operaciones elegibles, conservación de causas y eliminación de secretos.

Errores comunes

Los fallos frecuentes son capturar URLError antes que HTTPError, repetir toda respuesta, desactivar TLS, leer cuerpos ilimitados, comparar textos inestables, registrar credenciales, aceptar archivos truncados, repetir POST sin protección de idempotencia y capturar excepciones demasiado amplias.

Conclusión

urllib.error separa respuestas HTTP, fallos de transporte y descargas incompletas. Esa distinción mejora mensajes, métricas y decisiones de recuperación. Usa HTTPError para examinar estado, headers y cuerpo; inspecciona URLError.reason para conocer la causa; y trata los datos de ContentTooShortError como incompletos y no confiables.

Consulta la documentación oficial de urllib.error y HTTP Semantics. Un cliente seguro combina timeouts, límites, validación de destino, retries selectivos y logs sin secretos.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Teclas con la palabra HTML que representan entidades HTML en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    html.entities: convierte entidades HTML

    Aprende html.entities en Python para consultar entidades HTML, convertir nombres y code points y no confundir decodificación con sanitización.

    Ler mais

    Tempo de leitura: 7 minutos
    21/08/2026
    Carpeta con archivos que representa tipos MIME identificados con mimetypes en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    mimetypes: detecta tipos MIME de archivos

    Aprende mimetypes en Python para identificar tipos de archivo, validar cargas y definir Content-Type con más seguridad.

    Ler mais

    Tempo de leitura: 6 minutos
    20/08/2026
    Código HTML en una pantalla que representa análisis con html.parser en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    html.parser en Python: analiza HTML

    Aprende html.parser en Python para extraer texto, enlaces y metadatos, procesar HTML por bloques y no confundir parsing con sanitización.

    Ler mais

    Tempo de leitura: 4 minutos
    20/08/2026
    Persona usando un portátil en una sesión web que representa cookies con http.cookiejar en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    http.cookiejar en Python: gestiona cookies

    Aprende http.cookiejar en Python para mantener sesiones, aplicar políticas, persistir cookies de forma segura e integrar urllib.request.

    Ler mais

    Tempo de leitura: 5 minutos
    20/08/2026
    Rack de servidores que representa conexiones HTTP de bajo nivel con http.client en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    http.client en Python: HTTP de bajo nivel

    Aprende http.client en Python para controlar conexiones HTTP y HTTPS, streaming, headers, TLS, reutilización, límites y errores.

    Ler mais

    Tempo de leitura: 4 minutos
    20/08/2026
    Teclas formando HTTP que representan solicitudes con urllib.request en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    urllib.request en Python: HTTP nativo

    Aprende urllib.request en Python para GET, POST, JSON y descargas con timeout, TLS, redirects, proxies, límites y manejo de errores.

    Ler mais

    Tempo de leitura: 4 minutos
    19/08/2026