Web Scraping con Python — La Guía Completa 2026
Aprende web scraping con Python — requests + BeautifulSoup, paginación, Playwright para JS, y cuándo una API de scraping supera al DIY.
Necesitas datos que viven en una página web — precios, listados, resultados de búsqueda, reseñas — y no hay botón de descarga. Cada tutorial que has abierto se detiene en el "hola mundo" de raspar un sitio de demostración, o salta directo a un diagrama de framework sin nada ejecutable en medio. Mientras tanto, tu objetivo real devuelve un 403, renderiza todo con JavaScript, o cambia su markup la semana después de que tu script finalmente funcionó.
Esta guía es todo el recorrido en un solo lugar: un primer scraper ejecutable, luego paginación y cortesía, luego páginas con JavaScript usando Playwright, y después un relato honesto de por qué los scrapers en producción se rompen — y qué hacer cuando el tuyo lo haga. Cada bloque de código de abajo se ejecuta tal cual contra sitios de demostración públicos. Solo raspamos datos públicos, y decimos con claridad en qué punto el hazlo-tú-mismo deja de ser la herramienta correcta.
El stack de scraping con Python en 2026
Python sigue siendo el lenguaje por defecto para web scraping porque las tres herramientas que necesitas son maduras, están documentadas, y a un pip install de distancia:
| Nivel | Herramientas | Úsalo cuando |
|---|---|---|
| HTML estático | requests + beautifulsoup4 | Los datos están en el código fuente de la página (Ver código fuente los muestra) |
| Páginas con JavaScript | playwright | Los datos aparecen solo después de que el navegador ejecuta scripts |
| Protegido / a gran escala | API de scraping | El sitio te bloquea, o el costo de mantenimiento supera el valor de los datos |
Las versiones vigentes a mediados de 2026: requests 2.x, BeautifulSoup 4.15 (lanzada en junio de 2026), y Playwright para Python 1.61. La regla que mantiene los proyectos sanos: usa la herramienta más pequeña que se ajuste a la página. Un navegador headless apuntado a HTML estático desperdicia 50 veces los recursos para el mismo resultado; requests apuntado a una app de React te da un div vacío.
Instala el primer nivel y estás listo:
pip install requests beautifulsoup4
Inicio rápido: tu primer scraper en 15 líneas
Vamos a raspar books.toscrape.com, una librería de demostración construida exactamente para este propósito — 1,000 libros con títulos, precios y estado de existencias. El patrón es obtener, parsear, seleccionar:
import requests
from bs4 import BeautifulSoup
url = "https://books.toscrape.com/"
resp = requests.get(url, timeout=10)
resp.raise_for_status() # crash loudly on 4xx/5xx instead of parsing an error page
soup = BeautifulSoup(resp.text, "html.parser")
books = []
for card in soup.select("article.product_pod"):
books.append({
"title": card.h3.a["title"],
"price": card.select_one("p.price_color").get_text(strip=True),
"in_stock": "In stock" in card.select_one("p.availability").get_text(),
})
print(f"Scraped {len(books)} books")
print(books[0])
# {'title': 'A Light in the Attic', 'price': '£51.77', 'in_stock': True}
Tres cosas cargan con todo el peso aquí:
resp.raise_for_status()— los principiantes se saltan esto, y luego pasan una hora depurando por qué BeautifulSoup «no encontró nada» en lo que en realidad era una página 404.soup.select(...)acepta selectores CSS, los mismos que pruebas en las DevTools de tu navegador. Haz clic derecho en el elemento, inspecciona, y construye el selector ahí antes de escribir una línea de Python. Este paso — convertir markup desordenado en campos con nombre — es parsing de HTML, y ahí es donde vive la mayor parte del código de tu scraper (y la mayoría de sus futuras roturas).- Salida estructurada desde el inicio. Una lista de diccionarios se convierte a CSV, JSON o un DataFrame en una sola línea — configuramos la exportación en unos párrafos más adelante.
BeautifulSoup va mucho más allá de select — find_all con filtros de atributos, navegación del árbol, manejo de markup mal formado. Cubrimos la API completa en nuestro tutorial de BeautifulSoup; esta guía avanza hacia los problemas que aparecen después de que la primera página funciona.
Nivel 2: paginación y cortesía
Una página es una demostración. Los conjuntos de datos reales abarcan paginación — y en el momento en que solicitas páginas dentro de un bucle, te conviertes en un patrón de tráfico de bot, así que aquí también empieza a importar la etiqueta del scraping.
import time
import requests
from bs4 import BeautifulSoup
BASE = "https://books.toscrape.com/catalogue/page-{}.html"
HEADERS = {"User-Agent": "book-price-research/1.0 ([email protected])"}
books = []
for page in range(1, 6): # first 5 of 50 pages
resp = requests.get(BASE.format(page), headers=HEADERS, timeout=10)
if resp.status_code == 404:
break # walked off the end of the catalogue
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
for card in soup.select("article.product_pod"):
books.append({
"title": card.h3.a["title"],
"price": card.select_one("p.price_color").get_text(strip=True),
})
time.sleep(1.0) # ~1 request/second
print(f"{len(books)} books across 5 pages")
Los hábitos que separan a un buen ciudadano de una molestia:
- Limita tu propia velocidad. El
time.sleep(1.0)es un piso, no una formalidad. Un sitio que nunca notaría una solicitud por segundo absolutamente notará cincuenta. Los servidores se defienden con limitación de tasa — y una respuesta 429 es la advertencia educada antes del 403 que no lo es. - Revisa robots.txt primero. Obtén
https://the-site.com/robots.txty respeta sus reglasDisallow— el módulo integrado de Pythonurllib.robotparserpuede evaluarlas por ti. No es una ley, pero ignorarlo es la forma más rápida de convertir un sitio tolerante en uno hostil, y arrastra hacia abajo tu postura de legalidad. (Sobre ese tema: raspar datos públicos puede ser legal, pero depende de los datos, los términos y tu jurisdicción — mira ¿es legal el web scraping en 2026?). - Identifícate con honestidad en el User-Agent con una forma de contactarte, y respeta los 429 retrocediendo de forma exponencial en lugar de reintentar en un bucle cerrado.
- Raspa solo páginas públicas. Nada detrás de un inicio de sesión, nada personal, nada que los términos del sitio delimiten claramente como fuera de límites.
Guárdalo en algún lugar útil
Como recolectamos diccionarios, exportar es terreno de la biblioteca estándar — no se necesita pandas para el caso simple:
import csv
with open("books.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["title", "price"])
writer.writeheader()
writer.writerows(books)
Dos hábitos que vale la pena formar ahora, mientras el conjunto de datos es pequeño. Primero, limpia en el borde: convierte "£51.77" a un float en el momento en que lo raspas, no tres scripts más adelante — float(price.lstrip("£")) hoy te ahorra un error de parseo de configuración regional más tarde. Segundo, conserva el HTML crudo para todo lo que podrías volver a parsear — el disco es barato, y cuando descubras el próximo mes que también necesitabas la calificación en estrellas, volver a parsear páginas guardadas gana frente a volver a rastrear el sitio. Si el destino es una hoja de cálculo en el escritorio de alguien en lugar de una base de datos, el pipeline completo — formato, múltiples hojas, actualizaciones recurrentes — está en raspar un sitio web a Excel.
Nivel 3: páginas con JavaScript usando Playwright
Ejecuta el patrón de inicio rápido contra un sitio moderno de React o Vue y obtendrás una cáscara vacía — el HTML llega casi vacío y JavaScript lo llena después. requests nunca ejecuta ese JavaScript. El diagnóstico rápido: si Ver código fuente no contiene los datos pero la página renderizada sí, necesitas un navegador headless.
Playwright es la opción predeterminada de 2026 para esto en Python — incluye sus propios navegadores y espera el contenido de forma inteligente:
pip install playwright
playwright install chromium
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://quotes.toscrape.com/js/", timeout=30000)
page.wait_for_selector("div.quote") # block until JS has rendered the data
quotes = []
for q in page.locator("div.quote").all():
quotes.append({
"text": q.locator("span.text").inner_text(),
"author": q.locator("small.author").inner_text(),
})
browser.close()
print(f"{len(quotes)} quotes rendered by JavaScript")
Ese objetivo — quotes.toscrape.com/js/ — sirve una página vacía y construye cada cita en el lado del cliente, así que demuestra el punto: requests obtiene cero citas, Playwright obtiene diez. La línea clave es wait_for_selector, que reemplaza el frágil enfoque de sleep(5) y esperar suerte con una espera explícita del elemento que realmente necesitas.
Los costos son reales, sin embargo. Una sesión de navegador headless usa cientos de megabytes de RAM donde requests usaba unos pocos, y funciona un orden de magnitud más lento. Dos reglas prácticas: recurre a Playwright solo cuando los datos genuinamente no están en el HTML crudo, y revisa primero la pestaña de Red (Network) — muchos «sitios con JavaScript» obtienen sus datos de un endpoint JSON limpio que puedes llamar directamente con requests. Cuando sí necesites un navegador, profundizamos en selectores, esperas y escalado en web scraping con Playwright. Si mantienes un stack más antiguo, Selenium sigue funcionando bien y tiene quince años de respuestas de Stack Overflow detrás — comparamos ambos en web scraping con Selenium.
La parte honesta: por qué los scrapers de producción se rompen
Todo lo anterior funciona — de verdad, no solo en sitios de demostración. Muchas herramientas internas y proyectos de investigación funcionan durante años con requests, BeautifulSoup y una tarea cron. Pero hay un muro entre «funciona en mi máquina contra un sitio de demostración» y «se ejecuta cada noche contra un objetivo comercial», y vale la pena describirlo con claridad, porque ahí es donde la mayoría de los proyectos de scraping realmente mueren.
Los sistemas anti-bot responden antes que el contenido. Cloudflare, DataDome, Akamai y sus pares se sitúan frente a una gran parte de los sitios comerciales. Puntúan cada solicitud, y un cliente de Python falla esa puntuación rápido: 403 en la primera solicitud, 429 en cuanto tienes un patrón, desafíos de JavaScript que requests no puede ejecutar. Esto no es un caso extremo — es la postura predeterminada de los objetivos de comercio electrónico, viajes, bienes raíces y búsqueda en 2026.
Tu IP tiene una reputación, y es mala. Las IP de centros de datos — tu laptop en Wi-Fi de la nube, tu instancia de AWS, tu runner de CI — están catalogadas y pre-marcadas. Pasar eso significa IPs residenciales o móviles rotadas por solicitud a través de un pool de proxy rotativo, lo que significa un proveedor de proxies, facturación por GB, y una nueva categoría de infraestructura que hay que cuidar.
Incluso los navegadores headless quedan al descubierto. Los proveedores anti-bot sondean los handshakes TLS, el renderizado en canvas, las fuentes instaladas y docenas de otras señales para calcular una firma de dispositivo — fingerprinting del navegador — y Playwright de fábrica tiene una firma reconocible. Existen builds parcheados y plugins stealth, pero son una cinta de correr: los proveedores actualizan, los parches se atrasan, tu scraper empieza a fallar silenciosamente un martes cualquiera.
El cambio de diseño es el asesino silencioso. El sitio lanza un rediseño, p.price_color ya no existe, y tu scraper no falla — devuelve campos vacíos hacia tu pipeline hasta que alguien note que el panel se ve mal. Cada selector que escribes es un contrato sin versionar que la otra parte nunca firmó.
Suma todo eso y el patrón resulta familiar: el scraper tomó un fin de semana en escribirse, y ahora consume unas horas cada semana en facturas de proxy, reparaciones de selectores e investigaciones de bloqueos. Ese impuesto de mantenimiento — no ninguna habilidad de Python que falte — es el verdadero costo de hacerlo tú mismo, y lo contabilizamos con honestidad en web scraping vs API. Para las contramedidas que sí funcionan cuando quieres seguir haciéndolo tú mismo, mira scraping de sitios que bloquean bots.
El camino de la API: una llamada reemplaza el stack
La alternativa a pelear contra ese muro es entregar todo el stack — proxies, navegadores, fingerprints, parsers — a una API de web scraping que pelea contra él por ti a tiempo completo. Aquí hay una búsqueda real en Amazon, con JSON estructurado de vuelta, sin navegador, sin pool de proxies, sin parser:
import requests
resp = requests.get(
"https://api.crawlora.net/api/v1/amazon/search",
params={"k": "mechanical keyboard"},
headers={"x-api-key": "YOUR_API_KEY"},
)
resp.raise_for_status()
for item in resp.json()["data"]:
print(item["title"], item["price"], item["rating"])
La misma biblioteca requests que aprendiste en el inicio rápido — la habilidad se transfiere directamente. Lo que cambia es de qué eres responsable:
- Campos estructurados, no HTML. La respuesta es JSON normalizado —
title,price,rating,asin,review_count— así que cuando Amazon se rediseñe, eso es un parser que Crawlora arregla antes de que tú lo notes, no tú. - La pelea anti-bot ocurre del otro lado de la llave. La rotación de proxies, el renderizado, los reintentos y el mantenimiento del fingerprint son el trabajo a tiempo completo del proveedor.
- La facturación es por éxito — solo se te cobra por respuestas 2xx exitosas, lo cual importa precisamente porque raspar objetivos difíciles implica fallos.
- El plan gratuito es de 2,000 créditos al mes, sin tarjeta, lo suficiente para descubrir si los datos valen la pena antes de gastar algo.
El mismo patrón cubre docenas de plataformas — Google Search, TikTok, Zillow, GitHub, tiendas de aplicaciones — cada una con endpoints documentados en la documentación de la API y Python listo para copiar y pegar para cada una en los ejemplos en Python. Si tu scraper alimenta a un agente de LLM en lugar de una hoja de cálculo, los mismos endpoints están expuestos como un servidor MCP alojado (mira la integración MCP) — y ese patrón de agente más scraping es un tema en sí mismo, cubierto en web scraping con IA.
Para ser justos en ambas direcciones: una API tiene un precio por solicitud, cubre las plataformas que cubre, y coloca a un proveedor en tu pipeline. Hacerlo tú mismo no tiene ninguna de esas restricciones — que es exactamente por qué la respuesta correcta suele ser una mezcla.
Hazlo tú mismo vs API de scraping: cómo decidir
| Factor | Hazlo tú mismo (requests / Playwright) | API de scraping |
|---|---|---|
| Costo inicial | Gratis — todo de código abierto | Plan gratuito, luego créditos por solicitud |
| Sitios sin protección | Gana — rápido, gratis, control total | Funciona, pero pagas por protección que no necesitas |
| Sitios protegidos (Cloudflare, etc.) | Proxies + stealth + mantenimiento constante | Gana — se maneja detrás de la llave |
| Sitios de nicho / cola larga / internos | Gana — una API no puede cubrir cada sitio | Solo plataformas soportadas |
| Mantenimiento | Tuyo: selectores, proxies, bloqueos | Del proveedor: parsers y desbloqueo |
| Salida | Lo que tú parsees — totalmente personalizado | JSON normalizado por plataforma |
| Valor de aprendizaje | Gana — entiendes todo el stack | Abstrae las partes interesantes |
La división honesta: hacerlo tú mismo gana en costo, cobertura de objetivos de nicho y control; la API gana en plataformas protegidas, mantenimiento y tiempo hasta obtener los datos. La mayoría de los equipos terminan raspando ellos mismos los objetivos fáciles e internos, y enrutando las plataformas hostiles y de alto valor a través de una API.
Antes de poner cualquiera de los dos en producción, ejecuta la lista de verificación:
- ¿Los datos son públicos, y robots.txt y los términos del sitio permiten lo que estás haciendo?
- ¿Los datos están en el HTML crudo (requests + BeautifulSoup) o renderizados con JavaScript (Playwright)?
- ¿Estás limitando la frecuencia a un ritmo educado, con timeouts, raise_for_status, y retroceso exponencial ante 429?
- ¿Validas los campos raspados (no vacíos, tipos coherentes) para que el cambio de diseño falle de forma ruidosa en lugar de silenciosa?
- ¿Has calculado el impuesto de mantenimiento — proxies, reparaciones de selectores, lucha contra bloqueos — frente al costo por solicitud de una API de scraping?
- Para objetivos protegidos: ¿has probado si una sola llamada a la API te da los mismos datos sin nada de la infraestructura?
Hacia dónde ir ahora
Ahora tienes el mapa completo de 2026: requests y BeautifulSoup para páginas estáticas, Playwright cuando JavaScript se interpone, hábitos reales de cortesía, y una visión clara del muro de producción más la puerta de la API que lo atraviesa. El mejor siguiente paso es ejecutar el inicio rápido contra un objetivo que realmente te importe — sabrás en menos de una hora qué nivel del stack necesita tu proyecto. Y si esa primera solicitud regresa con un 403, sabrás exactamente a qué te enfrentas, y que de cualquier forma es un problema resuelto. La página de precios tiene las matemáticas de créditos si quieres compararlas contra tu factura de proxy.
Sáltate el muro — prueba la API en paralelo con tu scraper
JSON estructurado de Amazon, Google, TikTok y más. Proxies gestionados, renderizado y reintentos; facturación por éxito. 2,000 créditos al mes gratis, sin tarjeta.
Lecturas relacionadas:
Preguntas frecuentes
Is web scraping with Python legal?
Scraping public data can be lawful, but it depends on what you collect, the site's terms of service, and your jurisdiction. Stay on public pages, avoid anything behind a login, skip personal data, honor robots.txt, and keep your request rate polite. Court rulings have generally favored scraping publicly accessible data, but this is background, not legal advice — check the specifics for your use case.
What is the best Python library for web scraping?
It depends on the page. For static HTML, requests plus BeautifulSoup is the standard pairing — simple, fast, and well documented. For JavaScript-rendered pages, Playwright is the 2026 default headless browser library. For crawling many pages, Scrapy adds scheduling and pipelines. The rule of thumb: use the smallest tool that fits the page you are scraping.
Is Python good for web scraping?
Yes — Python is the most popular scraping language because its ecosystem covers every tier of the job: requests for HTTP, BeautifulSoup for HTML parsing, Playwright for browser automation, and pandas for cleaning and exporting the results. A working scraper is about 15 lines of code, and nearly every scraping tutorial, Stack Overflow answer, and API example ships Python first.
How do I scrape a website that uses JavaScript?
If the data is missing from View Source but visible on the rendered page, plain requests will return an empty shell. Use Playwright for Python: launch headless Chromium, call page.goto, then wait_for_selector on the element you need before extracting text. Also check the browser's Network tab first — many JavaScript sites load data from a JSON endpoint you can call directly, which is far cheaper than running a browser.
How do I avoid getting blocked while scraping?
Throttle to roughly one request per second, set an honest User-Agent, respect robots.txt, and back off exponentially on 429 responses. For sites behind Cloudflare or similar anti-bot systems, that is often not enough — datacenter IPs are pre-flagged and headless browsers get fingerprinted. At that point the options are rotating residential proxies plus stealth patches, or a scraping API that handles the unblocking for you.
What is the difference between BeautifulSoup and Scrapy?
BeautifulSoup is a parsing library: you hand it HTML you fetched yourself and it helps you extract fields with CSS selectors. Scrapy is a full crawling framework: it manages requests, concurrency, link following, throttling, and export pipelines. Use BeautifulSoup with requests for scripts and small jobs; consider Scrapy when you are crawling thousands of pages across a site.
Can I use an API instead of writing a Python scraper?
Yes, for supported platforms. A scraping API like Crawlora exposes documented endpoints — Amazon search, Google Search, TikTok, and more — that return normalized JSON in one requests.get call with an x-api-key header, while proxies, rendering, and retries are handled server-side. Billing is pay-on-success (only 2xx responses are charged), and the free tier is 2,000 credits/month, no card. DIY still wins for niche sites no API covers.
