Build a Daily Deal Tracker: Scrape, Compare, and Email Yourself

Part 4 of the Python for Automation track. Last updated: September 2026.

Time to assemble everything: Part 1 gave you file handling, Part 2 taught scraping, Part 3 covered scheduling and alerts. This post builds a daily deal tracker — a script that scrapes product prices every morning, remembers yesterday's prices, and emails you only when a price drops. It is the canonical automation project, and the pattern (scrape → store → diff → alert → schedule) generalizes to job listings, flight prices, and competitor monitoring.

The architecture

  1. Scrape — fetch the product pages, extract name + price (Part 2).
  2. Store — keep yesterday's prices in a small JSON file (Part 1's file skills).
  3. Diff — compare; collect only the price drops.
  4. Alert — one email summarizing the drops, or silence if nothing changed (Part 3).
  5. Schedule — run it daily at 7 AM (Part 3).

Step 1: scrape the prices

One function per site keeps selectors isolated — when a site redesigns, you fix one function, not the whole script. Adapt the selectors to the real pages you track:

import time
import requests
from bs4 import BeautifulSoup

HEADERS = {"User-Agent": "Mozilla/5.0 (deal-tracker; contact: you@example.com)"}

PRODUCTS = {
    "Wireless Mouse": "https://example.com/products/wireless-mouse",
    "USB-C Hub": "https://example.com/products/usb-c-hub",
}

def scrape_price(url: str) -> float:
    time.sleep(2)  # polite: don't hammer the site
    r = requests.get(url, headers=HEADERS, timeout=10)
    r.raise_for_status()
    soup = BeautifulSoup(r.text, "html.parser")
    price_text = soup.select_one("span.price").get_text(strip=True)
    return float(price_text.replace("$", "").replace(",", ""))

def scrape_all() -> dict:
    return {name: scrape_price(url) for name, url in PRODUCTS.items()}

Step 2: store yesterday's prices

A JSON file is a perfectly good database for a dozen products — no server, human-readable, diffable in git:

import json
from pathlib import Path

STORE = Path("prices.json")

def load_old_prices() -> dict:
    if STORE.exists():
        return json.loads(STORE.read_text())
    return {}  # first run: nothing to compare against

def save_prices(prices: dict) -> None:
    STORE.write_text(json.dumps(prices, indent=2))

Step 3: diff — alert only on drops

The key design decision: the script is silent unless something got cheaper. New products are recorded quietly; only genuine drops make the email:

def find_drops(old: dict, new: dict) -> dict:
    drops = {}
    for name, price in new.items():
        if name in old and price < old[name]:
            drops[name] = (old[name], price)   # (was, now)
    return drops

old = {"Wireless Mouse": 29.99, "USB-C Hub": 39.50}
new = {"Wireless Mouse": 24.99, "USB-C Hub": 39.50, "Keyboard": 59.00}
print(find_drops(old, new))
# {'Wireless Mouse': (29.99, 24.99)}

Step 4: email the deals

Reuse the send_alert function from Part 3. One email per run, listing every drop with the savings:

def email_drops(drops: dict, to: str) -> None:
    lines = ["Price drops found:\n"]
    for name, (was, now) in drops.items():
        saved = was - now
        lines.append(f"- {name}: ${was:.2f} -> ${now:.2f} (save ${saved:.2f})")
    send_alert(f"{len(drops)} price drop(s) today", "\n".join(lines), to)

# email_drops({'Wireless Mouse': (29.99, 24.99)}, "you@gmail.com")
# Subject: 1 price drop(s) today
# - Wireless Mouse: $29.99 -> $24.99 (save $5.00)

Step 5: assemble and schedule

The full script, using Part 3's reliable-job template — logging throughout, email on crash, and on success it updates the price store so tomorrow has something to compare against:

import logging
import traceback

log = logging.getLogger(__name__)

def main():
    log.info("Deal tracker started")
    new_prices = scrape_all()
    log.info(f"Scraped {len(new_prices)} products")

    old_prices = load_old_prices()
    drops = find_drops(old_prices, new_prices)

    if drops:
        log.info(f"{len(drops)} price drop(s) found, emailing")
        email_drops(drops, "you@gmail.com")
    else:
        log.info("No price drops — staying silent")

    save_prices({**old_prices, **new_prices})  # merge, keep history
    log.info("Deal tracker finished OK")

if __name__ == "__main__":
    try:
        main()
    except Exception:
        log.error("Tracker crashed:\n" + traceback.format_exc())
        send_alert("Automation FAILED: deal tracker",
                   traceback.format_exc(), "you@gmail.com")
        raise

Then schedule it — one cron line runs the whole thing every morning:

# crontab -e — every day at 7:00 AM, log everything
# 0 7 * * * /usr/bin/python3 /home/you/deal_tracker.py >> /home/you/tracker.log 2>&1

Where to take it from here

  • More products, more sites — add entries to PRODUCTS; one scrape function per site layout.
  • Price history chart — append each day's prices to a CSV and plot the trend with matplotlib (Data Science track, Part 4).
  • Smarter alerts — only email when the drop exceeds 10%, or when the price hits its all-time low in your history.
  • Other targets — the same scrape → store → diff → alert skeleton tracks job postings, flight prices, or competitor changelogs.

Key takeaways

  • The automation pattern is scrape → store → diff → alert → schedule — it generalizes far beyond prices.
  • A JSON file is a fine database for small trackers; merge new data so history survives.
  • Alert only on changes that matter — a silent script is a trustworthy script.
  • Wrap everything in the reliable-job template: logging, try/except, email on crash.
  • You now own the complete automation toolkit: files, scraping, scheduling, and alerts.

You've completed the Python for Automation track! Browse every tutorial on the Python topic page. Next on the roadmap: Python Interview Prep.

Comments

Popular posts from this blog

Java Banking Finance Services and Insurance (BFSI) domain interview questions

JSP Servlet Interview Questions For Freshers Series 1

Java program to check even or odd number