Python Central HubSign in

Automation and Scripting / Phase 3: Web Automation & Scraping

Building a Price Tracker Bot

Build a simple (ethical) price tracker structure - fetch, parse, store history, notify on drop.

Architecture

  1. Fetch product page (or API)
  2. Parse price
  3. Save price history (CSV/SQLite)
  4. Compare with threshold
  5. Notify (email/telegram)

Example: parse a price from HTML

Python
from bs4 import BeautifulSoup
 
html = "<span class='price'>$199.99</span>"
soup = BeautifulSoup(html, "html.parser")
price_text = soup.select_one(".price").get_text(strip=True)
print(price_text)

Persist history (CSV)

Python
import csv
from datetime import datetime
 
 
def append_price(csv_path: str, price: str):
    with open(csv_path, "a", newline="", encoding="utf-8") as f:
        w = csv.writer(f)
        w.writerow([datetime.now().isoformat(), price])

Notes

Real sites often block scraping.

Prefer APIs, and respect site policies.

Visualize it

A tracker bot loops through fetch, parse, and store steps, retrying politely when a request fails.

diagram Price tracker loop mermaid
How the bot fetches, parses, and stores prices, with retry on failure

How the tracker fits together

A price tracker is a small loop that runs on a schedule. Each run does the same five things, and each is a separate function, so you can test the parts that do not need the network.

The parts that are pure Python are the most valuable to get right, and the price text is the trickiest. A web page shows $1,199.99 or 1.199,99 โ‚ฌ, and you need a number. Parse it in one function, and handle failure:

Python
import csv
from datetime import datetime
from pathlib import Path
 
 
def parse_price(text: str) -> float:
    cleaned = text.replace("$", "").replace(",", "").strip()
    return float(cleaned)          # raises ValueError if the layout changed
 
 
def last_price(csv_path: Path) -> float | None:
    if not csv_path.exists():
        return None
    with csv_path.open(newline="", encoding="utf-8") as f:
        rows = list(csv.reader(f))
    return float(rows[-1][1]) if rows else None
 
 
def check(csv_path: Path, price_text: str, threshold: float) -> None:
    price = parse_price(price_text)
    previous = last_price(csv_path)
 
    with csv_path.open("a", newline="", encoding="utf-8") as f:
        csv.writer(f).writerow([datetime.now().isoformat(), price])
 
    if price <= threshold and (previous is None or previous > threshold):
        print(f"ALERT: price fell to {price} (threshold {threshold})")
 
 
check(Path("history.csv"), "$199.99", threshold=200)

The alert fires only when the price crosses the threshold, not on every run while it stays below. Without that check you would get an email every hour for as long as the sale lasts.

Why store history

Storing every price, not just the latest, lets you see trends, spot a fake "discount", and debug. CSV is fine for one product. For many products, use SQLite (sqlite3 is in the standard library) with a table of product, time, and price.

The fetch step, done politely

Fetch with requests and a timeout, send a User-Agent that names your bot, and run at most a few times a day. Check robots.txt and the terms first. Many shops forbid scraping. If the store has an official API or a price feed, use that. Scrape only what the site allows, and stop if you get 403 or 429 responses.

Common mistakes

  • Trusting a selector forever. Sites change their layout. When select_one returns None, log it and alert yourself instead of crashing silently.
  • Parsing without handling locale. 1.299,00 and 1,299.00 mean the same thing but parse differently.
  • Alerting on every run. Track the previous state.
  • Running every minute. That is rude to the site and gets you blocked. Hourly or daily is plenty.
  • Storing the price as text. Store numbers so you can compare them.

Adapting it

Wire the alert into the email, Telegram, or Slack lessons, and run the script with cron, Task Scheduler, or the schedule library.

๐Ÿงช Try It Yourself

Exercise 1 โ€“ Parse a Price and Compare

exerciseYour turn

Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.

Output
Press Run to see what your code prints.

Check your understanding

quizCheck yourself
  1. Why does the tracker compare the new price with the previous one before alerting?

    Show answer

    A โ€” To avoid sending the same alert on every run while the price stays below the threshold โ€” Alerting on the crossing, rather than on the state, gives one message when the price drops instead of one per run.

  2. select_one(".price") returns None after the site redesigns. What should the bot do?

    Show answer

    B โ€” Log the failure and notify you, rather than storing a wrong value โ€” A missing element is a signal that the page changed. Storing 0 would corrupt the history and could trigger false alerts.

  3. How often should a price tracker fetch a single product page?

    Show answer

    B โ€” Rarely, such as hourly or daily, and only where the site allows scraping โ€” Frequent requests burden the site and get you blocked. Prices rarely change more than a few times a day.

Was this page helpful?

Let us know how we did