Skip to content

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 lxml

Aná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étodoEjemploEncuentra
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

HerramientaMejor paraVelocidadJavaScriptCurva de aprendizaje
requests + BeautifulSoupPáginas HTML estáticasRápidaNoFácil
SeleniumPáginas renderizadas con JSLentaMedia
ScrapyCrawling a gran escalaRápidaCon pluginsPronunciada
PlaywrightApps JS modernasMediaMedia
httpx + selectolaxAnálisis de alto rendimientoMuy rápidaNoFá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.json

Limpieza 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ácticaDescripción
Verificar robots.txtRespetar las reglas de rastreo del sitio
Agregar retrasosUsar time.sleep() entre solicitudes (1-3 segundos)
IdentificarseEstablecer un User-Agent descriptivo
Cachear respuestasNo re-scrapear páginas que ya tienes
Buscar APIsMuchos sitios tienen APIs públicas más rápidas y permitidas
Leer los ToSAlgunos sitios prohíben explícitamente el scraping
Limitar velocidadNo 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 results

Aná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.

📚