Menu

Peticiones HTTP en Python: usar la librería requests (GET, POST, JSON)

Cómo hacer peticiones HTTP en Python con la librería requests — GET, POST, parámetros de query, cabeceras, cuerpos JSON y manejo de errores.

Traer datos de internet, en unas pocas líneas

La mayoría de programas Python reales eventualmente necesitan hablar con algo por red — una API REST, un servicio de meteorología, un endpoint de GitHub, una descarga. La librería estándar puede hacer esto (vía urllib), pero la herramienta de facto en el mundo Python es una librería de terceros llamada requests. La API es tan mucho más amigable que vale la pena el único pip install.

pip install requests

Si no estás ya trabajando dentro de un entorno virtual, configura uno primero — mantiene esta instalación dentro del alcance de tu proyecto.

Una primera petición GET

main.py
Output
Click Run to see the output here.

Tres líneas: llama a get, comprueba el código de estado, mira el body. response.text es el body de la respuesta como string. Truncado aquí porque el JSON completo es largo.

Códigos de estado que vale la pena reconocer a nivel de chuleta:

  • 200 — OK, todo funcionó.
  • 201 — Creado (respuesta común a POST).
  • 301 / 302 — redirects; requests los sigue automáticamente por defecto.
  • 400 — bad request; algo de lo que enviaste estaba mal.
  • 401 / 403 — no autenticado / no autorizado.
  • 404 — el recurso no existe.
  • 429 — rate limited; baja el ritmo.
  • 500 — error del servidor.

Parsear una respuesta JSON

Cuando el endpoint devuelve JSON, llama a .json() en la respuesta — parsea el body y te da un dict (o lista):

main.py
Output
Click Run to see the output here.

Por debajo, .json() es lo mismo que json.loads(response.text) — solo un atajo para el caso común.

Enviar parámetros de query

No pegues manualmente ?key=value&... a la URL. Pasa un dict a params=:

main.py
Output
Click Run to see the output here.

requests maneja la codificación de URL por ti — espacios, caracteres especiales, Unicode, todo funciona con seguridad.

La URL real que salió está disponible en response.url, útil para depurar.

Peticiones POST con cuerpos JSON

Para enviar datos — crear recursos, enviar formularios, llamar endpoints mutantes — usa requests.post:

import requests

payload = {
    "title": "Docs update",
    "body": "Added HTTP requests page.",
    "labels": ["docs"],
}

response = requests.post(
    "https://api.example.com/issues",
    json=payload,
    headers={"Authorization": "Bearer YOUR_TOKEN"},
)

print(response.status_code)
print(response.json())

El argumento json= hace dos cosas: serializa payload a JSON y pone Content-Type: application/json. Podrías hacer ambas cosas a mano con data=json.dumps(payload) y una cabecera explícita, pero json= es el atajo idiomático.

Para datos con codificación de formulario (el tipo que envía un formulario HTML clásico), usa data= en su lugar:

requests.post("https://example.com/login", data={"user": "rosa", "password": "..."})

Cabeceras

Pasa cualquier cabecera personalizada como un dict:

import requests

response = requests.get(
    "https://api.example.com/profile",
    headers={
        "Authorization": "Bearer abc123",
        "User-Agent": "my-tool/1.0",
    },
)

La mayoría de APIs quieren una cabecera Authorization para autenticación. El esquema exacto (Bearer, Basic, Token) está en su documentación.

Los timeouts no son opcionales

Por defecto, requests esperará para siempre una respuesta. En un programa real, eso convierte un servidor inestable en un script colgado. Pasa siempre un timeout:

import requests

try:
    response = requests.get("https://api.example.com/slow", timeout=5)
except requests.Timeout:
    print("Server took too long.")

El número son segundos. timeout=5 significa "ríndete si no tenemos respuesta en 5 segundos". Puedes pasar una tupla (connect_timeout, read_timeout) para más control.

Manejo de errores

Pueden pasar dos tipos de problemas:

  1. Errores a nivel HTTP (4xx, 5xx) — el servidor respondió, pero la respuesta es un error. response.status_code te lo dice.
  2. Problemas a nivel de red — timeouts, fallos de DNS, hosts inalcanzables. Estos lanzan excepciones.

El patrón idiomático combina ambos:

main.py
Output
Click Run to see the output here.

raise_for_status() es un no-op para respuestas 2xx y un lanzador de excepción en otro caso. RequestException es la clase base para cada error que requests lanza — un solo catch para "cualquier cosa que fuera mal hablando con este endpoint".

Descargar un archivo

Para descargas binarias grandes, hacer streaming de la respuesta para que no se quede en memoria:

import requests

url = "https://example.com/large.zip"

with requests.get(url, stream=True, timeout=30) as r:
    r.raise_for_status()
    with open("large.zip", "wb") as f:
        for chunk in r.iter_content(chunk_size=8192):
            f.write(chunk)

stream=True le dice a requests que no precargue el body. iter_content(chunk_size=...) produce el body un trozo a la vez, que escribes directo a disco.

Sesiones: reutilizar conexiones y defaults

Si vas a hacer varias peticiones al mismo servicio, usa una Session. Reutiliza la conexión TCP subyacente (más rápido) y te deja establecer defaults una vez:

import requests

session = requests.Session()
session.headers.update({"Authorization": "Bearer abc123"})

# Cada petición a través de esta sesión lleva la cabecera.
a = session.get("https://api.example.com/users/1")
b = session.get("https://api.example.com/users/2")
c = session.post("https://api.example.com/users", json={"name": "Rosa"})

Para scripts que golpean la misma API docenas de veces, una sesión es una aceleración significativa.

Un ejemplo realista: pequeño cliente de GitHub

Traer la última release de un repo:

import requests

def latest_release(owner, repo):
    url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    data = response.json()
    return {
        "tag": data["tag_name"],
        "name": data["name"],
        "published": data["published_at"],
        "url": data["html_url"],
    }

release = latest_release("python", "cpython")
print(release)

Menos de quince líneas: construye la URL, pídela, comprueba errores, extrae los campos que te importan. Esa es la forma de la mayoría de clientes de API que escribirás.

¿Y qué hay de urllib?

El urllib.request de la librería estándar puede hacer todo lo que requests puede — en más líneas y con peor ergonomía. Si absolutamente no puedes añadir una dependencia, está ahí:

import json
import urllib.request

with urllib.request.urlopen("https://api.github.com/repos/python/cpython") as r:
    data = json.loads(r.read().decode("utf-8"))

print(data["name"])

Para cualquier cosa más allá de un script rápido, requests (o httpx si necesitas async) vale la pena la instalación.

Unos cuantos hábitos

  • Pon siempre un timeout. Sin excepciones.
  • Usa raise_for_status() en scripts que esperan éxito — convierte una respuesta mala en una excepción ruidosa.
  • Pasa dicts a params= y json=, no strings construidos a mano.
  • Envuelve llamadas relacionadas en una Session cuando estés golpeando la misma API muchas veces.
  • Loguea response.status_code y response.text cuando depures — el body normalmente te dice exactamente qué le disgustó al servidor.

Siguiente: fechas y horas

Con requests en tu caja de herramientas, puedes hablar con cualquier API web moderna, descargar archivos y construir pequeñas integraciones entre servicios. Combinado con las páginas de JSON y CSV anteriores, ahora tienes el bucle completo "trae datos, léelos, haz algo con ellos, escríbelos de vuelta" — la forma de una enorme cantidad de scripts reales de Python. La mayor parte de esos datos llevan un timestamp, y la siguiente página cubre cómo representa Python fechas, horas y las trampas de timezone que vale la pena evitar.

Preguntas frecuentes

¿Cómo hago una petición HTTP en Python?

Instala la librería requests con pip install requests, luego llama a requests.get(url) para un GET o requests.post(url, json=...) para un POST. El objeto response tiene .status_code, .text, .json() y .headers. Ejemplo: r = requests.get('https://api.example.com/users/1').

¿Debería usar requests o urllib?

requests para cualquier cosa que escribirías a mano — su API es drásticamente más amigable. urllib está incorporado y está bien cuando añadir una dependencia es imposible, pero requiere más código para cosas como cuerpos JSON y sesiones. Para producción, muchos equipos también usan httpx (una librería compatible con requests con soporte async).

¿Cómo envío JSON en una petición POST en Python?

Pasa json={'key': 'value'} a requests.post(...). requests serializa el dict a JSON y pone Content-Type: application/json por ti. No pases data= y json= a la vez — elige uno.

¿Cómo manejo errores con la librería requests?

Comprueba response.status_code (200 significa éxito), o llama a response.raise_for_status() para lanzar una excepción con 4xx/5xx. Los problemas a nivel de red (timeouts, fallos de DNS) lanzan subclases de requests.RequestException — atrapa esa para cubrir ambos.

Aprende a programar con Coddy

COMENZAR