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.csvy pandas los leyó con los tipos correctos (preciofloat64, estrellasint64). Se comprobó además querequestsadivina mal la codificación de ese sitio (ISO-8859-1 en lugar de UTF-8), lo que convierte£51.77en£51.77; la corrección de la sección 5 se verificó.
Tabla de contenidos
- La tarea: raspa un sitio y construye un CSV
- 📖 Antes de raspar: ¿puedo?, ¿debo?
- Analizar el sitio antes de programar
- 📖 Extraer los datos de una página
- 📖 Recorrer varias páginas con cortesía (y sin caracteres rotos)
- 📖 Guardar en CSV y comprobar con pandas
- 🚀 Proyecto del día: construcción por versiones
- 🌟 Proyecto extra: datos abiertos frente a scraping
- 🏗️ Mi Portafolio Profesional — Etapa 12
- 🧩 Comprueba lo aprendido
- 🤖 Programar con apoyo de IA
- 🛠️ Observaciones sobre el material original
- 📋 Matriz de trazabilidad
- 📦 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.comyquotes.toscrape.com. - Datos abiertos (sin scraping): el portal
datos.gov.coofrece 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.txtantes de programar.
3. Analizar el sitio antes de programar
(Sección creada para esta adaptación.)
🔍 Antes de programar
- ¿Qué pregunta quiero responder con los datos? ("¿Los libros mejor calificados son más caros?") La pregunta decide qué columnas necesitas.
- ¿Qué columnas tendrá el CSV? Escríbelas:
titulo, precio_gbp, estrellas, url. - 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 |
- ¿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.)
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_siguienteQué 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).
selectdevuelve 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 diccionarioESTRELLASla 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. urljoinconvierte enlaces relativos (catalogue/page-2.html) en absolutos.- Devuelve también la URL de la página siguiente (o
Nonesi 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.)
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 todosBuenas prácticas incluidas:
max_paginas: empieza con 1 a 3 páginas mientras desarrollas.time.sleep(1): una pausa entre peticiones. (Aquí sí se usasleep: 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-Agenthonesto que identifica tu proyecto.robots.txt: puedes consultarlo desde Python conurllib.robotparser.RobotFileParsery su métodocan_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 usarrespuesta.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.)
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)DictWriterescribe diccionarios: las claves deben coincidir confieldnames.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, usaencoding="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):
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
- Revisión escrita de términos,
robots.txty existencia de API (sección 2). - Al menos 50 filas y 4 columnas, con tipos correctos (números como números).
- Recorrido de varias páginas con pausas.
- 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
- 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.htmly 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.mdcon 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
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
- Buscar el tema en
datos.gov.coy obtener los datos mediante su API o descarga CSV. - Si existe la misma información en una página web, compararla: ¿coinciden los datos? ¿cuál está más actualizada?
- 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):
- Repositorio con el script,
pagina_ejemplo.htmlpara las pruebas, pruebas de la extracción, el CSV (si los términos permiten compartirlo) y un cuaderno o script de análisis. README.mdcon la revisión ética (términos,robots.txt, API, datos personales), la pregunta y la respuesta con un gráfico.DIARIO.mddel Día 93 y ficha enproyectos.json.
10. 🧩 Comprueba lo aprendido
Conceptuales
- ¿Qué tres cosas revisas antes de raspar un sitio?
- ¿Por qué es preferible una API o un portal de datos abiertos al scraping?
- ¿Cómo sabes si un dato se carga con JavaScript?
- ¿Para qué sirven
timeoutytime.sleepen un scraper?
Análisis de código
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.mddel Día 93 y ficha en el portafolio.- Cuaderno de errores: selectores que no encuentran nada, datos cargados con JavaScript,
NoneTypeen la última página, enlaces relativos, codificación£, filas en blanco en el CSV de Windows.