En esta página
HTTP y requests
¿Qué es HTTP y por qué importa?
HTTP (HyperText Transfer Protocol) es el protocolo que utilizan los navegadores, aplicaciones móviles y servicios backend para comunicarse. En Python, consumir APIs HTTP es una de las tareas más frecuentes: desde obtener datos de servicios externos hasta integrar sistemas.
Los conceptos clave que debes conocer antes de continuar:
- Métodos HTTP:
GET(obtener datos),POST(crear),PUT/PATCH(actualizar),DELETE(eliminar) - URL: la dirección del recurso (
https://api.ejemplo.com/usuarios/42) - Headers: metadatos de la petición/respuesta (
Content-Type,Authorization,Accept) - Body: el cuerpo de la petición (para POST/PUT), generalmente en formato JSON
- Status codes: código numérico que indica el resultado (
200 OK,404 Not Found,500 Internal Server Error)
Instalando requests
pip install requestsrequests es la librería HTTP de Python más popular. Su lema es "HTTP para humanos" — tiene una API mucho más sencilla que el módulo urllib incluido en la biblioteca estándar.
GET — Obtener datos
import requests
# Petición GET simple
respuesta = requests.get("https://jsonplaceholder.typicode.com/posts/1")
# Información de la respuesta
print(respuesta.status_code) # 200
print(respuesta.headers["Content-Type"]) # application/json; charset=utf-8
print(respuesta.encoding) # utf-8
print(respuesta.url) # URL final (después de redirecciones)
# Cuerpo de la respuesta
datos = respuesta.json() # Parsea JSON automáticamente
print(datos["title"])
texto = respuesta.text # Como string
bytes_raw = respuesta.content # Como bytes (para imágenes, PDFs, etc.)
# GET con parámetros de consulta (query params)
params = {
"userId": 1,
"_limit": 5,
"_sort": "id",
"_order": "desc"
}
respuesta = requests.get(
"https://jsonplaceholder.typicode.com/posts",
params=params
)
# URL resultante: .../posts?userId=1&_limit=5&_sort=id&_order=desc
print(f"URL: {respuesta.url}")
posts = respuesta.json()
print(f"Posts obtenidos: {len(posts)}")POST — Enviar datos
import requests
import json
# POST con JSON (el más común en APIs modernas)
nuevo_post = {
"title": "Mi primer post desde Python",
"body": "Contenido del artículo creado con requests",
"userId": 1
}
respuesta = requests.post(
"https://jsonplaceholder.typicode.com/posts",
json=nuevo_post, # Serializa automáticamente y pone Content-Type: application/json
headers={"Authorization": "Bearer mi-token-secreto"}
)
print(respuesta.status_code) # 201 Created
post_creado = respuesta.json()
print(f"ID asignado: {post_creado['id']}")
# POST con datos de formulario (form-data)
respuesta_form = requests.post(
"https://httpbin.org/post",
data={"usuario": "ana", "contraseña": "secreto123"}
# Content-Type: application/x-www-form-urlencoded
)
# POST con archivo multipart
with open("imagen.png", "rb") as img:
respuesta_archivo = requests.post(
"https://httpbin.org/post",
files={"archivo": ("imagen.png", img, "image/png")},
data={"descripcion": "Mi foto de perfil"}
)PUT, PATCH y DELETE
# PUT — reemplazar completamente un recurso
respuesta_put = requests.put(
"https://jsonplaceholder.typicode.com/posts/1",
json={"id": 1, "title": "Título actualizado", "body": "Contenido nuevo", "userId": 1}
)
print(f"PUT: {respuesta_put.status_code}") # 200
# PATCH — actualización parcial
respuesta_patch = requests.patch(
"https://jsonplaceholder.typicode.com/posts/1",
json={"title": "Solo el título cambia"}
)
print(f"PATCH: {respuesta_patch.status_code}") # 200
# DELETE
respuesta_delete = requests.delete("https://jsonplaceholder.typicode.com/posts/1")
print(f"DELETE: {respuesta_delete.status_code}") # 200Manejo robusto de errores
import requests
from requests.exceptions import (
RequestException,
Timeout,
ConnectionError,
HTTPError,
TooManyRedirects
)
def hacer_peticion_segura(url: str, **kwargs) -> dict | None:
"""
Realiza una petición HTTP con manejo completo de errores.
Devuelve los datos JSON o None si hay error.
"""
try:
respuesta = requests.get(url, timeout=(5, 30), **kwargs)
# raise_for_status() lanza HTTPError para códigos 4xx y 5xx
respuesta.raise_for_status()
return respuesta.json()
except Timeout:
print("⚠️ La petición excedió el tiempo de espera")
except ConnectionError:
print("⚠️ No se pudo conectar al servidor")
except TooManyRedirects:
print("⚠️ Demasiadas redirecciones")
except HTTPError as e:
codigo = e.response.status_code
if codigo == 400:
print(f"❌ Petición inválida (400): {e.response.json()}")
elif codigo == 401:
print("❌ No autorizado (401): revisa las credenciales")
elif codigo == 403:
print("❌ Prohibido (403): sin permisos")
elif codigo == 404:
print(f"❌ Recurso no encontrado (404): {url}")
elif codigo == 429:
print("❌ Límite de peticiones excedido (429)")
elif codigo >= 500:
print(f"❌ Error del servidor ({codigo})")
else:
print(f"❌ Error HTTP {codigo}: {e}")
except RequestException as e:
print(f"❌ Error de red inesperado: {e}")
except ValueError:
print("❌ La respuesta no es JSON válido")
return NoneSession — Reutilizar conexiones y configuración
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def crear_sesion(reintentos: int = 3) -> requests.Session:
"""Crea una sesión configurada con reintentos automáticos."""
sesion = requests.Session()
# Configurar reintentos automáticos
estrategia_reintento = Retry(
total=reintentos,
backoff_factor=1, # Espera: 1s, 2s, 4s
status_forcelist=[429, 500, 502, 503, 504],
)
adaptador = HTTPAdapter(max_retries=estrategia_reintento)
sesion.mount("https://", adaptador)
sesion.mount("http://", adaptador)
# Headers comunes para todas las peticiones de esta sesión
sesion.headers.update({
"Authorization": "Bearer mi-token-de-api",
"Accept": "application/json",
"User-Agent": "MiApp/1.0 Python/3.14"
})
return sesion
# Usar la sesión (más eficiente que requests.get() individual)
with crear_sesion() as sesion:
usuarios = sesion.get("https://api.ejemplo.com/usuarios").json()
pedidos = sesion.get("https://api.ejemplo.com/pedidos").json()
# La conexión TCP se reutiliza entre peticioneshttpx — La alternativa moderna con soporte async
pip install httpximport httpx
# API síncrona (casi idéntica a requests)
with httpx.Client(timeout=10.0, base_url="https://jsonplaceholder.typicode.com") as cliente:
respuesta = cliente.get("/users/1")
respuesta.raise_for_status()
usuario = respuesta.json()
print(f"Usuario: {usuario['name']}")
# API asíncrona (ventaja principal de httpx)
import asyncio
async def obtener_multiples_usuarios(ids: list[int]) -> list[dict]:
"""Obtiene múltiples usuarios en paralelo con async."""
async with httpx.AsyncClient(
base_url="https://jsonplaceholder.typicode.com",
timeout=10.0
) as cliente:
# Crear todas las tareas y ejecutarlas concurrentemente
tareas = [cliente.get(f"/users/{uid}") for uid in ids]
respuestas = await asyncio.gather(*tareas)
usuarios = []
for resp in respuestas:
resp.raise_for_status()
usuarios.append(resp.json())
return usuarios
async def main() -> None:
ids = [1, 2, 3, 4, 5]
usuarios = await obtener_multiples_usuarios(ids)
for u in usuarios:
print(f" {u['id']:2}. {u['name']:25} — {u['email']}")
# Ejecutar la función asíncrona
asyncio.run(main())Ejemplo práctico: cliente para una API pública
import requests
from dataclasses import dataclass
@dataclass
class Post:
id: int
user_id: int
title: str
body: str
class ClienteJSONPlaceholder:
"""Cliente para la API de prueba JSONPlaceholder."""
BASE_URL = "https://jsonplaceholder.typicode.com"
def __init__(self) -> None:
self._sesion = requests.Session()
self._sesion.headers["Accept"] = "application/json"
def obtener_posts(self, user_id: int | None = None) -> list[Post]:
"""Obtiene posts, opcionalmente filtrados por usuario."""
params = {"userId": user_id} if user_id else {}
respuesta = self._sesion.get(f"{self.BASE_URL}/posts", params=params, timeout=10)
respuesta.raise_for_status()
return [Post(
id=p["id"],
user_id=p["userId"],
title=p["title"],
body=p["body"]
) for p in respuesta.json()]
def crear_post(self, user_id: int, title: str, body: str) -> Post:
"""Crea un nuevo post."""
respuesta = self._sesion.post(
f"{self.BASE_URL}/posts",
json={"userId": user_id, "title": title, "body": body},
timeout=10
)
respuesta.raise_for_status()
datos = respuesta.json()
return Post(id=datos["id"], user_id=datos["userId"],
title=datos["title"], body=datos["body"])
def __enter__(self) -> "ClienteJSONPlaceholder":
return self
def __exit__(self, *args) -> None:
self._sesion.close()
# Usar el cliente
with ClienteJSONPlaceholder() as cliente:
posts = cliente.obtener_posts(user_id=1)
print(f"Posts del usuario 1: {len(posts)}")
for post in posts[:3]:
print(f" [{post.id}] {post.title[:50]}...")
nuevo = cliente.crear_post(1, "Mi Post de Prueba", "Contenido del post")
print(f"\nPost creado con ID: {nuevo.id}")Resumen
requests es la librería estándar de facto para HTTP en Python: simple, legible y robusta. Usa siempre timeout, raise_for_status() y Session para código de producción. Para aplicaciones asíncronas, httpx ofrece casi la misma API con soporte nativo para async/await. En la próxima lección construiremos una API completa con FastAPI.
Inicia sesión para guardar tu progreso