Project Management con Spec Driven DevelopmentBeta

0%
Bloque Técnico-Operativo

Módulo 08

Python Scraping y APIs

6hPythonBeautifulSoupPandashttpx

httpx, beautifulsoup4, pandas. Scraping real de proveedores, monitoreo cíclico de precios y procesamiento de datos para decisiones de producto.

Recursos descargables

Consulta de API con Paginación

Script base para consultar cualquier API REST con httpx: manejo de errores, paginación por página y tope de seguridad

Python

Scraper de Tabla HTML

Extrae la tabla de precios de proveedores.html con BeautifulSoup4 y la exporta a CSV — descarga también el HTML de práctica de este kit

Python

HTML de Práctica — Proveedores

Página estática con la tabla de precios que usa el Scraper de Tabla HTML; guárdala en la misma carpeta que el script

HTML

Scraper de Sitio Real

Scraping de books.toscrape.com (sitio público de práctica): extrae título, precio y stock de cada producto y exporta CSV

Python

Monitoreo Cíclico de Precios

Pipeline de monitoreo con histórico acumulado en CSV y detección del mejor precio; listo para programar con cron (precios simulados para la demo)

Python

Dependencias del Kit

requirements.txt con httpx, beautifulsoup4, pandas y lxml, e instrucciones de entorno virtual

Texto

Configuración pyproject.toml

Alternativa moderna a requirements.txt para gestionar el kit con uv o pip install .

TOML
En este módulo
01
Introducción De 2 horas copiando datos a mano a 30 segundos de un script que trabaja por ti

Hay una escena que se repite en todas las empresas de manufactura.

Lunes, 9 de la mañana. El analista de supply chain abre 15 pestañas del navegador. Entra al portal del proveedor A, copia el precio. Entra al portal del proveedor B, copia el precio. Entra al portal de logística, copia el estado del envío. Pega todo en Excel. El proceso le toma 2 horas.

El miércoles, hay que repetirlo. El viernes, otra vez. Ese no es un trabajo analítico: es un trabajo mecánico que una máquina puede hacer en 30 segundos.

Y ahí es donde entra Python. No es magia ni necesitas ser ingeniero de software para usarlo. Es una herramienta que le dice a tu computadora: "cada lunes a las 9, abre estas páginas, extrae estos datos y déjamelos en una hoja."

En este módulo vas a escribir tu primer script. No uno genérico de tutorial: uno que extrae datos reales (precios, stock, estados) de fuentes externas y los guarda limpios en un archivo que abres en Excel.

Cuando termines vas a tener un superpoder que el 99% de los PMs no tiene: crear tus propias herramientas de datos sin pedirle nada a ingeniería.

Vamos.

2 h
Lo que tarda el proceso manual: 15 pestañas, copiar y pegar en Excel
30 s
Lo que tarda el mismo trabajo con un script de Python
99%
De los PMs que no sabe crear sus propias herramientas de datos

02
Desarrollo teórico-práctico Qué es un script, el pipeline Extraer→Transformar→Guardar y las 3 librerías que lo hacen

2.1. ¿Qué es un Script para un PM?

Un script en Python es un archivo de texto (.py) que contiene instrucciones para la computadora. No se compila ni genera una app: solo se ejecuta, hace su trabajo y termina. No es más complejo que una macro de Excel, pero es mucho más potente porque no depende de Excel.

Regla de oro

Extraer, Transformar, Guardar: todo script de datos del PM hace solo estas tres cosas. Extraer de una fuente (web, API, archivo), transformar (limpiar, estructurar, calcular) y guardar el resultado (CSV, Excel, base de datos). Si entiendes este patrón, entiendes el módulo entero.

2.2. El Pipeline de Datos del PM

① EXTRAER httpx · pide datos a una página web o API open() · lee un archivo HTML/CSV local Entra la materia prima: datos crudos ② TRANSFORMAR BeautifulSoup · parsea el HTML y extrae campos pandas · limpia tipos, ordena y calcula Estaciones de trabajo: del caos a la tabla ③ GUARDAR df.to_csv() · escribe un CSV abrible en Excel Histórico append · acumula consultas en el tiempo Producto terminado: datos listos para analizar
El mismo pipeline de los scripts del hands-on: la fuente entra por arriba, pasa por las librerías y sale un CSV limpio.
🏭
Analogía (Manufactura)

Un script es como una línea de ensamblaje. Entra materia prima (los datos crudos de la web), pasa por estaciones de trabajo (httpx pide, bs4 limpia, pandas estructura) y sale un producto terminado: un CSV listo para análisis.

Antes — proceso manual

  • 15 pestañas abiertas, una por proveedor
  • Copiar y pegar precio por precio en Excel
  • ~2 horas cada lunes, miércoles y viernes
  • Errores de tipeo y celdas pegadas mal
  • Sin histórico: cada semana se sobreescribe

Después — un script

  • Un comando: uv run monitoreo.py
  • Extrae, limpia y estructura solo
  • ~30 segundos, repetible cuando quieras
  • Tipos correctos y columnas consistentes
  • CSV histórico que se acumula automáticamente

2.3. Las Librerías que Vas a Usar

httpx
Hace peticiones HTTP (como cURL, pero desde Python)
Pide datos a páginas web y APIs
Por qué: más moderna y rápida que requests
beautifulsoup4 (bs4)
Parsea HTML y extrae información
Saca datos de páginas web que no tienen API
Por qué: traduce el caos del HTML en datos estructurados
pandas
Estructura datos en tablas (DataFrames)
Ordena, filtra y exporta a CSV/Excel
Por qué: es Excel dentro de Python

03
Paso a paso técnico (Hands-on) Cuatro scripts, de pedir datos a una API a monitorear precios de proveedores
  1. 3.1. Preparar el entorno con uv

    uv es el gestor de entornos virtuales más rápido para Python, creado por Astral (mismos creadores de Ruff). Reemplaza a pip y venv tradicionales.

    Paso 1 — Instalar uv

    Windows (PowerShell como administrador):

    powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
    

    macOS/Linux:

    curl -LsSf https://astral.sh/uv/install.sh | sh
    

    Verifica la instalación:

    uv --version
    

    Paso 2 — Crear el proyecto

    # Crea la carpeta del proyecto
    mkdir scraper-proveedores
    cd scraper-proveedores
    
    # Inicializa el proyecto con Python 3
    uv init
    
    # Activa el entorno virtual
    # En Windows (Git Bash):
    source .venv/Scripts/activate
    # En macOS/Linux:
    # source .venv/bin/activate
    

    Paso 3 — Instalar las librerías

    # Todas las librerías que necesitas en un solo comando
    uv add httpx beautifulsoup4 pandas lxml
    

    lxml es el motor de parsing que usa beautifulsoup4. Es opcional pero hace todo más rápido.

  2. 3.2. Tu primer script: pedir datos a una API

    Crea un archivo 01-consulta-api.py:

    import httpx
    
    url = "https://jsonplaceholder.typicode.com/posts"
    
    try:
        respuesta = httpx.get(url, timeout=30.0)
        respuesta.raise_for_status()
        datos = respuesta.json()
        print(f"Se obtuvieron {len(datos)} registros")
        for post in datos[:3]:
            print(f"ID: {post['id']} — Título: {post['title']}")
    except httpx.RequestError as e:
        print(f"Error de conexión: {e}")
    except httpx.HTTPStatusError as e:
        print(f"Error HTTP: {e.response.status_code}")
    

    Ejecútalo:

    uv run 01-consulta-api.py
    

    Qué hace cada línea:

    • import httpx → Trae la librería.
    • httpx.get(url, timeout=30.0) → Pide datos a la URL. timeout evita que el script se cuelgue si el servidor no responde.
    • respuesta.raise_for_status() → Si el servidor devolvió un error (404, 500), lanza una excepción.
    • respuesta.json() → Convierte la respuesta en una lista/diccionario de Python.
    • try/except → Si algo falla (red caída, servidor caído), el script no se rompe: muestra un mensaje y termina ordenadamente.
  3. 3.3. Segundo script: scraping de una página web (simulada)

    Vamos a hacer scraping de una página simulada de proveedores de manufactura. Primero creamos la página HTML local para practicar, luego veremos cómo hacerlo con una página real.

    Paso 1 — Crear una página HTML simulada

    Crea proveedores.html:

    <!DOCTYPE html>
    <html>
    <head><title>Portal de Proveedores — Manufactura</title></head>
    <body>
      <h1>Listado de Precios — Proveedores</h1>
      <table id="tabla-precios">
        <tr>
          <th>Proveedor</th>
          <th>Material</th>
          <th>Precio Unitario (USD)</th>
          <th>Stock (unidades)</th>
          <th>Tiempo Entrega (días)</th>
        </tr>
        <tr>
          <td>ACME Corp</td>
          <td>Acero Inoxidable 304</td>
          <td>12.50</td>
          <td>5000</td>
          <td>14</td>
        </tr>
        <tr>
          <td>Industrial Parts SA</td>
          <td>Rodamiento SKF 6205</td>
          <td>8.75</td>
          <td>1200</td>
          <td>7</td>
        </tr>
        <tr>
          <td>Global Logistics Co</td>
          <td>Sensor de Temperatura PT100</td>
          <td>45.00</td>
          <td>300</td>
          <td>21</td>
        </tr>
        <tr>
          <td>ACME Corp</td>
          <td>Rodamiento SKF 6205</td>
          <td>9.00</td>
          <td>800</td>
          <td>10</td>
        </tr>
        <tr>
          <td>QualiParts Inc</td>
          <td>Acero Inoxidable 304</td>
          <td>11.80</td>
          <td>3000</td>
          <td>7</td>
        </tr>
      </table>
      <p class="nota">* Precios sujetos a cambio sin previo aviso</p>
    </body>
    </html>
    

    Paso 2 — Escribir el scraper

    Crea 02-scraper-proveedores.py:

    import httpx
    from bs4 import BeautifulSoup
    import pandas as pd
    from pathlib import Path
    
    # 1. LEER EL HTML (desde archivo local)
    ruta_html = Path("proveedores.html")
    
    try:
        with open(ruta_html, "r", encoding="utf-8") as archivo:
            html = archivo.read()
    except FileNotFoundError:
        print(f"Error: No se encontró el archivo {ruta_html}")
        exit(1)
    
    # 2. PARSEAR EL HTML CON BEAUTIFULSOUP
    soup = BeautifulSoup(html, "lxml")
    
    # 3. ENCONTRAR LA TABLA
    tabla = soup.find("table", id="tabla-precios")
    if not tabla:
        print("Error: No se encontró la tabla de precios")
        exit(1)
    
    # 4. EXTRAER ENCABEZADOS
    encabezados = []
    for th in tabla.find("tr").find_all("th"):
        encabezados.append(th.text.strip())
    
    # 5. EXTRAER FILAS DE DATOS
    datos = []
    for fila in tabla.find_all("tr")[1:]:  # Saltar la fila de encabezados
        columnas = fila.find_all("td")
        if len(columnas) == len(encabezados):
            fila_datos = {
                encabezados[0]: columnas[0].text.strip(),
                encabezados[1]: columnas[1].text.strip(),
                encabezados[2]: float(columnas[2].text.strip()),
                encabezados[3]: int(columnas[3].text.strip()),
                encabezados[4]: int(columnas[4].text.strip()),
            }
            datos.append(fila_datos)
    
    # 6. CREAR DATAFRAME CON PANDAS
    df = pd.DataFrame(datos)
    
    # 7. ANÁLISIS BÁSICO
    print("=== DATOS EXTRAÍDOS ===")
    print(df.to_string(index=False))
    
    print("\n=== RESUMEN ===")
    print(f"Total de registros: {len(df)}")
    print(f"Proveedores únicos: {df['Proveedor'].nunique()}")
    print(f"Materiales únicos: {df['Material'].nunique()}")
    print(f"Precio promedio: ${df['Precio Unitario (USD)'].mean():.2f}")
    print(f"Stock total: {df['Stock (unidades)'].sum():,}")
    
    # 8. GUARDAR A CSV
    archivo_salida = "datos_proveedores.csv"
    df.to_csv(archivo_salida, index=False)
    print(f"\n✅ Datos guardados en: {archivo_salida}")
    

    Ejecútalo:

    uv run 02-scraper-proveedores.py
    
  4. 3.4. Tercer script: scraping de una página web real

    Ahora vamos a hacer scraping de datos reales. Usaremos una página pública que no requiere autenticación.

    Scraping ético

    Siempre verifica el robots.txt de un sitio antes de hacerle scraping (ej. sitio.com/robots.txt). Algunos sitios lo prohíben: respeta las reglas, no satures el servidor con peticiones y prefiere siempre una API oficial cuando exista.

    Crea 03-scraper-real.py:

    import httpx
    from bs4 import BeautifulSoup
    import pandas as pd
    
    url = "https://books.toscrape.com/"
    
    try:
        respuesta = httpx.get(url, timeout=30.0)
        respuesta.raise_for_status()
    except httpx.RequestError as e:
        print(f"Error de conexión: {e}")
        exit(1)
    except httpx.HTTPStatusError as e:
        print(f"Error HTTP: {e.response.status_code}")
        exit(1)
    
    soup = BeautifulSoup(respuesta.text, "lxml")
    
    libros = soup.find_all("article", class_="product_pod")
    
    datos = []
    for libro in libros:
        titulo = libro.h3.a["title"]
        precio_texto = libro.find("p", class_="price_color").text
        precio = float(precio_texto.replace("£", "").replace("Â", ""))
        disponibilidad = libro.find("p", class_="instock availability")
        stock = "En stock" if disponibilidad else "Agotado"
    
        datos.append({
            "titulo": titulo,
            "precio": precio,
            "stock": stock,
        })
    
    df = pd.DataFrame(datos)
    
    print("=== LIBROS DISPONIBLES ===")
    print(df.to_string(index=False))
    
    print(f"\n=== RESUMEN ===")
    print(f"Total de libros: {len(df)}")
    print(f"Precio promedio: £{df['precio'].mean():.2f}")
    print(f"Más caro: £{df['precio'].max():.2f}")
    print(f"Más barato: £{df['precio'].min():.2f}")
    
    df.to_csv("libros_scrapeados.csv", index=False)
    print("\n✅ Datos guardados en: libros_scrapeados.csv")
    
  5. 3.5. Cuarto script: monitoreo de precios de proveedores

    Este script simula un caso real de supply chain: monitorear precios de un material específico en múltiples proveedores.

    Crea 04-monitoreo-proveedores.py:

    """
    Simulador de monitoreo de precios de proveedores.
    En un escenario real, cada proveedor sería una URL distinta.
    Aquí simulamos consultas a una API local para demostrar el patrón.
    """
    import httpx
    import pandas as pd
    from datetime import datetime
    
    proveedores = [
        {"nombre": "ACME Corp", "url": "https://jsonplaceholder.typicode.com/posts/1"},
        {"nombre": "Industrial Parts", "url": "https://jsonplaceholder.typicode.com/posts/2"},
        {"nombre": "QualiParts Inc", "url": "https://jsonplaceholder.typicode.com/posts/3"},
        {"nombre": "Global Logistics", "url": "https://jsonplaceholder.typicode.com/posts/4"},
    ]
    
    resultados = []
    
    for proveedor in proveedores:
        try:
            respuesta = httpx.get(proveedor["url"], timeout=15.0)
            respuesta.raise_for_status()
            data = respuesta.json()
    
            # Simulamos extraer datos relevantes del JSON
            resultados.append({
                "proveedor": proveedor["nombre"],
                "material": "Rodamiento SKF 6205",
                "precio_unitario": round(abs(hash(data["title"])) % 20 + 5, 2),
                "stock": int(abs(hash(data["body"])) % 5000 + 100),
                "fecha_consulta": datetime.now().strftime("%Y-%m-%d %H:%M"),
                "estado": "OK",
            })
        except httpx.RequestError as e:
            resultados.append({
                "proveedor": proveedor["nombre"],
                "material": "Rodamiento SKF 6205",
                "precio_unitario": 0,
                "stock": 0,
                "fecha_consulta": datetime.now().strftime("%Y-%m-%d %H:%M"),
                "estado": f"Error: {e}",
            })
        except httpx.HTTPStatusError as e:
            resultados.append({
                "proveedor": proveedor["nombre"],
                "material": "Rodamiento SKF 6205",
                "precio_unitario": 0,
                "stock": 0,
                "fecha_consulta": datetime.now().strftime("%Y-%m-%d %H:%M"),
                "estado": f"HTTP {e.response.status_code}",
            })
    
    df = pd.DataFrame(resultados)
    
    print("=== MONITOREO DE PROVEEDORES ===")
    print(df.to_string(index=False))
    
    # Análisis: ¿cuál es el mejor precio?
    if df[df["estado"] == "OK"].empty:
        print("\n⚠️  Todos los proveedores fallaron. Revisa conexiones.")
    else:
        mejor = df.loc[df["precio_unitario"].idxmin()]
        print(f"\n🏆 Mejor precio: {mejor['proveedor']} — ${mejor['precio_unitario']:.2f}")
        print(f"   Stock disponible: {mejor['stock']:,} unidades")
    
    # Guardar histórico (modo append)
    try:
        historico = pd.read_csv("historico_precios.csv")
        df_completo = pd.concat([historico, df], ignore_index=True)
    except FileNotFoundError:
        df_completo = df
    
    df_completo.to_csv("historico_precios.csv", index=False)
    print(f"\n✅ Historial actualizado: {len(df_completo)} registros totales")
    

04
Cómo usar esto en tus procesos reales Del script suelto a una rutina automatizada que corre sola cada mañana

4.1. El Flujo Semanal de Monitoreo Automatizado

① Configuración inicial
1 hora · única vez
Identifica 3 fuentes que consultas manualmente cada semana (precios de proveedores, estado de envíos, métricas de competidores). Escribe un script por fuente y pruébalo individualmente.
② Automatización
30 min
Programa los scripts para que se ejecuten solos: en Windows con el Programador de Tareas (Task Scheduler), en macOS/Linux con cron.
③ Revisión diaria
5 min
Abre el CSV y mira los datos frescos. Si algo cambió drásticamente (precio subió 20%, stock cayó a cero), investiga.

4.2. Programar el Script para que se Ejecute Solo

Windows (Task Scheduler):

  1. Abre "Task Scheduler".
  2. Crea una tarea básica.
  3. Disparador: "Daily" a las 8:00 AM.
  4. Acción: "Start a program".
    • Programa: C:\Users\TU_USUARIO\.local\bin\uv.exe
    • Argumentos: run C:\ruta\completa\04-monitoreo-proveedores.py
    • Iniciar en: C:\ruta\completa\
Rutas completas

Usa siempre la ruta completa al ejecutable de uv y al script. El programador de tareas no sabe dónde está tu proyecto: si pones rutas relativas, la tarea fallará en silencio.

4.3. Diagnóstico Rápido

Pregunta 1
¿Hay datos que consultas manualmente cada semana?

→ ¿podrías automatizarlos con un script de 30 líneas? Probablemente sí.
No → ¿seguro? Revisa tu calendario de la semana pasada.

Pregunta 2
¿Cuándo cometiste por última vez un error copiando datos a mano?

Esta semana → urgente automatizar.
El mes pasado → ya es tarde para no automatizar.
Nunca → o eres muy preciso, o no te has dado cuenta.

Pregunta 3
¿Tus scripts sobreviven a un error de red?

Tienen try/except → sí, fallan ordenadamente.
No lo tienen → van a romperse y no te vas a enterar.

4.4. Entregable del Módulo

🛠 Construye tu script ETL de datos

Parte A — Script de extracción. Crea un script en Python que:

  1. Tome datos de una fuente externa (API pública, página web o el HTML simulado de este módulo).
  2. Use httpx para la petición (o lectura de archivo).
  3. Use beautifulsoup4 para parsear (si es HTML) o .json() (si es API).
  4. Use pandas para estructurar los datos en un DataFrame.
  5. Guarde los resultados en un CSV.
  6. Incluya manejo de errores con try/except para al menos: error de conexión, error HTTP y archivo no encontrado (si lee local).
  7. Imprima un resumen en la terminal al ejecutarse.

Parte B — Documentación.

  • Un párrafo explicando qué hace el script y cada cuánto debería ejecutarse.
  • Instrucciones de cómo ejecutarlo (un comando: uv run script.py).

Formato de entrega: archivo .py funcional (que corra sin errores) + CSV de salida generado por el script + captura de la terminal mostrando la ejecución y su resultado.


⚠ Errores comunes
  • Error: scrapear sin revisar robots.txt ni los términos del sitio. Corrección: respeta las reglas del sitio: datos públicos, ritmo razonable y identificación honesta.
  • Error: selectores CSS frágiles que mueren con el primer rediseño. Corrección: ancla a atributos estables (id, data-*) y valida que el selector siga devolviendo datos.
  • Error: un script sin try/except que muere en el registro 47 de 500. Corrección: captura el error, registra qué registro falló y continúa: el lote termina y tú depuras después.
  • Error: no fijar versiones de dependencias. Corrección: congela versiones exactas en requirements.txt/pyproject.toml o el script morirá en 6 meses.
  • Error: sobrescribir el CSV anterior en cada corrida. Corrección: guarda un archivo por fecha: la serie histórica es la mitad del valor del monitoreo.
05
Rúbrica de evaluación Aprobación: 2 de 3 criterios en "Aprobado" o superior
Criterio No Aprobado (0) Aprobado (1) Sobresaliente (2)
1. El script se ejecuta sin errores y produce datos limpios El script tiene errores de sintaxis, no corre, o produce datos corruptos (mezcla tipos, columnas mal nombradas) El script corre y produce un CSV, pero los datos tienen problemas menores (espacios extras, tipos incorrectos, filas duplicadas) El script corre limpiamente, el CSV tiene tipos correctos (números como números, no como texto), encabezados claros, y sin filas vacías
2. Manejo de errores cubre al menos 2 escenarios de fallo No hay try/except o solo hay un bloque genérico que oculta errores Hay try/except pero captura errores genéricamente sin distinguir entre error de conexión y error HTTP Hay bloques separados para httpx.RequestError, httpx.HTTPStatusError, y al menos un error adicional; cada uno con un mensaje específico
3. El script tiene un propósito claro para el negocio El script extrae datos sin un objetivo claro (no hay resumen, no hay análisis, no hay contexto de por qué esos datos importan) El script extrae datos útiles y muestra un resumen, pero no responde una pregunta de negocio específica El script responde una pregunta concreta: "¿qué proveedor tiene el mejor precio?", "¿cómo han cambiado los precios esta semana?", y el resumen final comunica esa respuesta claramente

Aprobación: 2 de 3 criterios en "Aprobado" o superior.

🧭
Para avanzar al Módulo 9

Modifica tu script para que, en lugar de imprimir en pantalla, envíe los resultados a un Google Sheet usando las APIs de Google. Si puedes hacer eso, estás listo para Apps Script y GCP.

Para llevar
  • Todo script de datos del PM hace solo tres cosas: Extraer, Transformar, Guardar. Ese es el pipeline completo.
  • Tres librerías bastan: httpx pide, beautifulsoup4 parsea HTML, pandas estructura y exporta a CSV.
  • Usa uv para crear el entorno e instalar dependencias en un solo comando, y uv run para ejecutar.
  • Un script sin try/except se rompe en silencio: maneja error de conexión, error HTTP y archivo no encontrado por separado.
  • El scraping es ético: respeta el robots.txt, no satures el servidor y prefiere una API oficial cuando exista.
  • El valor no está en extraer datos, sino en responder una pregunta de negocio: ¿qué proveedor tiene el mejor precio?

Kit: Python Scraping / APIs

ArchivoDescripción
⬇ 01-consulta-api.py Script básico: consulta a API pública con httpx
⬇ 02-scraper-proveedores.py Scraping local con HTML simulado de proveedores
⬇ 03-scraper-real.py Scraping real con URL pública configurable
⬇ 04-monitoreo-proveedores.py Monitoreo periódico de proveedores con pandas
⬇ proveedores.html HTML simulado con datos de proveedores para testing
⬇ requirements.txt Dependencias: httpx, beautifulsoup4, pandas, lxml

📁 kits/m08-python/