APIs y web scraping: traer datos de afuera sin morir (ni morir legalmente)
Ningun dataset te llega completo: siempre falta una fuente externa. La via noble es una API: el dueno te sirve JSON ordenado, con paginas y reglas. La via del salvaje es el scraping: arrancar datos de HTML escrito para humanos. Este objeto recorre las dos con codigo resuelto —autenticacion, paginacion, limites de peticiones, analisis de JSON anidado, selectores y almacenamiento— y sobre todo las reglas eticas y legales que separan al profesional del demandado.

Anatomia de una peticion
GET con params y cabeceras, resuelto
Paso 1 · Conceptos
Una peticion = URL base + query params + cabeceras. El servidor responde status + cuerpo. Cabecera clave: Authorization para el token; User-Agent para presentarte.Paso 2 · Codigo
La libreria requests lo hace legible. Status 200 = OK; r.json() convierte el cuerpo a diccionarios Python.import requests URL = 'recurso no disponible' r = requests.get(URL, params={'page': 1, 'limit': 50}, headers={'Authorization': 'Bearer ' + TOKEN}) print(r.status_code); datos = r.json()Paso 3 · Codigos
Status codes que vas a padecer: 200 OK; 401 sin token o malo; 403 token valido pero sin permiso; 404 recurso movido; 429 demasiado trafico (rate limit); 500 el problema es de ellos.Paso 4 · Cortesia
Rate limit: la cabecera X-Ratelimit-Remaining te dice tu credito restante; si ignoras 429 y sigues golpeando, te BANNEAN la IP o el token. Cortesia tecnica: sleep entre paginas, retry con espera CRECIENTE (backoff) cuando el servidor se cae.import time for page in range(1, 21): r = requests.get(URL, params={'page': page}, headers=H) if r.status_code == 429: time.sleep(60); continue guardar(r.json()); time.sleep(1)
Paso 1 de 4
Del JSON anidado a la tabla plana
Problema. La API devuelve cada usuario como {"id": 7, "empresa": {"nombre": "Acme", "ciudad": "MX"}, "posts": [{"titulo": "a"}, {"titulo": "b"}]}. Conviertelo a una fila por usuario con numero de posts.
Aplanar (flatten) resuelto
Paso 1 · Diagnosticar
Identifica la entidad-fila (usuario) y que campos "bajan" de un nivel (empresa.nombre) vs los que EXPLOTRAN una lista (posts → contar).Paso 2 · Codigo
Cada acceso es un riesgo: raso si el campo no viene. get() con default evita KeyError masivos en APIs impredictibles.filas = [] for u in datos: filas.append({ 'id': u.get('id'), 'empresa': u.get('empresa', {}).get('nombre'), 'n_posts': len(u.get('posts', []))})Paso 3 · Normalizar
Lista de listas (posts con detalles propios)? Entonces DOS tablas relacionales: usuarios y posts con clave ajena — el patron de modelado que ya te es familiar de SQL.Paso 4 · Crudo primero
Guarda cruda la respuesta (JSON original en disco o S3) ANTES de aplanar: si manana cambia el esquema, puedes re-parsear sin volver a pedir nada.
Paso 1 de 4
Scraping HTML con respeto
BeautifulSoup: del selector a la fila
Paso 1 · Etica
robots.txt dice lo PROHIBIDO (Disallow), no lo permitido: leelo primero, respeta crawl-delay, y si los Terminos de Servicio prohiben scraping, no hay tecnica que valide violarlos.Paso 2 · Codigo
Elige el selector por estructura, no por texto: clases semanticas de la tarjeta, no el valor que cambia cada dia.import requests from bs4 import BeautifulSoup html = requests.get('recurso no disponible', headers={'User-Agent': 'MiProyecto/1.0'}).text soup = BeautifulSoup(html, 'html.parser') for art in soup.select('article.product_pod'): titulo = art.h2.a.get_text(strip=True) precio = art.select_one('p.price_color').get_text()Paso 3 · Limpieza
get_text y no .text: strip elimina espacios que rompen joins. parsea precio a float ANTES de guardar — el simbolo monetario no es numero.Paso 4 · Infra
Circuit breaker del scraper maduro: cache local (si ya baje la pagina X, no la bajes otra vez), reintentos con backoff, log de URLs fallidas y SIEMPRE identificate con User-Agent honesto. Lo que nunca: sobrecargar un servidor pequeno, sortear bloqueos, o extraer datos personales sin base legal.
Paso 1 de 4
Ruta de ingesta externa
- Datos de afuera
- ¿API oficial?
- Token y params
- Paginacion y rate limit
- ¿Solo HTML?
- robots.txt y ToS
- Selectores semanticos
- Almacenar
- Crudo antes que plano
- Incremental con marca de agua
- ¿API oficial?
Autoexamen cronometrado (15 minutos)
Recibes HTTP 429. La lectura correcta es:
Si una pagina no aparece en el Disallow de robots.txt, tengo permiso legal de extraer todo.
Para leer la respuesta JSON de una peticion requests se usa la funcion del objeto respuesta.
Empareja status con su accion.
Descargas 1 paginas por segundo durante 5 minutos con rate limit de 1000 peticiones/hora. ¿Cuantas peticiones consumiste?
Para profundizar
Real Python: HTTP con requestsTutoriales practicos y gratuitos de la libreria estandar de facto.
Estandar robots.txtComo leerlo y que NO significa: la etica del crawler bien documentada.
Disena la ingesta de tu proyecto favorito: fuente, API o scraping, volumen estimado, rate limit, almacenamiento crudo y frecuencia incremental. ¿Donde esta el punto de tu diseno que un abogado leerian primero?
Tu texto se guarda sólo en este dispositivo.
Comentarios
Inicia sesión para comentar.
Todavía no hay comentarios. Sé la primera persona en opinar.