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

    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
    Criação de bot Telegram com Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo crear un bot de Telegram con Python

    Crea un bot de Telegram con Python: configura BotFather, protege el token, maneja comandos y mensajes, añade botones, archivos, logging

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Automação web com Selenium usando Python
    Automatización y ScriptsDesarrollo Web
    Foto de perfil de Leandro Hirt da Academify

    Selenium con Python: automatización web completa

    Aprende Selenium con Python para controlar navegadores, localizar elementos, usar esperas, completar formularios, tomar capturas y crear pruebas fiables.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Geração de arquivos PDF usando biblioteca FPDF em Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo crear archivos PDF con Python y FPDF

    Aprende a crear PDF con Python y fpdf2: páginas, fuentes, párrafos, imágenes, tablas, Unicode, rutas, informes y manejo de errores.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Logos do Python e Excel lado a lado representando a importação de dados do Excel para Python.
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo importar archivos Excel en Python con Pandas

    Importa archivos Excel en Python con Pandas y openpyxl: hojas, columnas, tipos, fechas, valores ausentes, limpieza y exportación.

    Ler mais

    Tempo de leitura: 3 minutos
    11/07/2026
    Python e ícone de e-mail sobre teclado de notebook
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo automatizar correos electrónicos con Python

    Automatiza correos con Python usando EmailMessage y smtplib, variables de entorno, HTML, adjuntos, destinatarios múltiples y manejo de errores.

    Ler mais

    Tempo de leitura: 3 minutos
    11/07/2026