Web scraping con Python, Requests y BeautifulSoup

Publicado el: 11/07/2026
Tempo de leitura: 4 minutos
logo do BeautifulSoup em um fundo branco

El web scraping es la recopilación automatizada de información publicada en páginas web. En lugar de copiar manualmente precios, titulares, enlaces o filas de una tabla, un programa puede descargar el HTML, localizar los elementos relevantes, limpiar los datos y guardarlos en un formato estructurado.

Esta guía explica web scraping con Python utilizando Requests y BeautifulSoup. Aprenderás a descargar páginas con límites de tiempo, inspeccionar HTML, seleccionar elementos, extraer atributos, seguir paginación, utilizar sesiones, controlar errores y exportar resultados.

Revisar las reglas antes de recopilar datos

Que una página sea visible públicamente no significa que cualquier recopilación esté permitida. Revisa los términos de servicio, la política de privacidad, las condiciones de copyright y el archivo robots.txt. Evita datos personales o sensibles y nunca intentes eludir autenticación, controles de acceso, CAPTCHAs o bloqueos técnicos.

Cuando exista una API oficial, normalmente es la opción más estable. Un scraper responsable utiliza un ritmo conservador y se detiene cuando el servidor indica limitación o rechazo.

Instalar Requests y BeautifulSoup

python -m venv .venv

# Windows
.venv\Scripts\activate

# macOS o Linux
source .venv/bin/activate

python -m pip install requests beautifulsoup4

La guía de entornos virtuales en Python explica cómo aislar dependencias. Las referencias principales son la documentación oficial de Requests y la documentación oficial de Beautiful Soup.

Descargar una página

Utiliza un timeout y comprueba el estado HTTP:

import requests

url = "https://example.com"

response = requests.get(url, timeout=15)
response.raise_for_status()

print(response.status_code)
print(response.text[:200])

raise_for_status() convierte respuestas de error en excepciones. El tutorial de Requests con Python explica headers, parámetros, JSON, sesiones y errores de red.

Utilizar un User-Agent honesto

headers = {
    "User-Agent": "EducationalScraper/1.0 (contacto: [email protected])"
}

response = requests.get(
    "https://example.com/products",
    headers=headers,
    timeout=15,
)
response.raise_for_status()

Identifica la herramienta de manera responsable. No utilices headers falsos para intentar evadir restricciones.

Analizar el HTML

from bs4 import BeautifulSoup

soup = BeautifulSoup(response.text, "html.parser")

print(soup.title)
print(soup.title.get_text(strip=True))

html.parser está incluido con Python. Otros parsers pueden ser más rápidos o tolerantes, pero requieren paquetes adicionales.

Inspeccionar la estructura

Abre la página en el navegador, utiliza la herramienta de inspección y busca etiquetas, IDs, clases o atributos estables. Evita selectores basados en clases generadas automáticamente o posiciones muy profundas.

<article class="product-card" data-id="42">
    <h2 class="product-name">Teclado mecánico</h2>
    <span class="price">$89.90</span>
    <a class="details" href="/products/42">Ver producto</a>
</article>

Localizar elementos

card = soup.find("article", class_="product-card")
name = card.find("h2", class_="product-name")

print(name.get_text(strip=True))

Con selectores CSS:

card = soup.select_one("article.product-card")
name = card.select_one(".product-name")
price = card.select_one(".price")

select_one() devuelve el primer elemento o None. select() devuelve todos los elementos encontrados.

Extraer texto y atributos

from urllib.parse import urljoin

name = card.select_one(".product-name").get_text(" ", strip=True)
price_text = card.select_one(".price").get_text(" ", strip=True)
link_element = card.select_one("a.details")
product_url = urljoin(response.url, link_element.get("href"))
product_id = card.get("data-id")

urljoin() convierte enlaces relativos en URLs absolutas. Usa get() para atributos opcionales.

Recorrer varias tarjetas

products = []

for card in soup.select("article.product-card"):
    name_element = card.select_one(".product-name")
    price_element = card.select_one(".price")
    link_element = card.select_one("a.details")

    if not name_element or not price_element:
        continue

    products.append({
        "name": name_element.get_text(" ", strip=True),
        "price_text": price_element.get_text(" ", strip=True),
        "url": urljoin(
            response.url,
            link_element.get("href", "") if link_element else "",
        ),
    })

Comprobar elementos opcionales evita que una tarjeta incompleta detenga todo el proceso.

Limpiar precios

from decimal import Decimal, InvalidOperation


def parse_price(text: str) -> Decimal:
    cleaned = text.replace("$", "").replace(",", "").strip()

    try:
        return Decimal(cleaned)
    except InvalidOperation as error:
        raise ValueError(f"Precio inválido: {text!r}") from error

Adapta la limpieza al formato regional del sitio. No asumas que la coma y el punto significan lo mismo en todos los países.

Utilizar una sesión

with requests.Session() as session:
    session.headers.update(headers)
    response = session.get(url, timeout=15)
    response.raise_for_status()

Una sesión reutiliza conexiones y headers. También puede conservar cookies cuando ese comportamiento esté permitido y sea necesario.

Reintentos limitados

from requests import Session
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

retry_policy = Retry(
    total=3,
    backoff_factor=1,
    status_forcelist=[429, 500, 502, 503, 504],
    allowed_methods=["GET"],
    respect_retry_after_header=True,
)

session = Session()
session.mount("https://", HTTPAdapter(max_retries=retry_policy))

Una respuesta 429 indica demasiadas solicitudes. Respeta Retry-After y reduce el ritmo.

Seguir paginación

from time import sleep
from urllib.parse import urljoin

next_url = "https://example.com/products"
all_products = []
max_pages = 5

for _ in range(max_pages):
    if not next_url:
        break

    response = session.get(next_url, timeout=15)
    response.raise_for_status()
    soup = BeautifulSoup(response.text, "html.parser")

    # Extraer tarjetas aquí.

    next_link = soup.select_one("a.next-page")
    next_url = (
        urljoin(response.url, next_link.get("href"))
        if next_link and next_link.get("href")
        else None
    )

    if next_url:
        sleep(2)

Define un máximo durante el desarrollo para evitar ciclos inesperados.

Exportar a CSV

import csv

with open("products.csv", "w", newline="", encoding="utf-8") as file:
    writer = csv.DictWriter(
        file,
        fieldnames=["name", "price", "url"],
    )
    writer.writeheader()
    writer.writerows(products)

La guía de archivos CSV con Python explica lectura, escritura y validación. Para análisis tabular, utiliza Pandas.

Exportar a JSON

import json

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

JSON conserva mejor las estructuras anidadas y es práctico para aplicaciones y APIs.

Manejar errores de red

def fetch_soup(session, url):
    try:
        response = session.get(url, timeout=15)
        response.raise_for_status()
    except requests.Timeout as error:
        raise RuntimeError(f"Tiempo agotado: {url}") from error
    except requests.HTTPError as error:
        raise RuntimeError(
            f"Error HTTP {error.response.status_code}: {url}"
        ) from error
    except requests.RequestException as error:
        raise RuntimeError(f"Solicitud fallida: {url}") from error

    return BeautifulSoup(response.text, "html.parser")

La guía de excepciones en Python explica cómo conservar el contexto del error.

Páginas que dependen de JavaScript

Requests descarga la respuesta del servidor, pero algunos sitios construyen el contenido después con JavaScript. Comprueba el HTML recibido y el código fuente. Cuando exista una API pública documentada, úsala. Para automatización permitida de navegadores, consulta la guía de Selenium con Python.

Probar el parser

La estructura de una página puede cambiar. Separa descarga y análisis, guarda pequeñas muestras autorizadas de HTML y escribe pruebas para los selectores. El tutorial de Pytest ayuda a probar funciones sin contactar el sitio en cada ejecución.

Errores frecuentes

  • No utilizar timeout.
  • Ignorar códigos HTTP.
  • Enviar solicitudes demasiado rápido.
  • Suponer que todos los elementos existen.
  • Utilizar selectores frágiles.
  • Guardar texto sin limpiar como número.
  • Recopilar datos personales sin base legítima.
  • Intentar eludir autenticación o CAPTCHAs.
  • Ignorar una API oficial disponible.

Conclusión

Un scraper responsable separa tres tareas: descargar páginas cuidadosamente, analizar y validar la información, y guardar resultados estructurados. Utiliza timeouts, comprobación de estado, sesiones, reintentos limitados, selectores estables y pausas respetuosas. Revisa siempre las reglas del sitio y prefiere APIs oficiales cuando existan.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Comparando listas e diferenças entre arrays em Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo comparar dos listas en Python y encontrar diferencias

    Compara listas en Python con igualdad, sorted, sets y Counter, conserva duplicados, detecta registros modificados y evita errores frecuentes.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Automação em Python para organizar arquivos do computador
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo organizar automáticamente los archivos de tu PC con Python

    Organiza automáticamente archivos del PC con Python, pathlib y shutil, evitando sobrescrituras, descargas incompletas y movimientos sin registro.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Script Python configurado para iniciar junto com o Windows
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo ejecutar un script de Python al iniciar Windows

    Configura un script Python para iniciar con Windows usando la carpeta Inicio, archivos BAT, Task Scheduler, entornos virtuales, logs y

    Ler mais

    Tempo de leitura: 6 minutos
    11/07/2026
    Sistema de backup automático de arquivos usando Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo automatizar copias de seguridad con Python

    Aprende a automatizar copias de seguridad con Python usando pathlib, shutil, fechas, verificación, registros y una estrategia segura de restauración.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Execução de comandos do terminal usando Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Subprocess en Python: ejecutar comandos del terminal

    Aprende subprocess en Python para ejecutar comandos, capturar salida, manejar errores, establecer timeouts y evitar riesgos con shell=True.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    representação de automação com ícones de engrenagens, gráficos e pessoas no background
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo automatizar tareas cotidianas con Python

    Aprende a automatizar tareas con Python: organizar archivos, renombrar lotes, procesar CSV, crear backups, usar logs y programar scripts.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026