W3docs

Python: Trabajo con APIs (requests)

Aprende a usar la librería requests de Python para hacer llamadas GET y POST, enviar cabeceras, parámetros de consulta, manejar respuestas JSON y gestionar errores.

La librería requests es la forma estándar de realizar llamadas HTTP desde Python. Envuelve el módulo de bajo nivel urllib de Python en una API limpia, de modo que obtener una página web o llamar a una API REST toma una sola línea en lugar de diez. Este capítulo cubre todo lo que necesitas: instalar requests, hacer llamadas GET y POST, enviar cabeceras y parámetros de consulta, trabajar con respuestas JSON, subir archivos, usar sesiones y manejar errores de forma robusta.

Instalación

requests no forma parte de la librería estándar, por lo que se instala con pip:

pip install requests

Si estás trabajando dentro de un entorno virtual (recomendado), actívalo primero para que el paquete esté limitado a tu proyecto. Tras la instalación, verifica que funciona:

import requests
print(requests.__version__)   # e.g. 2.32.3

Hacer una petición GET

requests.get() envía una petición HTTP GET y devuelve un objeto Response. Esta es la operación más común — se usa para obtener datos de APIs, páginas web y archivos.

import requests

response = requests.get("https://jsonplaceholder.typicode.com/todos/1")

print(response.status_code)   # 200
print(response.url)           # https://jsonplaceholder.typicode.com/todos/1
print(response.text)          # raw response body as a string

Salida esperada:

200
https://jsonplaceholder.typicode.com/todos/1
{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}

Leer la respuesta como JSON

La mayoría de las APIs modernas devuelven JSON. Llama a .json() en la respuesta en lugar de analizar .text manualmente — llama a json.loads() por ti y devuelve un dict o list de Python.

import requests

response = requests.get("https://jsonplaceholder.typicode.com/todos/1")
data = response.json()

print(data["title"])      # delectus aut autem
print(data["completed"])  # False

Consulta el capítulo de Python JSON para obtener más detalles sobre cómo se corresponden los objetos de Python con los tipos JSON.

Enviar parámetros de consulta

Los parámetros de consulta son los pares clave-valor que aparecen después del ? en una URL, como ?q=python&page=2. Pásalos como un dict al argumento paramsrequests los codifica en la URL y los añade automáticamente.

import requests

params = {
    "q": "python requests",
    "page": 1,
    "per_page": 5,
}

response = requests.get("https://httpbin.org/get", params=params)

# requests builds the full URL for you
print(response.url)
# https://httpbin.org/get?q=python+requests&page=1&per_page=5

Usa siempre params= en lugar de construir la URL a mano — maneja correctamente los caracteres especiales y la codificación.

Enviar cabeceras de petición

Las cabeceras transportan metadatos: tokens de autenticación, preferencias de tipo de contenido, claves de API y más. Pásalas como un dict a headers=:

import requests

headers = {
    "Accept": "application/json",
    "Authorization": "Bearer my-api-token",
    "User-Agent": "MyApp/1.0",
}

response = requests.get("https://httpbin.org/headers", headers=headers)
print(response.json())

Cabeceras comunes que enviarás:

CabeceraPropósito
AuthorizationToken de autenticación (Bearer, Basic, etc.)
Content-TypeFormato del cuerpo de la petición (por ejemplo, application/json)
AcceptFormato que quieres recibir del servidor
User-AgentIdentifica tu cliente ante el servidor
X-API-KeyClave de API en una cabecera personalizada (varía según el servicio)

Hacer una petición POST

requests.post() envía datos al servidor — se usa para crear recursos, enviar formularios o llamar a acciones.

Enviar JSON

Pasa un dict de Python a json=. La librería lo serializa y establece la cabecera Content-Type: application/json automáticamente:

import requests

payload = {
    "title": "Buy groceries",
    "completed": False,
    "userId": 1,
}

response = requests.post(
    "https://jsonplaceholder.typicode.com/todos",
    json=payload,
)

print(response.status_code)   # 201 Created
print(response.json())

Salida esperada:

201
{'title': 'Buy groceries', 'completed': False, 'userId': 1, 'id': 201}

Enviar datos de formulario

Algunas APIs o formularios HTML esperan datos de tipo application/x-www-form-urlencoded. Usa data= en lugar de json=:

import requests

form_data = {
    "username": "alice",
    "password": "secret",
}

response = requests.post("https://httpbin.org/post", data=form_data)
print(response.status_code)

Enviar archivos (subida multipart)

Para subir un archivo, ábrelo en modo binario y pásalo mediante files=:

import requests

with open("report.pdf", "rb") as f:
    response = requests.post(
        "https://httpbin.org/post",
        files={"file": f},
    )

print(response.status_code)

requests codifica la subida como multipart/form-data, que es lo que esperan la mayoría de los endpoints de carga de archivos.

Otros métodos HTTP

Las APIs REST usan distintos verbos HTTP para diferentes operaciones. requests proporciona una función por método:

import requests

base = "https://jsonplaceholder.typicode.com/todos/1"

# Update a resource (replace entirely)
response = requests.put(base, json={"title": "Updated", "completed": True, "userId": 1})
print(response.status_code)   # 200

# Partial update
response = requests.patch(base, json={"completed": True})
print(response.status_code)   # 200

# Delete a resource
response = requests.delete(base)
print(response.status_code)   # 200

Manejo de errores

Verificar códigos de estado

El código de estado HTTP te indica si la petición fue exitosa. Los grupos más importantes:

RangoSignificado
2xxÉxito (200 OK, 201 Created, 204 No Content)
3xxRedirección (manejada automáticamente por requests)
4xxError del cliente (400 Bad Request, 401 Unauthorized, 404 Not Found)
5xxError del servidor (500 Internal Server Error, 503 Service Unavailable)

raise_for_status()

Llamar a .raise_for_status() en una respuesta lanza una excepción HTTPError automáticamente si el código de estado es 4xx o 5xx. Esta es la forma más limpia de fallar rápido ante respuestas incorrectas:

import requests

response = requests.get("https://jsonplaceholder.typicode.com/todos/99999")

try:
    response.raise_for_status()
    data = response.json()
    print(data)
except requests.exceptions.HTTPError as err:
    print(f"HTTP error: {err}")

Sin raise_for_status(), una respuesta 404 parece un éxito — tu código lee un cuerpo de error y lo procesa silenciosamente.

Manejo de errores de red

Los fallos a nivel de red (fallo de resolución DNS, conexión rechazada, tiempo de espera agotado) lanzan requests.exceptions.ConnectionError o requests.exceptions.Timeout. Captura ambos con la clase base requests.exceptions.RequestException:

import requests

try:
    response = requests.get("https://api.example.com/data", timeout=5)
    response.raise_for_status()
    data = response.json()
except requests.exceptions.Timeout:
    print("The request timed out — server took too long to respond.")
except requests.exceptions.ConnectionError:
    print("Could not connect — check your network or the URL.")
except requests.exceptions.HTTPError as err:
    print(f"HTTP error {response.status_code}: {err}")
except requests.exceptions.RequestException as err:
    print(f"Unexpected error: {err}")

Este patrón cubre toda la jerarquía de excepciones: tiempo de espera, conexión, error HTTP y la clase base que captura todo lo demás. Consulta Python try/except para una visión más detallada del manejo de excepciones en Python.

Establece siempre un tiempo de espera

Por defecto, requests esperará indefinidamente si el servidor no responde. Pasa siempre timeout= para evitar que los programas se queden colgados:

# timeout=(connect_timeout, read_timeout) in seconds
response = requests.get("https://api.example.com/data", timeout=(3, 10))

La forma de tupla establece el tiempo de espera de conexión y el tiempo de espera de lectura por separado. Un tiempo de espera de lectura de 10 segundos significa "esperar hasta 10 segundos entre bytes una vez establecida la conexión".

Usar sesiones

Un objeto requests.Session persiste configuraciones — cabeceras, cookies, autenticación — a través de múltiples peticiones al mismo host. También reutiliza la conexión TCP subyacente (agrupación de conexiones), lo que es más rápido que crear una nueva conexión para cada llamada.

import requests

with requests.Session() as session:
    # Set headers once — every request in this session will include them
    session.headers.update({
        "Authorization": "Bearer my-api-token",
        "Accept": "application/json",
    })

    # All requests reuse the connection and headers
    r1 = session.get("https://api.example.com/users")
    r2 = session.get("https://api.example.com/posts")
    r3 = session.post("https://api.example.com/todos", json={"title": "New"})

    print(r1.status_code, r2.status_code, r3.status_code)

Usa una sesión siempre que hagas más de una petición al mismo servidor.

Inspeccionar la respuesta

El objeto Response expone todo lo que el servidor devolvió:

import requests

response = requests.get("https://httpbin.org/get")

print(response.status_code)      # 200
print(response.reason)           # OK
print(response.headers)          # dict of response headers
print(response.headers["Content-Type"])  # application/json
print(response.encoding)         # utf-8
print(response.elapsed)          # how long the request took
print(response.url)              # final URL (after redirects)

Para contenido binario (imágenes, PDFs), usa response.content (devuelve bytes) en lugar de response.text:

import requests

response = requests.get("https://httpbin.org/image/png")
with open("image.png", "wb") as f:
    f.write(response.content)

Autenticación

HTTP Basic Auth

Pasa una tupla (username, password) a auth=:

import requests

response = requests.get(
    "https://httpbin.org/basic-auth/alice/secret",
    auth=("alice", "secret"),
)
print(response.status_code)   # 200

Token Bearer (claves de API)

La mayoría de las APIs modernas usan un token bearer en la cabecera Authorization:

import requests

headers = {"Authorization": "Bearer eyJhbGciOiJIUzI1NiIs..."}
response = requests.get("https://api.example.com/me", headers=headers)

Nunca escribas tokens directamente en los archivos fuente. Cárgalos desde variables de entorno o un gestor de secretos:

import os
import requests

token = os.environ["API_TOKEN"]
headers = {"Authorization": f"Bearer {token}"}
response = requests.get("https://api.example.com/me", headers=headers)

Ejemplo real — GitHub API

Este ejemplo demuestra un patrón completo: sesión, autenticación bearer, paginación, manejo de errores y análisis de JSON:

import os
import requests

GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "")
BASE_URL = "https://api.github.com"

with requests.Session() as session:
    session.headers.update({
        "Authorization": f"Bearer {GITHUB_TOKEN}",
        "Accept": "application/vnd.github+json",
        "X-GitHub-Api-Version": "2022-11-28",
    })

    try:
        # Fetch the first page of public repos for a user
        response = session.get(
            f"{BASE_URL}/users/torvalds/repos",
            params={"per_page": 5, "sort": "updated"},
            timeout=10,
        )
        response.raise_for_status()
        repos = response.json()

        for repo in repos:
            print(f"{repo['name']:40s}{repo['stargazers_count']}")

    except requests.exceptions.HTTPError as err:
        print(f"GitHub API error: {err}")
    except requests.exceptions.RequestException as err:
        print(f"Network error: {err}")

Este patrón — sesión con cabeceras compartidas, raise_for_status(), try/except delimitado — es el enfoque listo para producción para cualquier cliente de API que escribas.

Peticiones concurrentes con asyncio

Para programas que llaman a muchos endpoints a la vez, la librería sincrónica requests bloquea en cada llamada. Cambia a aiohttp (el equivalente asíncrono) y combínalo con el módulo asyncio de Python:

import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.json()

async def main():
    urls = [
        "https://jsonplaceholder.typicode.com/todos/1",
        "https://jsonplaceholder.typicode.com/todos/2",
        "https://jsonplaceholder.typicode.com/todos/3",
    ]
    async with aiohttp.ClientSession() as session:
        results = await asyncio.gather(*[fetch(session, u) for u in urls])
    for r in results:
        print(r["title"])

asyncio.run(main())

Consulta el capítulo de Python asyncio para entender cómo funciona async/await antes de adoptar este patrón.

Referencia rápida

TareaCódigo
Petición GETrequests.get(url)
GET con parámetrosrequests.get(url, params={"key": "val"})
GET con cabecerasrequests.get(url, headers={"Authorization": "Bearer token"})
POST con cuerpo JSONrequests.post(url, json={"key": "val"})
POST con datos de formulariorequests.post(url, data={"key": "val"})
Subir un archivorequests.post(url, files={"file": open("f.pdf", "rb")})
PUT / PATCH / DELETErequests.put/patch/delete(url, json=...)
Verificar estadoresponse.status_code
Lanzar excepción en 4xx/5xxresponse.raise_for_status()
Analizar cuerpo JSONresponse.json()
Cuerpo de texto sin formatoresponse.text
Cuerpo en bytes sin formatoresponse.content
Establecer tiempo de esperarequests.get(url, timeout=5)
Reutilizar conexiónwith requests.Session() as s: ...

Capítulos relacionados

Práctica

Práctica
Which argument sends a Python dict as a JSON body in a POST request?
Which argument sends a Python dict as a JSON body in a POST request?
Was this page helpful?