Bot de alertas de precios con Python y scraping

Actualizado el: 21/08/2026
Tempo de leitura: 4 minutos
Bot em Python para monitorar produtos e avisar quando houver queda de preço automaticamente

Un bot de alerta de precios consulta periódicamente una página o una API, extrae el valor de un producto y envía una notificación cuando se cumple una condición. Es un proyecto práctico para aprender solicitudes HTTP, análisis de HTML, programación de tareas, almacenamiento de estado y manejo de errores. La parte importante no es únicamente encontrar un número: el sistema debe identificar el producto correcto, evitar alertas duplicadas y respetar las reglas del sitio.

Para seguir esta guía conviene conocer Requests en Python, el web scraping con Beautiful Soup, las variables de entorno, el módulo logging y el manejo de excepciones.

Elegir una fuente adecuada

Siempre que sea posible, utiliza una API oficial, un feed de datos o una función de alertas proporcionada por la tienda. El scraping de HTML es más frágil porque la estructura visual puede cambiar. Antes de automatizar consultas, revisa los términos de uso, el archivo robots.txt y los límites de acceso.

La documentación oficial de Requests explica sesiones, timeouts y errores HTTP. La documentación oficial de Beautiful Soup describe selectores y análisis de documentos HTML.

Instalar las dependencias

python -m venv .venv
.venv\Scripts\activate
python -m pip install requests beautifulsoup4

En macOS o Linux utiliza source .venv/bin/activate. Guarda las versiones utilizadas en un archivo de requisitos para reproducir el entorno.

Descargar la página con seguridad

import requests

URL = "https://ejemplo.com/producto"


def descargar_pagina(url: str) -> str:
    cabeceras = {
        "User-Agent": "Mozilla/5.0 PriceMonitor/1.0"
    }
    respuesta = requests.get(
        url,
        headers=cabeceras,
        timeout=(5, 15),
    )
    respuesta.raise_for_status()
    return respuesta.text

Un timeout de conexión y lectura evita bloqueos indefinidos. No intentes eludir protecciones, captchas o controles de acceso. Si el sitio bloquea automatizaciones, utiliza una alternativa autorizada.

Encontrar el precio

Inspecciona el HTML y busca un selector estable, preferiblemente un atributo semántico:

from bs4 import BeautifulSoup


def extraer_texto_precio(html: str) -> str:
    soup = BeautifulSoup(html, "html.parser")
    elemento = soup.select_one('[data-testid="product-price"]')
    if elemento is None:
        raise ValueError("No se encontró el precio")
    return elemento.get_text(" ", strip=True)

No uses un selector basado solo en una cadena de clases generadas automáticamente, porque puede cambiar en cada despliegue.

Convertir el texto a Decimal

from decimal import Decimal, InvalidOperation
import re


def convertir_precio(texto: str) -> Decimal:
    limpio = re.sub(r"[^0-9,.-]", "", texto)

    if "," in limpio and "." in limpio:
        limpio = limpio.replace(".", "").replace(",", ".")
    elif "," in limpio:
        limpio = limpio.replace(",", ".")

    try:
        precio = Decimal(limpio)
    except InvalidOperation as error:
        raise ValueError(f"Precio inválido: {texto}") from error

    if precio < 0:
        raise ValueError("El precio no puede ser negativo")
    return precio

Las reglas de separadores dependen de la región. Si el sitio utiliza un formato diferente, adapta la función y añade pruebas específicas.

Comprobar el producto correcto

Además del precio, verifica el nombre o un identificador:

def extraer_producto(html: str) -> dict:
    soup = BeautifulSoup(html, "html.parser")
    titulo = soup.select_one("h1")
    precio = soup.select_one('[data-testid="product-price"]')

    if not titulo or not precio:
        raise ValueError("La estructura de la página cambió")

    return {
        "titulo": titulo.get_text(" ", strip=True),
        "precio": convertir_precio(
            precio.get_text(" ", strip=True)
        ),
    }

Esto evita enviar una alerta si la URL fue redirigida a una categoría, página de error o producto alternativo.

Definir el umbral

from decimal import Decimal

PRECIO_OBJETIVO = Decimal("299.90")


def debe_alertar(precio_actual: Decimal) -> bool:
    return precio_actual <= PRECIO_OBJETIVO

También puedes detectar una reducción porcentual respecto al último precio:

def reduccion_porcentual(anterior, actual):
    if anterior <= 0:
        return Decimal("0")
    return (anterior - actual) / anterior * 100

Guardar el estado

Para no repetir la misma alerta en cada ejecución, guarda el último precio y la última notificación:

import json
from pathlib import Path

RUTA_ESTADO = Path("estado_precio.json")


def cargar_estado():
    if not RUTA_ESTADO.exists():
        return {}
    return json.loads(RUTA_ESTADO.read_text(encoding="utf-8"))


def guardar_estado(datos):
    temporal = RUTA_ESTADO.with_suffix(".tmp")
    temporal.write_text(
        json.dumps(datos, indent=2, ensure_ascii=False),
        encoding="utf-8",
    )
    temporal.replace(RUTA_ESTADO)

La escritura temporal reduce el riesgo de dejar un JSON incompleto si el proceso se interrumpe.

Enviar una notificación por correo

import os
import smtplib
from email.message import EmailMessage


def enviar_correo(titulo, precio, url):
    usuario = os.getenv("SMTP_USER")
    clave = os.getenv("SMTP_PASSWORD")
    destino = os.getenv("ALERT_EMAIL")

    if not all([usuario, clave, destino]):
        raise RuntimeError("Faltan variables SMTP")

    mensaje = EmailMessage()
    mensaje["Subject"] = f"Bajó el precio: {titulo}"
    mensaje["From"] = usuario
    mensaje["To"] = destino
    mensaje.set_content(
        f"Precio actual: {precio}\nProducto: {url}"
    )

    with smtplib.SMTP_SSL("smtp.example.com", 465) as servidor:
        servidor.login(usuario, clave)
        servidor.send_message(mensaje)

Utiliza una contraseña de aplicación o un proveedor transaccional. Nunca guardes credenciales directamente en el archivo.

Integrar el flujo

from datetime import datetime, timezone
import logging

logging.basicConfig(
    filename="monitor_precios.log",
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
    encoding="utf-8",
)


def ejecutar():
    html = descargar_pagina(URL)
    producto = extraer_producto(html)
    estado = cargar_estado()

    precio = producto["precio"]
    ultima_alerta = estado.get("precio_alertado")

    if debe_alertar(precio) and str(precio) != ultima_alerta:
        enviar_correo(producto["titulo"], precio, URL)
        estado["precio_alertado"] = str(precio)

    estado["ultimo_precio"] = str(precio)
    estado["ultima_revision"] = datetime.now(
        timezone.utc
    ).isoformat()
    guardar_estado(estado)
    logging.info("Precio comprobado: %s", precio)

Manejar fallos

def main():
    try:
        ejecutar()
    except requests.Timeout:
        logging.warning("Timeout consultando el producto")
    except requests.HTTPError as error:
        logging.error("HTTP %s", error.response.status_code)
    except (ValueError, KeyError, json.JSONDecodeError):
        logging.exception("No se pudo interpretar la información")
    except OSError:
        logging.exception("Error leyendo o guardando el estado")

No envíes una alerta de “precio cero” cuando el selector falla. Un dato ausente debe tratarse como error, no como oferta.

Programar la ejecución

En Windows utiliza el Programador de tareas. En Linux o macOS puedes usar cron o un servicio. Una frecuencia razonable suele ser cada varias horas, dependiendo del permiso y de los límites del sitio. No ejecutes solicitudes cada pocos segundos.

Probar el monitor

Guarda ejemplos de HTML en archivos locales y prueba la extracción sin Internet. Incluye casos con precio normal, descuento, producto agotado, selector ausente y separadores distintos. Simula también timeout, respuesta 500 y correo fallido. Los tests no deben consultar una tienda real.

Mejoras útiles

Puedes monitorizar varios productos mediante un archivo JSON o CSV, añadir historial para crear gráficos, enviar avisos por Telegram o Discord, usar SQLite y aplicar una ventana de silencio para no repetir notificaciones. Si el sitio ofrece datos estructurados JSON-LD con información de producto, puede ser una fuente más estable que el texto visual.

Seguridad y responsabilidad

Limita la frecuencia, identifica el cliente de forma honesta, almacena secretos fuera del repositorio y registra únicamente información necesaria. No recolectes datos personales ni intentes superar medidas técnicas. Una automatización responsable debe poder desactivarse rápidamente y respetar las condiciones de la fuente.

Conclusión

Un bot de alerta de precios combina scraping, validación, persistencia y notificaciones. La solución confiable comprueba el producto, convierte el valor con precisión, utiliza timeouts, evita alertas duplicadas y maneja cambios de estructura. Empieza con una sola página y una frecuencia conservadora. Después añade pruebas, almacenamiento e interfaces sin comprometer el respeto por el sitio consultado.

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