1. Request the page
Use requests to download the product page on a timer.
Python classroom demo
This demo page is built to help students connect a real-world automation idea to a readable Python script: check a product page, detect when stock returns, and send one clear alert.
Tracked item
08:00 - product still sold out
09:30 - product page changed
09:30 - notification sent
11:00 - product still in stock, no duplicate alert
How it works
Use requests to download the product page on a timer.
Use BeautifulSoup to find the title, price, and stock label.
Compare the current result with the last saved result.
Send an email only when the item flips from sold out to available.
Python example
Update the selectors for the target site and add email credentials through environment variables when you want real alerts.
import json
import os
import smtplib
import ssl
import time
from dataclasses import dataclass
from email.message import EmailMessage
import requests
from bs4 import BeautifulSoup
PRODUCT_URL = "https://example.com/products/quiet-keyboard"
CHECK_EVERY_SECONDS = 90
STATE_FILE = "monitor_state.json"
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/126.0 Safari/537.36"
)
}
@dataclass
class ProductStatus:
title: str
in_stock: bool
price: str
def fetch_status() -> ProductStatus:
response = requests.get(PRODUCT_URL, headers=HEADERS, timeout=15)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
title = soup.select_one("h1").get_text(strip=True)
price = soup.select_one("[data-price]").get_text(" ", strip=True)
stock_text = soup.select_one(".stock-status").get_text(" ", strip=True).lower()
in_stock = "out of stock" not in stock_text and "sold out" not in stock_text
return ProductStatus(title=title, in_stock=in_stock, price=price)
def load_previous_state() -> bool:
if not os.path.exists(STATE_FILE):
return False
with open(STATE_FILE, "r", encoding="utf-8") as file:
return json.load(file).get("was_in_stock", False)
def save_state(in_stock: bool) -> None:
with open(STATE_FILE, "w", encoding="utf-8") as file:
json.dump({"was_in_stock": in_stock}, file)
def send_notification(status: ProductStatus) -> None:
sender = os.environ.get("ALERT_EMAIL")
password = os.environ.get("ALERT_APP_PASSWORD")
recipient = os.environ.get("ALERT_RECIPIENT", sender)
subject = f"Back in stock: {status.title}"
body = (
f"{status.title} is available again.\n"
f"Price: {status.price}\n"
f"Link: {PRODUCT_URL}"
)
if not sender or not password or not recipient:
print("EMAIL SETTINGS MISSING")
print(body)
return
message = EmailMessage()
message["Subject"] = subject
message["From"] = sender
message["To"] = recipient
message.set_content(body)
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
server.login(sender, password)
server.send_message(message)
def monitor() -> None:
was_in_stock = load_previous_state()
while True:
try:
status = fetch_status()
print(f"Checked {status.title}: in_stock={status.in_stock}")
if status.in_stock and not was_in_stock:
send_notification(status)
save_state(status.in_stock)
was_in_stock = status.in_stock
except Exception as error:
print(f"Monitor error: {error}")
time.sleep(CHECK_EVERY_SECONDS)
if __name__ == "__main__":
monitor()