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: admin@example.com)"
}

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

    Imagem ilustrativa de conteúdo Python para YouTube
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    YouTube con Python: descarga vídeos propios con yt-dlp

    Descarga copias de tus propios vídeos o contenido autorizado con Python y yt-dlp, validación, historial, límites, FFmpeg y prácticas responsables.

    Ler mais

    Tempo de leitura: 6 minutos
    12/07/2026
    Automação de postagens no Twitter usando Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Tweepy: automatiza publicaciones en X con Python

    Automatiza publicaciones en X con Tweepy, credenciales seguras, validación, deduplicación, programación, logs y prácticas responsables contra el spam.

    Ler mais

    Tempo de leitura: 5 minutos
    12/07/2026
    Geração e edição de planilhas Excel usando Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Excel con Python: crea y edita con openpyxl

    Crea y edita Excel con Pandas y openpyxl: hojas, fórmulas, estilos, filtros, validación, gráficos, rutas seguras y manejo de archivos

    Ler mais

    Tempo de leitura: 4 minutos
    12/07/2026
    Gerando aplicativo APK Android com Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    APK Android con Python, Kivy y Buildozer

    Genera un APK Android con Python, Kivy y Buildozer: configuración, permisos, recursos, compilación, pruebas, almacenamiento, firma y publicación.

    Ler mais

    Tempo de leitura: 6 minutos
    12/07/2026
    Web scraper de notícias em Python com envio para Telegram
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Scraper de noticias con Python y Telegram

    Crea un scraper de noticias con Requests y Beautiful Soup, elimina duplicados y envía titulares a Telegram de forma segura

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Compactação de arquivos ZIP usando Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    zipfile: descomprime archivos ZIP con seguridad

    Descomprime archivos ZIP con Python de forma segura: valida rutas, evita Zip Slip, limita tamaños, verifica integridad y procesa archivos

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026