Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

La ruta más clara para empezar es descargar una página con requests, comprobar la respuesta, extraer campos con Beautiful Soup y guardar resultados en JSON o CSV. Cuando necesites seguir muchos enlaces, programar paginación, limitar la velocidad y exportar grandes volúmenes, pasa a Scrapy. Este tutorial construye un scraper pequeño y acotado, muestra cómo inspeccionar fallos y explica cuándo conviene cambiar de herramienta.

Antes de escribir código: define qué vas a extraer

Scrapear es extraer datos de documentos que ya has descargado; crawlear (rastrear) es descubrir y solicitar páginas siguiendo enlaces. Un proyecto puede hacer ambas cosas, pero conviene separarlas desde el diseño.

Especifica el resultado

  • URL inicial y, si existe, el patrón de paginación.
  • Campos exactos: por ejemplo, título, precio, categoría y enlace.
  • Formato de salida: JSON para conservar estructuras o CSV para hojas de cálculo.
  • Límite de páginas y frecuencia máxima de solicitudes para no sobrecargar el sitio.

Confirma primero que esos datos aparecen en el HTML que devuelve el servidor. Si solo se generan después con JavaScript, un parser de HTML no los verá; inspecciona la respuesta real antes de añadir complejidad.

Prepara el entorno

  1. Crea un entorno virtual: python -m venv .venv.
  2. Actívalo (Linux/macOS: source .venv/bin/activate; Windows PowerShell: .venvScriptsActivate.ps1).
  3. Instala las dependencias: python -m pip install requests beautifulsoup4 lxml.

Beautiful Soup es una biblioteca para extraer datos de HTML y XML y ofrece un árbol navegable; la descarga HTTP la realiza otra biblioteca. Consulta su documentación oficial para los métodos y parsers disponibles.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Primer scraper funcional con requests y Beautiful Soup

El siguiente ejemplo obtiene tarjetas de un catálogo de demostración. Sustituye los selectores por los que existan en tu página; las clases de un sitio no son universales ni permanentes.

from __future__ import annotations

import json
from urllib.parse import urljoin

import requests
from bs4 import BeautifulSoup

URL = "https://example.com/catalogo"
HEADERS = {"User-Agent": "tutorial-scraper/1.0 (contacto: [email protected])"}

response = requests.get(URL, headers=HEADERS, timeout=30)
response.raise_for_status()
response.encoding = response.apparent_encoding or response.encoding

soup = BeautifulSoup(response.text, "lxml")
items = []
for card in soup.select("article.product-card"):
    title_node = card.select_one("h2")
    price_node = card.select_one(".price")
    link_node = card.select_one("a[href]")
    items.append({
        "titulo": " ".join(title_node.get_text(" ", strip=True).split()) if title_node else None,
        "precio": " ".join(price_node.get_text(" ", strip=True).split()) if price_node else None,
        "url": urljoin(response.url, link_node["href"]) if link_node else None,
    })

with open("productos.json", "w", encoding="utf-8") as f:
    json.dump(items, f, ensure_ascii=False, indent=2)

print(f"Extraídos: {len(items)}")

Qué hace cada parte

  • raise_for_status() detiene el programa ante respuestas HTTP de error en vez de guardar una página de error como si fueran datos.
  • timeout=30 evita que una conexión quede bloqueada indefinidamente.
  • select() devuelve todos los elementos que coinciden con un selector CSS; select_one() toma el primero.
  • get_text(" ", strip=True) elimina etiquetas y normaliza espacios. El valor None hace visible un campo ausente en lugar de inventarlo.
  • urljoin() convierte enlaces relativos en URL absolutas usando la URL final de la respuesta (útil tras redirecciones).

Guarda una copia de response.text durante el desarrollo. Abre ese HTML, busca literalmente una parte del título y comprueba en las herramientas del navegador que el selector apunta al mismo nodo. Si la estructura cambia, actualiza los selectores y añade una prueba para detectar que el número de tarjetas no cae inesperadamente.

Normalización y validación de datos

Normaliza solo lo necesario: espacios, separadores de miles y campos vacíos. Conserva el texto original si una transformación puede perder información. Para precios, por ejemplo, guarda el texto visible y una columna numérica propia después de decidir cómo tratar monedas y decimales.

  • Comprueba que la respuesta tiene el tipo esperado (Content-Type: text/html).
  • Registra URL, código de estado y cantidad de registros por página.
  • Elimina duplicados con una clave estable, como la URL del producto, no con el título que puede repetirse.
  • Escribe primero un lote pequeño y revisa manualmente varias filas.

Añade paginación sin perder el control

Empieza con un máximo explícito. Un patrón de enlaces «Siguiente» puede implementarse así:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from urllib.parse import urljoin

MAX_PAGES = 5
url = "https://example.com/catalogo"
all_items = []

for page_number in range(1, MAX_PAGES + 1):
    r = requests.get(url, headers=HEADERS, timeout=30)
    r.raise_for_status()
    soup = BeautifulSoup(r.text, "lxml")

    for card in soup.select("article.product-card"):
        link = card.select_one("a[href]")
        all_items.append({
            "titulo": card.select_one("h2").get_text(" ", strip=True),
            "url": urljoin(r.url, link["href"]) if link else None,
        })

    next_link = soup.select_one("a[rel='next'], a.next")
    if not next_link or not next_link.get("href"):
        break
    url = urljoin(r.url, next_link["href"])

print(f"Páginas visitadas: {page_number}; filas: {len(all_items)}")

El selector de «siguiente» depende del marcado. Detén el bucle si la URL ya visitada vuelve a aparecer, si no hay resultados o si alcanzas tu límite. Para CSV, usa csv.DictWriter con una lista fija de nombres de columna y newline="" para evitar líneas en blanco en algunos sistemas.

Cuándo pasar a Scrapy

Scrapy es un framework de rastreo: sus spiders analizan respuestas, producen elementos estructurados y pueden programar nuevas solicitudes. La visión general de Scrapy describe su planificador, selectores, concurrencia y exportaciones; el tutorial oficial recorre la creación del proyecto, un spider, el seguimiento de enlaces y la exportación.

Necesidad requests + Beautiful Soup Scrapy
Una o pocas páginas Script corto y control directo Configuración adicional
Seguir enlaces y paginación Bucles que debes mantener Solicitudes programadas por spiders
Varios dominios o muchas reglas Debes construir cola, reintentos y límites Componentes integrados para esas tareas
Exportar resultados Implementas JSON/CSV Feed exports configurables

La elección anterior es funcional, no una comparación de velocidad. Para crear un proyecto, ejecuta scrapy startproject mi_crawler, genera un spider con scrapy genspider catalogo example.com y prueba un primer lote con scrapy crawl catalogo -O items.json. Empieza con un dominio y pocos registros antes de ampliar el alcance.

Ritmo, concurrencia y cortesía

Configura un retraso de descarga, limita la concurrencia por dominio y considera AutoThrottle. La documentación de Scrapy explica estas opciones en su descripción general. Usa valores conservadores, observa respuestas y errores, y detén el crawler si el sitio empieza a devolver bloqueos o códigos anómalos. No conviertas una tarea de datos en una avalancha de solicitudes.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

robots.txt, condiciones y responsabilidad

Scrapy incluye RobotsTxtMiddleware. Solo filtra solicitudes cuando el middleware está habilitado y la configuración ROBOTSTXT_OBEY está activa; revisa la documentación del middleware y el comportamiento de tu parser.

Un archivo robots.txt no resuelve por sí solo la autorización legal. Lee las condiciones del sitio, identifica la jurisdicción, trata los datos personales con especial cuidado y limita la recopilación a lo necesario. La visibilidad pública tampoco equivale automáticamente a permiso para almacenar o republicar. Para una decisión legal concreta necesitas asesoramiento conforme a tu país y uso.

Si la página depende de JavaScript

Primero compara el HTML descargado con lo que ves en el navegador y busca una API pública documentada o datos embebidos. No asumas que un navegador automatizado es siempre la respuesta: añade complejidad, consumo y nuevos puntos de fallo. Capturar una imagen sirve para inspección visual o documentación, pero no sustituye extraer campos estructurados.

Or skip the browser setup

Si solo necesitas una captura para comprobar cómo se ve una página antes de analizarla, ScreenshotNeo devuelve PNG, JPEG, WebP o PDF con una petición GET. Acepta el banner de cookies como un visitante y elimina más de 60 plataformas de consentimiento, popups de newsletters y widgets de chat antes de capturar; cada paso puede desactivarse. Las comprobaciones de bots/CAPTCHA, páginas en blanco, tiempos de espera, cargas fallidas y aciertos de caché no se facturan, y las cabeceras X-Page-Verdict y X-Billed indican el resultado.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Consulta la documentación de ScreenshotNeo para opciones como página completa con imágenes lazy, un elemento CSS, 12 dispositivos o viewport personalizado, escala retina, modo oscuro, CSS/JavaScript propio, clics previos, esperas, bloqueo de recursos, cookies y cabeceras, zona horaria, geolocalización, fondo transparente, redimensionado, caché con TTL, enlaces firmados, trabajos asíncronos con webhook, lotes de hasta 100 URL y API de uso.

Una llamada desde Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

cURL y Node.js

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' }); const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

El plan gratuito incluye 1.000 capturas al mes sin tarjeta; los planes de pago empiezan en 5 USD por 3.000 capturas (Growth: 15 USD/15.000; Pro: 39 USD/60.000; Scale: 99 USD/250.000; Business: 249 USD/1.000.000). La facturación anual ofrece dos meses gratis y todas las funciones están en cada plan. Su servidor MCP proporciona take_screenshot, get_page_info y capture_pdf para Claude, Cursor y otros clientes MCP.

Crea tu cuenta gratuita de ScreenshotNeo para probar las 1.000 capturas mensuales sin tarjeta.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Solución de problemas

Obtienes 403, 429 o una página de bloqueo

Reduce la frecuencia, respeta las reglas del sitio, usa un identificador de usuario honesto y comprueba si existe una API oficial. Un 429 suele indicar demasiadas solicitudes: espera y reintenta con límites, no con más concurrencia.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

El selector devuelve cero elementos

Guarda el HTML recibido y búscalo con un editor. Puede que el contenido se genere con JavaScript, que el selector apunte a una clase distinta o que hayas recibido una página de error. Comprueba también que el código de estado sea 200 y que la codificación se interprete correctamente.

Los textos aparecen cortados o con caracteres extraños

Usa get_text(" ", strip=True), revisa response.encoding y compara con la etiqueta meta charset. No fuerces UTF-8 sin verificar la respuesta.

El script se queda esperando

Define timeout, registra la URL que estaba procesando y captura excepciones de conexión. Guarda resultados de forma incremental para poder reanudar sin repetir todo.

Los datos se duplican al paginar

Normaliza las URL, detecta enlaces ya visitados y deduplica por un identificador estable. Establece un máximo de páginas y conserva una muestra de cada respuesta para auditar el origen.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Lista de comprobación antes de ampliar el crawler

  • Los campos están definidos y aparecen en HTML accesible.
  • Los selectores se probaron contra una respuesta guardada.
  • Hay límites de páginas, retraso y concurrencia.
  • Se registran estados, errores, URL y número de elementos.
  • El formato de salida y la política de duplicados están decididos.
  • Revisaste términos, robots.txt y obligaciones sobre datos personales.
  • Probaste con un lote pequeño y sabes cómo detener o reanudar el proceso.

Frequently Asked Questions

¿Beautiful Soup descarga páginas por sí sola?

No. Analiza HTML o XML ya disponible; necesitas requests u otro cliente HTTP para obtener la respuesta.

¿Debo usar Scrapy desde el primer día?

No necesariamente. Para una página o un prototipo, requests y Beautiful Soup son más pequeños; Scrapy resulta útil cuando el rastreo, la programación de solicitudes y las exportaciones pasan a ser el problema principal.

¿Robots.txt me da permiso legal para scrapear?

No. Es una señal técnica que puedes hacer cumplir con Scrapy, pero debes revisar condiciones, jurisdicción, tipo de datos y finalidad de uso.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.