Скрипт автоматической проверки цен с уведомлениями (Telegram + Email)

    📝 Кратко: Хватит ждать скидок вручную! Я создал полноценный скрипт для **мониторинга цен Python**, который будет автоматически проверять сайт, сравнивать текущую цену с сохраненной и отправлять уведомления о снижении в Telegram и на Email.
Скрипт автоматической проверки цен на python

Привет! Наверное, каждый из нас сталкивался с желанием купить что-то, но с мыслью: «Подожду, пока цена упадет». Отслеживать цены вручную — это утомительно и неэффективно. К счастью, Python идеально подходит для создания собственного мини-сервиса мониторинга цен Python.

Этот проект является отличным примером автоматизации: вы заставляете компьютер работать за вас, пока отдыхаете. Мы не будем просто парсить HTML, мы создадим полноценную систему, которая:

  1. Безопасно хранит данные (URL и прошлые цены).
  2. Надежно извлекает данные с помощью BeautifulSoup.
  3. Сравнивает новую цену со старой.
  4. Отправляет персонализированные уведомления (Telegram и Email) только при реальном снижении цены.

Давайте погрузимся в код и создадим скрипт, который сэкономит вам деньги!

🛠️ Шаг 1. Настройка среды и базы данных

Для реализации проекта нам понадобятся три ключевые библиотеки: requests для получения HTML-кода, BeautifulSoup для парсинга, и sqlite3 для хранения данных (встроенная в Python).

Установка зависимостей и переменные окружения

pip install requests beautifulsoup4 python-dotenv

Вместо традиционного pip, я рекомендую использовать современный, высокоскоростной менеджер uv (от создателей Ruff). Ознакомиться с новым пакетным менеджером и его преимуществами вы можете в моей статье: uv — молниеносный менеджер пакетов и виртуальных окружений

Мы используем python-dotenv для безопасного хранения конфиденциальных данных (токены Telegram, данные Email-аккаунта).

import sqlite3
import requests
from bs4 import BeautifulSoup
import re
import os
from dotenv import load_dotenv

# Демонстрационный URL и селектор для тестов
DEMO_URL = "http://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"
DEMO_SELECTOR = "p.price_color"

DB_NAME = 'price_monitor.db'
HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
# Загрузка переменных окружения
load_dotenv()
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")
EMAIL_SENDER = os.getenv("EMAIL_SENDER")

Инициализация базы данных (SQLite)

Чтобы скрипт мог сравнивать новую цену с предыдущей, нам нужно место для хранения данных. SQLite — это простой локальный файл, идеально подходящий для нашего мини-сервиса.

def setup_database():
    """Создает базу данных и таблицу items, если они не существуют."""
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS items (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            url TEXT NOT NULL,
            last_price REAL,
            css_selector TEXT NOT NULL
        )
    """)
    conn.commit()
    conn.close()
    print("База данных готова.")

🕸️ Шаг 2. Надежное извлечение цены (Парсинг)

Успех скрипта мониторинга цен Python на 90% зависит от правильного CSS-селектора. Вам нужно вручную найти уникальный класс или ID элемента, который содержит цену.

Поиск CSS-селектора на сайте

Чтобы понять, какой селектор использовать, откройте страницу товара в браузере, наведите курсор на цену, кликните правой кнопкой мыши и выберите «Просмотреть код» или «Inspect». Найдите уникальный класс или ID, который содержит цену.

Например, для демо-сайта Books To Scrape цена находится в теге <p> с классом price_color. Наш селектор: p.price_color или просто .price_color.

Поиск селектора

Функция извлечения и очистки цены

Эта функция загружает HTML, ищет элемент по селектору и с помощью регулярных выражений очищает цену от символов валюты, пробелов и запятых, чтобы получить чистое число типа float.

def get_current_price(url, css_selector):
    """Извлекает и очищает цену с веб-страницы по селектору."""
    try:
        # 1. Загрузка страницы
        response = requests.get(url, headers=HEADERS, timeout=10)
        response.raise_for_status() # Проверка HTTP-статуса
        
        # 2. Парсинг HTML
        soup = BeautifulSoup(response.text, 'html.parser')
        
        # 3. Поиск элемента по CSS-селектору
        price_element = soup.select_one(css_selector)
        
        if not price_element:
            print(f"Ошибка парсинга: Элемент по селектору '{css_selector}' не найден.")
            return None

        raw_price_text = price_element.get_text()
        
        # 4. Очистка текста: извлечение числа
        clean_price_match = re.search(r'[\d,.]+', raw_price_text)
        
        if clean_price_match:
            # Преобразование в float
            cleaned_text = clean_price_match.group(0).replace(',', '.')
            return float(cleaned_text)
        
        return None

    except requests.exceptions.RequestException as e:
        print(f"Ошибка запроса страницы {url}: {e}")
        return None
    except ValueError:
        print(f"Ошибка конвертации цены: {raw_price_text}")
        return None

📚 Документация: Beautiful Soup 4

🔔 Шаг 3. Сравнение и логика уведомлений

Теперь мы объединяем базу данных и парсер, чтобы выполнить основную логику: сравнение текущей цены с сохраненной и отправку уведомления.

Функции отправки уведомлений (Заглушки)

Для начала нам понадобятся две функции-заглушки. Они будут вызваны, когда обнаружится снижение цены. Обратите внимание, что мы добавляем pass и простой print(), чтобы основной скрипт мог работать без настройки Telegram или Email.

def send_telegram_notification(message):
    """Отправляет сообщение в Telegram (заглушка)."""
    # Здесь будет ваша реализация из статьи по Telegram API
    print(f"Уведомление (Telegram): {message}")
    pass

def send_email_notification(subject, body):
    """Отправляет письмо на Email (заглушка)."""
    # Здесь будет ваша реализация из статьи по Email
    print(f"Уведомление (Email) с темой: {subject}")
    pass

Основной цикл проверки

Функция check_and_notify будет выполняться при каждом запуске скрипта. Она выполняет три основные задачи: считывание данных, вызов парсера и обновление базы данных.

def check_and_notify():
    """Проверяет цены всех товаров, сравнивает с предыдущей и отправляет уведомления."""
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.cursor()
    
    # 1. Выбираем все товары
    cursor.execute("SELECT id, name, url, last_price, css_selector FROM items")
    items = cursor.fetchall()
    
    changes_detected = False

    for item_id, name, url, last_price, selector in items:
        current_price = get_current_price(url, selector)
        
        if current_price is None:
            continue

        if last_price is None:
            print(f"[{name}] Первая запись цены: {current_price} сохранена.")
            
        elif current_price < last_price:
            # 2. Обнаружено снижение цены
            reduction = round(last_price - current_price, 2)
            print(f"[{name}] Снижение цены! {last_price} -> {current_price}")
            changes_detected = True
            
            message = (f"&#x1f4b8 СНИЖЕНИЕ ЦЕНЫ! Товар: {name} ({url}). "
                       f"Цена упала с {last_price} до {current_price} (на {reduction}).")
            
            # 3. Вызов функций уведомления
            send_telegram_notification(message)
            send_email_notification(f"Снижение цены на {name}", message)
            
        else:
            print(f"[{name}] Цена стабильна или выросла: {current_price}")

        # 4. Обновление сохраненной цены
        cursor.execute("UPDATE items SET last_price = ? WHERE id = ?", (current_price, item_id))
    
    conn.commit()
    conn.close()
    if changes_detected:
        print("\nПроверка завершена. Обнаружены изменения цены.")
    else:
        print("\nПроверка завершена. Цены стабильны.")

Интеграция уведомлений

В основном коде мы используем простые функции-заглушки. Для того чтобы они заработали с реальными сервисами, вам потребуется настройка.

⚙️ Шаг 4. Полный код и запуск сервиса

Для удобства, весь код скрипта мониторинга цен Python собран в единый файл. Убедитесь, что у вас установлен файл .env с переменными окружения.

Полный листинг monitor.py

import sqlite3
import requests
from bs4 import BeautifulSoup
import re
import os
from dotenv import load_dotenv

# --- КОНФИГУРАЦИЯ И ПЕРЕМЕННЫЕ ---
# Демонстрационный URL и селектор для тестов (Books To Scrape)
DEMO_URL = "http://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"
DEMO_SELECTOR = "p.price_color"

DB_NAME = 'price_monitor.db'
HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}

# Загрузка переменных окружения
load_dotenv()
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")
EMAIL_SENDER = os.getenv("EMAIL_SENDER")

# --- ФУНКЦИИ БАЗЫ ДАННЫХ ---
def setup_database():
    """Создает базу данных и таблицу items, если они не существуют."""
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS items (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            url TEXT NOT NULL,
            last_price REAL,
            css_selector TEXT NOT NULL
        )
    """)
    conn.commit()
    conn.close()

# --- ФУНКЦИИ ПАРСИНГА ---
def get_current_price(url, css_selector):
    """Извлекает и очищает цену с веб-страницы по селектору."""
    try:
        response = requests.get(url, headers=HEADERS, timeout=10)
        response.raise_for_status() 
        
        soup = BeautifulSoup(response.text, 'html.parser')
        price_element = soup.select_one(css_selector)
        
        if not price_element:
            print(f"Ошибка парсинга: Элемент по селектору '{css_selector}' не найден.")
            return None

        raw_price_text = price_element.get_text()
        clean_price_match = re.search(r'[\d,.]+', raw_price_text)
        
        if clean_price_match:
            cleaned_text = clean_price_match.group(0).replace(',', '.')
            return float(cleaned_text)
        
        return None

    except requests.exceptions.RequestException as e:
        print(f"Ошибка запроса страницы {url}: {e}")
        return None
    except ValueError:
        print(f"Ошибка конвертации цены: {raw_price_text}")
        return None

# --- ФУНКЦИИ УВЕДОМЛЕНИЙ (Заглушки) ---
def send_telegram_notification(message):
    """Отправляет сообщение в Telegram (заглушка)."""
    # Здесь будет ваша реализация из статьи "Как отправить сообщение в Telegram..."
    print(f"Уведомление (Telegram): {message}")
    pass

def send_email_notification(subject, body):
    """Отправляет письмо на Email (заглушка)."""
    # Здесь будет ваша реализация из статьи "Как отправить письмо с Python через Gmail..."
    print(f"Уведомление (Email) с темой: {subject}")
    pass

# --- ОСНОВНАЯ ЛОГИКА ---
def check_and_notify():
    """Проверяет цены всех товаров, сравнивает с предыдущей и отправляет уведомления."""
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.cursor()
    
    cursor.execute("SELECT id, name, url, last_price, css_selector FROM items")
    items = cursor.fetchall()
    
    changes_detected = False

    for item_id, name, url, last_price, selector in items:
        current_price = get_current_price(url, selector)
        
        if current_price is None:
            continue

        if last_price is None:
            print(f"[{name}] Первая запись цены: {current_price} сохранена.")
            
        elif current_price < last_price:
            reduction = round(last_price - current_price, 2)
            print(f"[{name}] Снижение цены! {last_price} -> {current_price}")
            changes_detected = True
            
            message = (f"&#x1f4b8 СНИЖЕНИЕ ЦЕНЫ! Товар: {name} ({url}). "
                       f"Цена упала с {last_price} до {current_price} (на {reduction}).")
            
            send_telegram_notification(message)
            send_email_notification(f"Снижение цены на {name}", message)
            
        else:
            print(f"[{name}] Цена стабильна или выросла: {current_price}")

        # Обновление сохраненной цены
        cursor.execute("UPDATE items SET last_price = ? WHERE id = ?", (current_price, item_id))
    
    conn.commit()
    conn.close()
    if changes_detected:
        print("\nПроверка завершена. Обнаружены изменения цены.")
    else:
        print("\nПроверка завершена. Цены стабильны.")

# --- ЗАПУСК СКРИПТА ---
if __name__ == '__main__':
    # 1. Настройка базы данных
    setup_database()

    # 2. Добавление тестового товара (Если его еще нет в базе)
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.cursor()
    
    # Проверка, чтобы не дублировать тестовый товар
    cursor.execute("SELECT COUNT(*) FROM items WHERE name=?", ('Тестовый товар (Книга)',))
    if cursor.fetchone()[0] == 0:
        cursor.execute("INSERT INTO items (name, url, css_selector) VALUES (?, ?, ?)", 
                       ('Тестовый товар (Книга)', DEMO_URL, DEMO_SELECTOR))
        conn.commit()
        print(f"Добавлен тестовый товар: Тестовый товар (Книга).")
    
    conn.close()

    # 3. Запуск основного цикла проверки
    check_and_notify()

Инструкция по запуску

Сохраните этот код в файл monitor.py. После вам достаточно запустить его из консоли:

python monitor.py

Планирование автоматического запуска

Для того чтобы скрипт работал как полноценный сервис, его нужно запускать по расписанию:

  • Linux/macOS: Используйте cron (crontab -e) для ежедневного или ежечасного запуска скрипта.
  • Windows: Используйте Планировщик заданий Windows (Task Scheduler).

✅ Заключение

Мы создали полноценный мини-сервис мониторинга цен Python, который включает все необходимые компоненты: от хранения данных в SQLite до сложной логики сравнения и отправки уведомлений.

Этот скрипт — отличная основа, которую вы можете масштабировать: добавить больше сайтов, настроить асинхронный парсинг для ускорения проверки (используя aiohttp) или интегрировать его в веб-интерфейс.

Самое главное: вы автоматизировали рутинную и потенциально дорогостоящую задачу, заставив Python работать на экономию ваших средств!

🔁 Если вам полезны советы по Python для автоматизации и уведомлений, посмотрите также:
Как отправить сообщение в Telegram с помощью Python — подробный гайд, необходимый для настройки Telegram-уведомлений
Как отправить письмо с Python через Gmail — инструкция по настройке Email-уведомлений для вашего скрипта
Как ускорить Python в 10 раз с помощью асинхронности — будет полезно, если вы решите мониторить десятки или сотни товаров одновременно
💬 Остались вопросы? Пишите в комментариях — с радостью уточню, дополню или помогу с вашим кодом.
📢 Подписывайтесь на Telegram-канал PythonAuto, чтобы не пропустить новые гайды по автоматизации, парсингу и Python.
👉 Ваш интерес — лучшая мотивация для новых статей!

Оставьте комментарий