Saltar al contenido
Volver al inicio 100 Días de Código en Python — Día 93: Proyecto de portafolio — Web scraping de datos que te interesan, hacia un CSV

100 Días de Código en Python — Día 93: Proyecto de portafolio — Web scraping de datos que te interesan, hacia un CSV

Nivel: Grado décimo Área: Tecnología e Informática Modalidad: Tutorial práctico Fuente: Curso "100 Days of Code: The Complete Python Pro Bootcamp" (Angela Yu, App Brewery). Contenido del Día 93, proyecto de portafolio profesional (categoría Web Scraping).

Este tutorial adapta pedagógicamente el Día 93. La sección 📝 Contenido original del curso proviene del único archivo de la carpeta: dia93-tarea12.md (enunciado, lista de sitios sugeridos y pregunta de reflexión, en inglés; aquí traducido). El curso no ofrece guía ni solución. Este tutorial muestra el proceso completo con un sitio creado para practicar scraping (books.toscrape.com), para que tú lo apliques a otro sitio de tu elección. Todo lo que no está en ese archivo (ética y legalidad, análisis de los sitios sugeridos, código verificado, paginación, codificación, retos 🟢🟡🟠🔴, proyecto extra y la etapa 12 de Mi Portafolio Profesional) es creación de esta adaptación.

Nota sobre las verificaciones: el 28 de septiembre de 2026, con Python 3.14, requests 2.34.2, Beautiful Soup 4.15.0 y pandas 3.0.6, se ejecutó el código de las secciones 4 a 6 contra books.toscrape.com (3 páginas, con 1 segundo de pausa entre peticiones): se obtuvieron 60 libros, se guardaron en libros.csv y pandas los leyó con los tipos correctos (precio float64, estrellas int64). Se comprobó además que requests adivina mal la codificación de ese sitio (ISO-8859-1 en lugar de UTF-8), lo que convierte £51.77 en £51.77; la corrección de la sección 5 se verificó.


Tabla de contenidos

  1. La tarea: raspa un sitio y construye un CSV
  2. 📖 Antes de raspar: ¿puedo?, ¿debo?
  3. Analizar el sitio antes de programar
  4. 📖 Extraer los datos de una página
  5. 📖 Recorrer varias páginas con cortesía (y sin caracteres rotos)
  6. 📖 Guardar en CSV y comprobar con pandas
  7. 🚀 Proyecto del día: construcción por versiones
  8. 🌟 Proyecto extra: datos abiertos frente a scraping
  9. 🏗️ Mi Portafolio Profesional — Etapa 12
  10. 🧩 Comprueba lo aprendido
  11. 🤖 Programar con apoyo de IA
  12. 🛠️ Observaciones sobre el material original
  13. 📋 Matriz de trazabilidad
  14. 📦 Evidencias de aprendizaje del Día 93

1. La tarea: raspa un sitio y construye un CSV

(Fuente: dia93-tarea12.md)

📝 Contenido original del curso

Usando lo que aprendiste de web scraping, raspa un sitio web para obtener datos que te interesen. Intenta construir un CSV con los datos raspados.

Lo que raspes depende de ti. Algunas sugerencias: estadísticas de jugadores de la NBA; libros y calificaciones de Audible; listados de remates de casas en Miami; datos de juegos de Steam (SteamDB); productos de Alibaba; médicos registrados en Idaho; recetas (Allrecipes); inmuebles (Trulia); canciones (SoundCloud); montañas rusas (RCDB); información nutricional de alimentos.

Pregunta de la tarea — Momento de reflexión: cómo abordaste el proyecto; qué fue difícil y qué fácil; cómo mejorarías; tu mayor aprendizaje; qué harías distinto.

Prerequisitos: HTML (Días 41–42); Beautiful Soup, selectores y robots.txt (Días 45–47); Selenium para páginas con JavaScript (Días 48–53); archivos CSV y pandas (Día 25); requests (Día 33).


2. 📖 Antes de raspar: ¿puedo?, ¿debo?

(Sección creada para esta adaptación.)

Que los datos se vean en tu navegador no significa que puedas copiarlos de forma automática. Antes de escribir una línea de código, revisa:

Pregunta Dónde mirarlo Si la respuesta es "no"
¿Los términos de uso permiten la extracción automática? Pie de página del sitio: "Terms", "Condiciones de uso" Elige otro sitio
¿robots.txt permite a los robots visitar esas páginas? https://sitio.com/robots.txt Respétalo
¿Hay una API oficial o datos abiertos con la misma información? Busca "API" o "datos abiertos" + nombre del sitio Usa la API: es más estable y está permitida
¿Los datos son personales (nombres, direcciones, teléfonos)? Míralos No los recolectes; en Colombia, la Ley 1581 de 2012 protege los datos personales
¿Mis peticiones pueden sobrecargar el sitio? Número de páginas y velocidad Pausas entre peticiones, pocas páginas, guardar en caché

Análisis de los sitios sugeridos por el curso

El enunciado es de 2020. Antes de elegir uno, considera:

  • Datos personales: la lista de médicos registrados y los remates de casas contienen nombres y direcciones de personas reales. Para un proyecto escolar, evítalos.
  • Contenido cargado con JavaScript: varios sitios de estadísticas y comercio electrónico cargan sus datos con JavaScript; requests + Beautiful Soup no verá esos datos (necesitarías Selenium, Días 48–53, o buscar la API que usa la página).
  • Términos restrictivos y protección contra robots: muchos sitios comerciales (tiendas, música, inmuebles) prohíben la extracción automática y bloquean robots.
  • Enlaces con fechas concretas (como el de remates de noviembre de 2020) probablemente ya no muestran esos datos.

Alternativas recomendadas para el aula:

  • Sitios hechos para practicar: books.toscrape.com y quotes.toscrape.com.
  • Datos abiertos (sin scraping): el portal datos.gov.co ofrece miles de conjuntos de datos con API.
  • Sitios de tu interés cuyos términos lo permitan, con pocas páginas y pausas.

🏫 Para el aula: presenta al docente el sitio elegido y tu revisión de términos y robots.txt antes de programar.


3. Analizar el sitio antes de programar

(Sección creada para esta adaptación.)

🔍 Antes de programar

  1. ¿Qué pregunta quiero responder con los datos? ("¿Los libros mejor calificados son más caros?") La pregunta decide qué columnas necesitas.
  2. ¿Qué columnas tendrá el CSV? Escríbelas: titulo, precio_gbp, estrellas, url.
  3. Inspecciona el HTML (clic derecho → Inspeccionar) y anota el selector de cada dato:
Dato Dónde está en books.toscrape.com Selector
Cada libro <article class="product_pod"> article.product_pod
Título completo Atributo title del enlace del <h3> h3 a → ["title"]
Precio <p class="price_color">£51.77</p> p.price_color
Estrellas Clase de <p class="star-rating Three"> p.star-rating → ["class"][1]
Página siguiente <li class="next"><a href="..."> li.next a
  1. ¿El dato está en el HTML? Ver "código fuente" (Ctrl + U): si el dato no aparece ahí pero sí en la página, se carga con JavaScript.

💡 El texto visible del título se recorta con "..." en la lista; el título completo está en el atributo title. Inspeccionar evita este tipo de sorpresas.


4. 📖 Extraer los datos de una página

(Sección creada para esta adaptación. Verificado.)

Python
from urllib.parse import urljoin
from bs4 import BeautifulSoup

ESTRELLAS = {"One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5}

def extraer_libros(html, url_pagina):
    sopa = BeautifulSoup(html, "html.parser")
    libros = []
    for articulo in sopa.select("article.product_pod"):
        enlace = articulo.select_one("h3 a")
        precio = articulo.select_one("p.price_color").get_text(strip=True)
        clase_estrellas = articulo.select_one("p.star-rating")["class"][1]
        libros.append({
            "titulo": enlace["title"],
            "precio_gbp": float(precio.replace("£", "")),
            "estrellas": ESTRELLAS[clase_estrellas],
            "url": urljoin(url_pagina, enlace["href"]),
        })
    siguiente = sopa.select_one("li.next a")
    url_siguiente = urljoin(url_pagina, siguiente["href"]) if siguiente else None
    return libros, url_siguiente

Qué hace cada parte:

  • La función recibe el HTML (texto), no la URL: se puede probar con un fragmento de HTML escrito a mano, sin internet (verificado).
  • select devuelve todos los elementos que cumplen el selector; select_one, el primero.
  • ["class"] devuelve una lista de clases: ["star-rating", "Three"]; la segunda dice las estrellas, y el diccionario ESTRELLAS la convierte en número.
  • Limpiar al extraer: el precio se guarda como número (51.77), no como texto ("£51.77"). Así el CSV se analiza directamente.
  • urljoin convierte enlaces relativos (catalogue/page-2.html) en absolutos.
  • Devuelve también la URL de la página siguiente (o None si es la última).

⚠️ Errores frecuentes

Error Qué ocurre Solución
AttributeError: 'NoneType' object has no attribute 'get_text' Un elemento no existe en algún artículo Comprueba if elemento: antes de usarlo; decide qué guardar si falta
Lista vacía El selector está mal o los datos se cargan con JavaScript Revisa el código fuente (Ctrl + U)
Títulos cortados con "..." Se tomó el texto visible Usa el atributo title
Enlaces que no funcionan Son relativos urljoin

5. 📖 Recorrer varias páginas con cortesía (y sin caracteres rotos)

(Sección creada para esta adaptación. Verificado con 3 páginas.)

Python
import time
import requests

CABECERAS = {"User-Agent": "ProyectoEscolar/1.0 (practica de web scraping)"}

def raspar(url_inicial, max_paginas=3):
    todos, url, paginas = [], url_inicial, 0
    while url and paginas < max_paginas:
        respuesta = requests.get(url, headers=CABECERAS, timeout=10)
        respuesta.raise_for_status()
        respuesta.encoding = "utf-8"
        libros, url = extraer_libros(respuesta.text, url)
        todos.extend(libros)
        paginas += 1
        time.sleep(1)          # cortesía: no saturar el servidor
    return todos

Buenas prácticas incluidas:

  • max_paginas: empieza con 1 a 3 páginas mientras desarrollas.
  • time.sleep(1): una pausa entre peticiones. (Aquí sí se usa sleep: es un script, no una GUI.)
  • timeout=10: sin él, una petición puede quedarse esperando para siempre.
  • raise_for_status(): detiene el programa si el servidor responde con error (404, 500) en lugar de "raspar" una página de error.
  • User-Agent honesto que identifica tu proyecto.
  • robots.txt: puedes consultarlo desde Python con urllib.robotparser.RobotFileParser y su método can_fetch(agente, url) (verificado).

🐞 El caso de £51.77

Verificado: books.toscrape.com envía UTF-8, pero no lo declara en sus cabeceras, y requests supone ISO-8859-1. Resultado: £51.77 llega como £51.77 y float() falla. Soluciones (verificadas):

  • respuesta.encoding = "utf-8" antes de usar respuesta.text, o
  • pasar los bytes a Beautiful Soup: BeautifulSoup(respuesta.content, "html.parser"), que detecta la codificación por sí mismo.

Moraleja: si ves é en lugar de é o  antes de un símbolo, casi siempre es un problema de codificación, no de tus datos.


6. 📖 Guardar en CSV y comprobar con pandas

(Sección creada para esta adaptación. Verificado.)

Python
import csv

with open("libros.csv", "w", newline="", encoding="utf-8") as archivo:
    escritor = csv.DictWriter(archivo, fieldnames=["titulo", "precio_gbp", "estrellas", "url"])
    escritor.writeheader()
    escritor.writerows(libros)
  • DictWriter escribe diccionarios: las claves deben coincidir con fieldnames.
  • newline="" evita líneas en blanco entre filas en Windows.
  • encoding="utf-8" conserva tildes y símbolos. (Si Excel muestra caracteres raros al abrirlo, usa encoding="utf-8-sig".)
  • Los títulos con comas se escriben entre comillas automáticamente: no rompen las columnas.

Alternativa: pd.DataFrame(libros).to_csv("libros.csv", index=False). Más corto, pero exige pandas; csv viene con Python.

Comprueba siempre el CSV con pandas (Día 25):

Python
import pandas as pd
df = pd.read_csv("libros.csv")
print(df.shape)          # verificado: (60, 4) con 3 páginas
print(df.dtypes)         # precio_gbp: float64, estrellas: int64
print(df["estrellas"].value_counts().sort_index())

7. 🚀 Proyecto del día: construcción por versiones

(Desarrolla la tarea dia93-tarea12.md. Las versiones son una propuesta de esta adaptación.)

Problema

Raspar un sitio web de tu interés y construir un CSV con sus datos.

Contexto

Quieres responder una pregunta con datos (sobre libros, videojuegos, deportes, recetas, precios locales…) y los datos solo están en páginas web.

Objetivo

Un script ético y robusto que genere un CSV limpio y un pequeño análisis que responda tu pregunta.

Requisitos funcionales

  1. Revisión escrita de términos, robots.txt y existencia de API (sección 2).
  2. Al menos 50 filas y 4 columnas, con tipos correctos (números como números).
  3. Recorrido de varias páginas con pausas.
  4. Un análisis con pandas (y, si quieres, un gráfico) que responda tu pregunta.

Restricciones técnicas

  • Otro sitio distinto de books.toscrape.com (o, si el docente lo autoriza, quotes.toscrape.com con una pregunta propia).
  • Sin datos personales.
  • Función de extracción probada con HTML guardado en un archivo (sin internet).

Planificación

  1. Pregunta. 2. Sitio y revisión ética. 3. Columnas del CSV. 4. Selectores (tabla de la sección 3). 5. Extraer una página. 6. Recorrer páginas. 7. Guardar. 8. Comprobar con pandas. 9. Analizar. 10. Documentar.

Construcción paso a paso

  • Versión 1 — Una página: descargar una página, guardarla como pagina.html y extraer desde ese archivo.
  • Versión 2 — Limpieza: convertir precios, fechas y calificaciones al tipo correcto.
  • Versión 3 — Paginación: recorrer páginas con pausas y límite.
  • Versión 4 — CSV: guardar y comprobar con pandas.
  • Versión 5 — Robustez: artículos incompletos, errores de red (try/except requests.RequestException), codificación.
  • Versión final — Análisis: responder la pregunta con pandas; README.md con la revisión ética y el diario.

Pruebas

# Caso Resultado esperado
1 extraer_libros con HTML escrito a mano de un libro de 3 estrellas y £10.50 [{'titulo': ..., 'precio_gbp': 10.5, 'estrellas': 3, ...}] y None como siguiente
2 Última página url_siguiente es None y el ciclo termina
3 Un artículo sin precio No se cae el programa; se registra o se deja vacío (según tu decisión)
4 Sin internet Mensaje claro
5 El CSV en pandas Tipos numéricos correctos; sin filas duplicadas (df.duplicated().sum() == 0)
6 Título con coma Se lee como una sola columna

🧪 Prueba y depuración

Python
while True:
    r = requests.get(url)
    sopa = BeautifulSoup(r.text, "html.parser")
    for libro in sopa.select("article.product_pod"):
        datos.append([libro.h3.a.text, libro.select_one(".price_color").text])
    url = sopa.select_one("li.next a")["href"]

Encuentra cinco problemas: no hay pausa ni límite de páginas; no hay timeout; en la última página select_one devuelve None y ["href"] falla; el href es relativo (falta urljoin); se usa el título recortado y el precio como texto con símbolo (y posiblemente mal codificado).

⭐ Extensión del proyecto

  • 🟢 Reto 1 — Aplicación: agregar la categoría de cada libro (o el dato equivalente en tu sitio), visitando la página de detalle de algunos elementos.
  • 🟡 Reto 2 — Ampliación: caché local: guardar cada página descargada en una carpeta y no volver a pedirla si ya existe.
  • 🟠 Reto 3 — Desafío: si tu sitio carga datos con JavaScript, usa Selenium (Días 48–53) o encuentra en la pestaña "Red" del navegador la petición JSON que usa la página y consúmela con requests. Compara ambos enfoques.
  • 🔴 Reto 4 — Pensamiento computacional: ejecuta tu script una vez por semana durante un mes y guarda cada ejecución con fecha. Diseña cómo detectar cambios (elementos nuevos, eliminados, precios que suben o bajan) y cómo presentarlos.

8. 🌟 Proyecto extra: datos abiertos frente a scraping

(Proyecto creado para esta adaptación; no existe en el curso original.)

Situación

La secretaría de ambiente del municipio publica información en su página web, y también existe un portal de datos abiertos. El grupo ecológico del colegio quiere saber cuál fuente conviene usar para un informe sobre calidad del aire (o sobre otro tema que elijan).

Requisitos

  1. Buscar el tema en datos.gov.co y obtener los datos mediante su API o descarga CSV.
  2. Si existe la misma información en una página web, compararla: ¿coinciden los datos? ¿cuál está más actualizada?
  3. Un informe breve: qué fuente elegiste, por qué y con qué limitaciones.

Decisiones que debes tomar (y justificar)

  • ¿Qué criterios usas para decidir que una fuente es confiable?
  • Si las fuentes no coinciden, ¿cuál reportas y cómo lo explicas?

Casos de prueba

  • La descarga de datos abiertos funciona sin raspar HTML.
  • El informe cita la fuente y la fecha de consulta.

9. 🏗️ Mi Portafolio Profesional — Etapa 12

(Proyecto integrador creado para esta adaptación; etapas anteriores en los Días 82 a 92.)

Etapa 12 (hoy):

  1. Repositorio con el script, pagina_ejemplo.html para las pruebas, pruebas de la extracción, el CSV (si los términos permiten compartirlo) y un cuaderno o script de análisis.
  2. README.md con la revisión ética (términos, robots.txt, API, datos personales), la pregunta y la respuesta con un gráfico.
  3. DIARIO.md del Día 93 y ficha en proyectos.json.

10. 🧩 Comprueba lo aprendido

Conceptuales

  1. ¿Qué tres cosas revisas antes de raspar un sitio?
  2. ¿Por qué es preferible una API o un portal de datos abiertos al scraping?
  3. ¿Cómo sabes si un dato se carga con JavaScript?
  4. ¿Para qué sirven timeout y time.sleep en un scraper?

Análisis de código

Python
articulo.select_one("p.star-rating")["class"]

¿Qué devuelve para <p class="star-rating Four">? ¿Cómo obtienes el número 4?

Predicción

urljoin("https://books.toscrape.com/catalogue/page-2.html", "page-3.html"). ¿Qué URL resulta?

Depuración

"Mis precios salen como £51.77 y float() falla." Explica la causa y dos soluciones.

Modificación

Cambia extraer_libros para que devuelva también si el libro está disponible (p.instock.availability).

Creación

Escribe promedio_por_estrellas(df) que devuelva el precio promedio para cada número de estrellas (pista: groupby, Día 74).


11. 🤖 Programar con apoyo de IA

Ciclo de trabajo: formula el problema → inténtalo tú primero → consulta a la IA sobre un punto concreto → lee y entiende → prueba → compara con la documentación oficial → modifica → explica con tus palabras.

Actividad 1 — Selectores. Pega un fragmento de HTML (no la página completa) y pide a una IA el selector CSS de un dato. Pruébalo tú en la consola del navegador con document.querySelectorAll("...") antes de usarlo en Python.

Actividad 2 — Legalidad. Pregunta a una IA si es legal raspar el sitio que elegiste. Luego lee tú los términos del sitio y compara: ¿la IA tenía la información correcta y actualizada? (Las IA no pueden garantizar esto: la fuente es el propio sitio.)

Actividad 3 — Codificación. Pide a una IA que explique por qué aparece é en lugar de é. Comprueba la explicación con la documentación de requests sobre Response.encoding.

Declara el uso: sección "Uso de IA" en el README.md.


12. 🛠️ Observaciones sobre el material original

Archivo Observación Tratamiento
dia93-tarea12.md No menciona ética, términos de uso ni robots.txt (sí se vieron en el Día 45) Sección 2
dia93-tarea12.md Incluye sitios con datos personales (médicos, remates de viviendas) Se desaconsejan para el aula
dia93-tarea12.md Varios sitios sugeridos cargan datos con JavaScript o prohíben robots; un enlace tiene una fecha de 2020 Se analizan en la sección 2 y se proponen alternativas (toscrape, datos abiertos)
books.toscrape.com (usado aquí) No declara su codificación y requests la adivina mal Sección 5, con la corrección verificada

13. 📋 Matriz de trazabilidad

Contenido original Archivo de origen Concepto desarrollado Ejercicio original Reto adicional Proyecto relacionado
Raspar un sitio con datos de interés dia93-tarea12.md Secciones 3 a 5 Scraping a CSV Retos 1 a 3 Secciones 7 y 9
Construir un CSV con los datos dia93-tarea12.md Sección 6 Scraping a CSV Reto 4 Sección 7
Lista de 11 sitios sugeridos dia93-tarea12.md Sección 2 (análisis) — — Sección 8
Momento de reflexión dia93-tarea12.md Sección 7 (versión final) Pregunta de reflexión — Sección 9
(No existe en el curso original) — Ética, datos personales, codificación, cortesía — Datos abiertos frente a scraping Sección 8

14. 📦 Evidencias de aprendizaje del Día 93

  • La revisión ética escrita del sitio elegido.
  • El script funcionando y el CSV comprobado con pandas.
  • Las pruebas de la función de extracción con HTML guardado.
  • La pregunta respondida con datos (tabla o gráfico).
  • DIARIO.md del Día 93 y ficha en el portafolio.
  • Cuaderno de errores: selectores que no encuentran nada, datos cargados con JavaScript, NoneType en la última página, enlaces relativos, codificación £, filas en blanco en el CSV de Windows.