Saltar al contenido

lección 5

Instalar requests: APIs y pedir datos al mundo exterior

Aprende a consumir APIs REST públicas para ingerir datos externos en tus pipelines.

55 min

### ¿Qué es una API? La analogía del restaurante

Imagina que estás en un restaurante. No puedes entrar en la cocina a coger la comida tú mismo: usas al camarero. Le dices "quiero una pizza margarita" (petición), el camarero va a la cocina, y te trae el plato (respuesta). Una API funciona exactamente igual: tu programa (tú) le pide datos a un servidor (la cocina) a través de una interfaz estandarizada (el camarero). No necesitas saber cómo está organizada la cocina por dentro: solo conoces el menú (la documentación de la API).

API significa Application Programming Interface: una interfaz para que programas hablen con otros programas. Cuando abres la app del tiempo en tu móvil, esa app está haciendo una petición a una API meteorológica. Cuando pides un Uber, la app hace peticiones a APIs de mapa, de pricing, de drivers disponibles. El mundo moderno funciona con APIs.

Para ingeniería de datos, las APIs son una fuente de datos fundamental. Quieres datos del tiempo, de mercados financieros, de redes sociales, de tu CRM, de tu sistema de pagos: todos exponen APIs. Tu pipeline ingesta datos de APIs igual que de bases de datos o archivos.

### Paso 1: Instalar requests

1# requests: la librería más usada para hacer peticiones HTTP
2pip install requests==2.32.3
3
4# Verificar
5python -c "import requests; print(requests.__version__)"
6# Salida: 2.32.3

requests: simple, elegante y usada por millones de proyectos

requests es una de esas librerías que definen un estándar. Su autor, Kenneth Reitz, la diseñó para que fuera "para humanos": la API es tan intuitiva que casi puedes adivinar cómo usarla. Fue creada en 2011 porque la librería estándar de Python (urllib) era innecesariamente compleja para tareas comunes.

### Anatomía de una petición HTTP

Una petición HTTP tiene dos partes: tú pides (request) y el servidor responde (response)

### Tu primera petición: JSONPlaceholder

JSONPlaceholder es una API pública gratuita para practicar. Devuelve datos falsos (usuarios, posts, comentarios) pero con la misma estructura que una API real. Perfecta para aprender sin necesitar API keys ni autenticación.

1import requests
2
3# GET = "dame datos" (la operación más común)
4response = requests.get('https://jsonplaceholder.typicode.com/users')
5
6# Verificar que fue exitosa
7print(f"Status code: {response.status_code}") # 200 = OK
8
9# Convertir JSON a diccionario/lista Python
10usuarios = response.json()
11
12print(f"Usuarios recibidos: {len(usuarios)}")
13print(f"Primer usuario: {usuarios[0]['name']}")
14print(f"Su email: {usuarios[0]['email']}")
15print(f"Su ciudad: {usuarios[0]['address']['city']}")

Tu primera petición: pedir usuarios a JSONPlaceholder

### De JSON a DataFrame: el flujo completo

El patrón de ingesta desde API es siempre el mismo: pedir datos → verificar respuesta → convertir a DataFrame → limpiar/transformar. Aquí está el flujo completo:

1import requests
2import pandas as pd
3
4def ingerir_usuarios() -> pd.DataFrame:
5 """Ingiere usuarios desde la API y devuelve un DataFrame limpio."""
6
7 # 1. Pedir datos
8 response = requests.get('https://jsonplaceholder.typicode.com/users')
9 response.raise_for_status() # Lanza error si status != 200
10
11 # 2. Convertir a DataFrame (json_normalize para datos anidados)
12 usuarios = response.json()
13 df = pd.json_normalize(usuarios, sep='_')
14
15 # 3. Seleccionar y renombrar columnas útiles
16 df = df[['id', 'name', 'email', 'address_city', 'company_name']].rename(columns={
17 'address_city': 'ciudad',
18 'company_name': 'empresa'
19 })
20
21 # 4. Limpiar
22 df['email'] = df['email'].str.lower()
23 df['ciudad'] = df['ciudad'].str.title()
24
25 return df
26
27# Ejecutar
28usuarios_df = ingerir_usuarios()
29print(usuarios_df)
30print(f"\nColumnas: {list(usuarios_df.columns)}")

Patrón completo: API → JSON → DataFrame → limpieza

### APIs con parámetros: filtrar desde la petición

Muchas APIs te permiten filtrar datos enviando parámetros en la URL. Es como el WHERE de SQL pero en la petición HTTP. Así evitas pedir TODOS los datos para luego filtrar en Python.

1import requests
2import pandas as pd
3
4# Open-Meteo: API del tiempo gratuita y sin API key
5# Pedir el tiempo de Madrid (lat=40.42, lon=-3.70)
6response = requests.get('https://api.open-meteo.com/v1/forecast', params={
7 'latitude': 40.42,
8 'longitude': -3.70,
9 'daily': 'temperature_2m_max,temperature_2m_min,precipitation_sum',
10 'timezone': 'Europe/Madrid',
11 'forecast_days': 7
12})
13
14response.raise_for_status()
15data = response.json()
16
17# Convertir a DataFrame
18tiempo = pd.DataFrame({
19 'fecha': data['daily']['time'],
20 'temp_max': data['daily']['temperature_2m_max'],
21 'temp_min': data['daily']['temperature_2m_min'],
22 'lluvia_mm': data['daily']['precipitation_sum']
23})
24
25tiempo['fecha'] = pd.to_datetime(tiempo['fecha'])
26print(tiempo)

API del tiempo real: parámetros en la URL filtran los datos pedidos

Usa el parámetro params de requests.get() en vez de construir la URL a mano. requests se encarga de encodear caracteres especiales y formatear la URL correctamente. Menos errores, código más limpio.

### Manejo de errores en APIs: el mundo real falla

Las APIs fallan. El servidor se cae, tu internet se corta, llegas al límite de peticiones (rate limit), la API cambia sin avisarte. Un pipeline robusto maneja TODOS estos casos sin explotar.

1import requests
2import time
3
4def pedir_con_reintentos(url: str, params: dict = None, max_intentos: int = 3) -> dict:
5 """Pide datos a una API con reintentos y backoff exponencial."""
6
7 for intento in range(max_intentos):
8 try:
9 response = requests.get(url, params=params, timeout=10)
10
11 # 4xx (excepto 429): error del cliente, no reintentar
12 if 400 <= response.status_code < 500 and response.status_code != 429:
13 raise Exception(f"Error {response.status_code}: {response.text[:200]}")
14
15 response.raise_for_status()
16 return response.json()
17
18 except requests.exceptions.RequestException as e:
19 if intento < max_intentos - 1:
20 espera = 2 ** intento
21 print(f"Intento {intento+1} fallido: {e}. Esperando {espera}s...")
22 time.sleep(espera)
23 else:
24 print(f"Todos los intentos fallaron para {url}")
25 raise
26
27 raise Exception(f"Fallaron todos los {max_intentos} intentos para {url}")

Reintentos con backoff exponencial: el patrón que todo pipeline de producción necesita

SIEMPRE pon timeout en tus peticiones (requests.get(url, timeout=10)). Sin timeout, tu programa puede quedarse colgado INDEFINIDAMENTE esperando una respuesta que nunca llega. He visto pipelines bloqueados 8 horas por no tener timeout.

### Paginación: cuando la API no te da todo de golpe

Las APIs grandes no devuelven 1 millón de resultados en una sola respuesta. Te dan "páginas" de 100 o 1000 registros y tú debes ir pidiendo página a página. Es como leer un libro: no te dan todo el libro en una frase, te lo dan capítulo a capítulo.

1import requests
2import pandas as pd
3
4def ingerir_posts_paginados() -> pd.DataFrame:
5 """Ingiere todos los posts de JSONPlaceholder página a página."""
6 todos_los_posts = []
7 page = 1
8 per_page = 10
9
10 while True:
11 response = requests.get(
12 'https://jsonplaceholder.typicode.com/posts',
13 params={'_page': page, '_limit': per_page}
14 )
15 response.raise_for_status()
16
17 datos = response.json()
18 if not datos: # Lista vacía = no hay más páginas
19 break
20
21 todos_los_posts.extend(datos)
22 print(f"Página {page}: {len(datos)} posts")
23 page += 1
24
25 df = pd.DataFrame(todos_los_posts)
26 print(f"Total ingerido: {len(df)} posts")
27 return df
28
29posts = ingerir_posts_paginados()
30print(posts.head())

Paginación: pedir datos en bucle hasta que no haya más

## ejercicios

[01]

Pipeline de ingesta del tiempo de 3 ciudades

Escribe una función que ingiera los datos del tiempo de 3 ciudades (Madrid, Barcelona, Sevilla) usando la API de Open-Meteo, combine los resultados en un solo DataFrame con una columna "ciudad", y guarde el resultado como CSV.

Cargando editor...
[02]

Ingerir todos los posts con paginación

La API de JSONPlaceholder tiene 100 posts. Escribe un script que los ingiera todos de 20 en 20 (paginando) y cree un DataFrame con las columnas: id, userId, title. Muestra cuántas peticiones fueron necesarias.

Cargando editor...
[03]

Función con reintentos y logging

Escribe una función pedir_datos_robusto(url) que haga una petición GET con timeout de 5 segundos, reintente hasta 3 veces con backoff exponencial (esperando entre intentos, no después del último), y lance una excepción clara si todos los intentos fallan. No debe reintentar errores 4xx (un 404 no se arregla repitiéndolo).

Cargando editor...

Regístrate para guardar tu progreso.

## comentarios

Reporta erratas, ayuda a otros o comparte tu opinión. Sé constructivo.

Inicia sesión para comentar y responder.

cargando comentarios...