Cuando se trabaja con HTTP, las solicitudes fallidas son una realidad inevitable que hay que afrontar. En el desarrollo web, un estado 200 indica una buena respuesta. Sin embargo, no siempre obtenemos un 200, y esta guía le ayudará a comprender cómo manejar estos códigos de estado distintos de 200.
Según Mozilla, los códigos de estado se pueden dividir en las siguientes categorías:
- 100-199: Respuestas informativas
- 200-299: Respuestas correctas
- 300-399: Mensajes de redireccionamiento
- 400-499: Mensajes de error del cliente
- 500-599: Mensajes de error del servidor
¿Qué son los códigos de estado?
Los códigos de error son importantes. Al crear programas del lado del cliente, como los Scrapers web, debemos centrarnos principalmente en los códigos de estado del rango 400+ y 500+. Los códigos del rango 400 suelen abarcar errores del lado del cliente, como problemas de autenticación, limitación de velocidad, tiempos de espera y el famoso error 404: Archivo no encontrado. En el rango 500, generalmente nos encontramos con problemas del servidor.
Durante décadas, Mozilla ha documentado los estándares de desarrollo web del W3C y el IETF. A continuación se muestra una lista de códigos de error comunes con los que te puedes encontrar. Esta lista no es exhaustiva. Estos errores provienen de la documentación oficial de Mozilla. Dependiendo del sitio de destino, los códigos pueden variar ligeramente, pero la lógica debe seguir siendo la misma.
| Código de estado | Significado | Descripción |
|---|---|---|
| 400 | Solicitud incorrecta | Compruebe el formato de su solicitud |
| 401 | No autorizado | Compruebe su clave API |
| 403 | Prohibido | No puede acceder a estos datos |
| 404 | No encontrado | El sitio/punto final no existe |
| 408 | Tiempo de espera de la solicitud agotado | Se ha agotado el tiempo de espera de la solicitud, inténtelo de nuevo |
| 429 | Demasiadas solicitudes | Reduzca la velocidad de sus solicitudes |
| 500 | Error interno del servidor | Error genérico del servidor, reintente la solicitud |
| 501 | No implementado | El servidor aún no admite esta función. |
| 502 | Puerta de enlace incorrecta | Respuesta fallida de un servidor ascendente. |
| 503 | Servicio no disponible | El servidor está temporalmente inactivo, inténtelo más tarde. |
| 504 | Tiempo de espera de la puerta de enlace agotado | Se ha agotado el tiempo de espera del servidor ascendente |
Estrategias de reintento
Al implementar un mecanismo de reintento, puede utilizar bibliotecas precompiladas como HTTPAdapter y Tenacity. Dependiendo de su caso, es posible que incluso desee escribir su propia lógica de reintento.
Normalmente, queremos un límite de reintentos y una estrategia para retroceder. Necesitamos un límite para no quedarnos atrapados en un bucle infinito de reintentos. Necesitamos retroceder poco a poco para respetar el servidor host. Cuando las solicitudes llegan demasiado rápido, te bloquean o sobrecargan el servidor.
- Límites de reintentos: Debe establecer un límite. Después de X reintentos, su Scraper se dará por vencido.
- Algoritmo de retroceso: este es relativamente sencillo. Debes empezar con un pequeño retroceso y aumentarlo con cada reintento. Queremos empezar con 0,3, luego aumentar a 0,6, 1,2 y así sucesivamente.
Queremos reintentar nuestras solicitudes hasta un límite determinado. Después de cada solicitud fallida, queremos esperar un poco más de tiempo.
HTTPAdapter
Con HTTPAdapter, necesitamos configurar tres cosas: total, backoff_factor y status_forcelist. allowed_methods no es realmente un requisito, pero hace que nuestro código sea más seguro al ayudar a definir nuestras condiciones de reintento. En el código siguiente, utilizamos httpbin para forzar automáticamente un error y activar nuestra lógica de reintento.
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
#crear una sesión
session = requests.Session()
#configurar los ajustes de reintento
retry = Retry(
total=3, #máximo de reintentos
backoff_factor=0.3, #tiempo entre reintentos (retroceso exponencial)
status_forcelist=(429, 500, 502, 503, 504), #códigos de estado para activar un reintento
allowed_methods={"GET", "POST"}
)
#montar el adaptador con nuestra configuración personalizada
adaptador = HTTPAdapter(máximo_reintentos=reintento)
sesión.montar("http://", adaptador)
sesión.montar("https://", adaptador)
#realizar una solicitud con nuestra lógica de reintento
intentar:
imprimir("Realizando una solicitud con lógica de reintento...")
response = session.get("https://httpbin.org/status/500")
response.raise_for_status()
print("✅ Solicitud correcta:", response.status_code)
except requests.exceptions.RequestException as e:
print("❌ Solicitud fallida tras los reintentos:", e)
Una vez que hayamos creado un objeto Session, hacemos lo siguiente:
- Creamos un objeto
Retryy definimos lo siguiente:total: El límite máximo para reintentar una solicitud.backoff_factor: Tiempo de espera entre reintentos. Se ajusta exponencialmente a medida que aumentan nuestros reintentos.status_forcelist: Una lista de códigos de estado incorrectos. Cualquier código de esta lista activará automáticamente un reintento.
- Cree un objeto
HTTPAdaptercon nuestra variablede reintento:adapter = HTTPAdapter(max_retries=retry). - Una vez creado el
adaptador, lo montamos en los métodos HTTP y HTTPS utilizandosession.mount().
Al ejecutar este código, se ejecutarán nuestros tres reintentos (total=3) y obtendrá el siguiente resultado.
Realizando una solicitud con lógica de reintento...
❌ Solicitud fallida tras los reintentos: HTTPSConnectionPool(host='httpbin.org', port=443): Se ha superado el número máximo de reintentos con la URL: /status/500 (Causado por ResponseError('demasiadas respuestas de error 500'))
Tenacity
También puede utilizar Tenacity, una popular biblioteca de código abierto para reintentos en Python. No se limita a HTTP, pero nos ofrece una forma expresiva y comprensible de implementar reintentos.
En primer lugar, debe instalarla.
pip install tenacity
Una vez instalada, creamos un decorador y lo utilizamos para envolver una función de solicitudes. Con nuestro decorador @retry, añadimos los argumentos stop, wait y retry.
import requests
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type, RetryError
#definir una estrategia de reintento
@retry(
stop=stop_after_attempt(3), #reintentar hasta 3 veces
wait=wait_exponential(multiplier=0.3), #retroceso exponencial
retry=retry_if_exception_type(requests.exceptions.RequestException), #reintentar en caso de fallos en la solicitud)
def make_request():
print("Realizando una solicitud con lógica de reintento...")
response = requests.get("https://httpbin.org/status/500")
respuesta.raise_for_status()
imprimir("✅ Solicitud exitosa:", respuesta.status_code)
devolver respuesta
# Intentar realizar la solicitud
intentar:
realizar_solicitud()
excepto RetryError como e:
imprimir("❌ Solicitud fallida después de todos los reintentos:", e)
La lógica y la configuración aquí son muy similares a nuestro primer ejemplo con HTTPAdapter.
stop=stop_after_attempt(3): Esto le indica atenacityque se dé por vencido después de 3 intentos fallidos.wait=wait_exponential(multiplier=0.3)utiliza la misma espera que utilizamos antes. También retrocede exponencialmente, igual que antes.retry=retry_if_exception_type(requests.exceptions.RequestException)le indica atenacityque utilice esta lógica cada vez que se produzca unaRequestException.make_request()realiza una solicitud a nuestro punto final de error. Recibe todos los rasgos del decorador que hemos creado anteriormente.
Al ejecutar este código, se obtiene un resultado similar.
Realizando una solicitud con lógica de reintento...
Realizando una solicitud con lógica de reintento...
Realizando una solicitud con lógica de reintento...
❌ La solicitud falló después de todos los reintentos: RetryError[<Future at 0x75e762970760 state=finished raised HTTPError>]
Cree su propio mecanismo de reintento
También puede crear su propio mecanismo de reintento. Cuando se trata de código personalizado, a menudo esta puede ser la mejor opción. Con una cantidad relativamente pequeña de código, podemos lograr el mismo efecto que obtenemos de estas bibliotecas.
En el código siguiente, necesitamos importar sleep para nuestro retroceso exponencial. Una vez más, establecemos nuestra configuración: total, backoff_factor y bad_codes. A continuación, utilizamos un bucle while para mantener nuestra lógica de reintento. Mientras aún tengamos intentos y no hayamos tenido éxito, intentamos la solicitud.
import requests
from time import sleep
#crear una sesión
session = requests.Session()
#definir nuestra configuración de reintentos
total = 3
backoff_factor = 0.3
bad_codes = [429, 500, 502, 503, 504]
#contador de intentos y booleano de éxito
current_tries = 0
success = False
#intentar hasta que tengamos éxito o se agoten los intentos
while current_tries < total and not success:
try:
print("Realizando una solicitud con lógica de reintento...")
response = session.get("https://httpbin.org/status/500")
if response.status_code in bad_codes:
raise requests.exceptions.HTTPError(f"Recibido {response.status_code}, activando reintento")
print("✅ Solicitud exitosa:", response.status_code)
success = True
except requests.exceptions.RequestException as e:
print(f"❌ Solicitud fallida: {e}, reintentos restantes: {total-current_tries}")
sleep(backoff_factor)
backoff_factor = backoff_factor * 2
current_tries+=1
La lógica real aquí se maneja mediante un simple bucle while.
- Si
response.status_codeestá en nuestra lista debad_codes, lanzamos una excepción. - Si una solicitud falla, hacemos lo siguiente:
- Imprimimos un mensaje de error en la consola.
sleep(backoff_factor)espera antes de enviar la siguiente solicitud.backoff_factor = backoff_factor * 2duplica nuestrobackoff_factorpara el siguiente intento.- Incrementamos
current_triespara no permanecer en el bucle indefinidamente.
Este es el resultado de nuestra lógica de reintento personalizada.
Realizando una solicitud con lógica de reintento...
❌ Solicitud fallida: se recibió 500, se activa el reintento, reintentos restantes: 3
Realizando una solicitud con lógica de reintento...
❌ Solicitud fallida: se recibió 500, se activa el reintento, reintentos restantes: 2
Realizando una solicitud con lógica de reintento...
❌ Solicitud fallida: se recibió 500, se activa el reintento, reintentos restantes: 1
Superar los bloqueos
En la práctica, algunos sitios te bloquearán. Lo mejor es utilizar siempre un Proxy con las solicitudes de Python. Con un Proxy, tu solicitud se enruta a través de una máquina diferente. Esto protegerá tu identidad y evitará que tu dirección IP sea bloqueada por el sitio de destino. Incluso tenemos una guía detallada sobre cómo superar los bloqueos de IP. Nuestros Proxies residenciales están diseñados para ayudarte a superar estos retos.
Conclusión
Ahora ya sabe cómo gestionar las solicitudes HTTP fallidas en Python. Tanto si está escribiendo un Scraper, un cliente API o herramientas de automatización, ya sabe cómo gestionar estos problemas. Para evitar todo tipo de solicitudes fallidas, hemos desarrollado productos como la API Web Unlocker y el Navegador de scraping. Estas herramientas gestionan automáticamente las medidas antibots, los retos CAPTCHA y los bloqueos de IP, lo que garantiza un Scraping web fluido y eficiente incluso en los sitios web más difíciles.
Regístrese ahora y comience su prueba gratuita hoy mismo.