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
- Scrape — fetch the product pages, extract name + price (Part 2).
- Store — keep yesterday's prices in a small JSON file (Part 1's file skills).
- Diff — compare; collect only the price drops.
- Alert — one email summarizing the drops, or silence if nothing changed (Part 3).
- 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
Post a Comment