Web Scraping con Python: Guía completa usando Requests, BeautifulSoup y Selenium
Updated on
Necesitas datos que no están disponibles a través de APIs -- precios de productos de sitios web de la competencia, artículos de investigación de portales académicos, ofertas de empleo de plataformas de contratación o artículos de noticias de múltiples fuentes. Copiar datos manualmente es lento, propenso a errores e imposible a escala. El web scraping automatiza este proceso, pero elegir la herramienta o enfoque incorrecto lleva a solicitudes bloqueadas, parsers rotos y problemas legales.
Esta guía cubre el stack completo de web scraping en Python: requests + BeautifulSoup para páginas estáticas, Selenium para contenido renderizado con JavaScript y Scrapy para crawling a gran escala. Aprenderás técnicas prácticas para manejar desafíos del mundo real.
Inicio rápido: requests + BeautifulSoup
La combinación más común para scrapear páginas HTML estáticas:
import requests
from bs4 import BeautifulSoup
# Obtener la página
url = 'https://books.toscrape.com/'
response = requests.get(url)
response.raise_for_status() # Lanzar error para códigos de estado incorrectos
# Analizar HTML
soup = BeautifulSoup(response.text, 'html.parser')
# Extraer títulos de libros
books = soup.select('article.product_pod h3 a')
for book in books[:5]:
print(book['title'])Instalación
# Instalar paquetes necesarios
# pip install requests beautifulsoup4 lxmlAnálisis HTML con BeautifulSoup
Encontrar elementos
from bs4 import BeautifulSoup
html = """
<div class="products">
<div class="product" id="p1">
<h2 class="name">Widget A</h2>
<span class="price">$9.99</span>
<p class="desc">A useful widget</p>
</div>
<div class="product" id="p2">
<h2 class="name">Widget B</h2>
<span class="price">$14.99</span>
<p class="desc">A better widget</p>
</div>
</div>
"""
soup = BeautifulSoup(html, 'html.parser')
# Por etiqueta
print(soup.find('h2').text) # "Widget A"
# Por clase
prices = soup.find_all('span', class_='price')
for p in prices:
print(p.text) # "$9.99", "$14.99"
# Por ID
product = soup.find(id='p2')
print(product.find('h2').text) # "Widget B"
# Selector CSS
names = soup.select('.product .name')
for n in names:
print(n.text)Selectores comunes
| Método | Ejemplo | Encuentra |
|---|---|---|
find('tag') | soup.find('h2') | Primer elemento h2 |
find_all('tag') | soup.find_all('a') | Todos los elementos ancla |
find(class_='x') | soup.find(class_='price') | Primer elemento con la clase |
find(id='x') | soup.find(id='main') | Elemento con el ID |
select('css') | soup.select('div.product > h2') | Coincidencias del selector CSS |
select_one('css') | soup.select_one('#header') | Primera coincidencia CSS |
Extracción de datos
from bs4 import BeautifulSoup
html = '<a href="/page/2" class="next" data-page="2">Next Page</a>'
soup = BeautifulSoup(html, 'html.parser')
link = soup.find('a')
# Contenido de texto
print(link.text) # "Next Page"
print(link.get_text(strip=True)) # "Next Page" (sin espacios)
# Atributos
print(link['href']) # "/page/2"
print(link.get('class')) # ['next']
print(link['data-page']) # "2"Manejo de encabezados y sesiones
Muchos sitios web bloquean solicitudes sin encabezados apropiados:
import requests
from bs4 import BeautifulSoup
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept-Language': 'en-US,en;q=0.9',
}
# Usar una sesión para cookies y encabezados persistentes
session = requests.Session()
session.headers.update(headers)
response = session.get('https://books.toscrape.com/')
soup = BeautifulSoup(response.text, 'html.parser')
print(f"Se encontraron {len(soup.select('article.product_pod'))} libros")Paginación
Seguir enlaces de página siguiente
import requests
from bs4 import BeautifulSoup
base_url = 'https://books.toscrape.com/catalogue/'
all_books = []
url = 'https://books.toscrape.com/catalogue/page-1.html'
while url:
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
# Extraer libros de la página actual
for book in soup.select('article.product_pod'):
title = book.select_one('h3 a')['title']
price = book.select_one('.price_color').text
all_books.append({'title': title, 'price': price})
# Encontrar página siguiente
next_btn = soup.select_one('li.next a')
if next_btn:
url = base_url + next_btn['href']
else:
url = None
print(f"Scrapeados {len(all_books)} libros hasta ahora...")
print(f"Total: {len(all_books)} libros")Scraping de páginas JavaScript con Selenium
Cuando una página renderiza contenido con JavaScript, requests solo obtiene el HTML crudo antes del renderizado. Usa Selenium para controlar un navegador real:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Configurar Chrome sin interfaz gráfica
options = webdriver.ChromeOptions()
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
try:
driver.get('https://quotes.toscrape.com/js/')
# Esperar a que se cargue el contenido
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, 'quote'))
)
quotes = driver.find_elements(By.CLASS_NAME, 'quote')
for quote in quotes:
text = quote.find_element(By.CLASS_NAME, 'text').text
author = quote.find_element(By.CLASS_NAME, 'author').text
print(f'"{text}" -- {author}')
finally:
driver.quit()Comparación de herramientas
| Herramienta | Mejor para | Velocidad | JavaScript | Curva de aprendizaje |
|---|---|---|---|---|
requests + BeautifulSoup | Páginas HTML estáticas | Rápida | No | Fácil |
Selenium | Páginas renderizadas con JS | Lenta | Sí | Media |
Scrapy | Crawling a gran escala | Rápida | Con plugins | Pronunciada |
Playwright | Apps JS modernas | Media | Sí | Media |
httpx + selectolax | Análisis de alto rendimiento | Muy rápida | No | Fácil |
Scrapy para scraping a gran escala
Para rastrear miles de páginas, Scrapy proporciona concurrencia integrada, lógica de reintentos y pipelines de datos:
# spider.py
import scrapy
class BookSpider(scrapy.Spider):
name = 'books'
start_urls = ['https://books.toscrape.com/']
def parse(self, response):
for book in response.css('article.product_pod'):
yield {
'title': book.css('h3 a::attr(title)').get(),
'price': book.css('.price_color::text').get(),
}
# Seguir paginación
next_page = response.css('li.next a::attr(href)').get()
if next_page:
yield response.follow(next_page, self.parse)
# Ejecutar con: scrapy runspider spider.py -o books.jsonLimpieza y almacenamiento de datos
import requests
from bs4 import BeautifulSoup
import pandas as pd
# Scrapear datos
url = 'https://books.toscrape.com/'
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
books = []
for item in soup.select('article.product_pod'):
title = item.select_one('h3 a')['title']
price_text = item.select_one('.price_color').text
price = float(price_text.replace('£', ''))
rating_class = item.select_one('p.star-rating')['class'][1]
books.append({
'title': title,
'price': price,
'rating': rating_class,
})
# Convertir a DataFrame
df = pd.DataFrame(books)
print(df.head())
# Guardar como CSV
df.to_csv('books.csv', index=False)Prácticas éticas de scraping
| Práctica | Descripción |
|---|---|
| Verificar robots.txt | Respetar las reglas de rastreo del sitio |
| Agregar retrasos | Usar time.sleep() entre solicitudes (1-3 segundos) |
| Identificarse | Establecer un User-Agent descriptivo |
| Cachear respuestas | No re-scrapear páginas que ya tienes |
| Buscar APIs | Muchos sitios tienen APIs públicas más rápidas y permitidas |
| Leer los ToS | Algunos sitios prohíben explícitamente el scraping |
| Limitar velocidad | No sobrecargar servidores con solicitudes concurrentes |
import time
import requests
def polite_scrape(urls, delay=2):
"""Scrapear con retrasos entre solicitudes."""
results = []
for url in urls:
response = requests.get(url)
results.append(response.text)
time.sleep(delay) # Ser cortés
return resultsAnálisis de datos scrapeados
Después de recopilar datos mediante web scraping, PyGWalker (opens in a new tab) te ayuda a explorar y visualizar el conjunto de datos interactivamente en Jupyter:
import pandas as pd
import pygwalker as pyg
df = pd.read_csv('scraped_data.csv')
walker = pyg.walk(df)Para ejecutar scripts de scraping iterativamente en Jupyter con asistencia de IA, RunCell (opens in a new tab) proporciona un entorno impulsado por IA donde puedes depurar selectores y probar lógica de análisis interactivamente.
Preguntas frecuentes
¿Cuál es la mejor librería de Python para web scraping?
Para páginas estáticas, requests + BeautifulSoup es la opción más simple y popular. Para páginas renderizadas con JavaScript, usa Selenium o Playwright. Para crawling a gran escala con miles de páginas, Scrapy proporciona concurrencia integrada y gestión de pipelines.
¿Cómo scrapeo un sitio web que usa JavaScript?
Usa Selenium o Playwright para controlar un navegador sin interfaz que ejecute JavaScript. Alternativamente, revisa la pestaña Red del navegador buscando endpoints de API que devuelvan datos JSON -- scrapear la API directamente es más rápido y confiable que la automatización del navegador.
¿Es legal el web scraping?
La legalidad del web scraping depende de la jurisdicción, los términos de servicio del sitio y cómo se usan los datos. Scrapear datos públicamente disponibles es generalmente legal en muchas jurisdicciones, pero siempre verifica el robots.txt y los ToS del sitio. Evita scrapear datos personales o contenido protegido por derechos de autor.
¿Cómo evito ser bloqueado al scrapear?
Usa retrasos entre solicitudes (1-3 segundos), rota los User-Agent, respeta robots.txt, usa sesiones para cookies y evita hacer demasiadas solicitudes concurrentes. Si te bloquean constantemente, verifica si el sitio tiene una API pública.
¿Cómo manejo la paginación en web scraping?
Encuentra el enlace o botón de "página siguiente" en el HTML, extrae su URL y síguelo en un bucle hasta que no haya más páginas. Alternativamente, si las páginas usan parámetros de consulta (ej., ?page=2), itera a través de los números de página directamente.
Conclusión
El ecosistema de web scraping de Python cubre cada escenario: requests + BeautifulSoup para scraping rápido de páginas estáticas, Selenium para sitios pesados en JavaScript y Scrapy para crawling a escala de producción. Comienza con la herramienta más simple que funcione, agrega complejidad solo cuando sea necesario, y siempre scrapea éticamente -- respeta robots.txt, agrega retrasos y verifica primero si hay APIs. Almacena tus datos scrapeados en formatos estructurados como CSV o DataFrames para análisis inmediato.