A Python flight-price tracker needs comparable quote records before it needs a search API. Record the route, departure, currency, fare conditions, tax inclusion and observation time. Only compare prices when those fields match. Search results can help discover an airline or travel page; they do not establish that a displayed fare is current, available or bookable.
Keep quote collection separate from change detection
Obtain quotes through an authorized airline or travel-data interface, or through a source whose access conditions you have checked. Store the provider’s identifier and timestamp. A Reader response is extracted page text, not an inventory reservation or fare guarantee. If the source is unavailable, keep an explicit missing observation rather than carrying an old price forward as today’s quote.
Run a comparison on fictional records
from datetime import datetime
from decimal import Decimal
def quote_key(record):
required = ("route", "departure", "currency", "fare_class", "baggage", "taxes_included", "source")
if any(k not in record for k in required) or type(record["taxes_included"]) is not bool:
raise ValueError("Quote terms are incomplete")
return tuple(record[k] for k in required)
def price_change(previous, current):
if quote_key(previous) != quote_key(current):
return {"status":"not_comparable"}
old_time = datetime.fromisoformat(previous["observed_at"])
new_time = datetime.fromisoformat(current["observed_at"])
if old_time.tzinfo is None or new_time.tzinfo is None or new_time <= old_time:
raise ValueError("Require increasing timezone-aware observation times")
old, new = Decimal(str(previous["amount"])), Decimal(str(current["amount"]))
if not old.is_finite() or not new.is_finite() or min(old, new) < 0:
raise ValueError("Require finite nonnegative prices")
return {"status":"comparable", "difference":str(new-old),
"currency":current["currency"], "observed_at":current["observed_at"]}
if __name__ == "__main__":
# Fictional quotes, not bookable airline inventory.
base = {"route":"AAA-BBB", "departure":"2026-12-01", "currency":"USD",
"fare_class":"economy", "baggage":"one cabin bag", "taxes_included":True,
"source":"synthetic-fixture"}
old = dict(base, amount="240.00", observed_at="2026-10-10T10:00:00+00:00")
new = dict(base, amount="225.00", observed_at="2026-10-10T11:00:00+00:00")
print(price_change(old, new))
Run this file with Python. The fictional USD quote decreases by 15.00; the number comes directly from 225.00 - 240.00, not from an observed airfare. Changing the currency or baggage field returns not_comparable. A timestamp without a timezone, a non-finite price, or an older observation is rejected.
Persist enough information to explain an alert
| Record | Why it matters |
|---|---|
| Source and observed_at | Explains where and when the quoted price was seen |
| Currency and taxes_included | Avoids a false discount caused by a different total |
| Fare class and baggage | Avoids comparing different ticket conditions |
| Availability and expiry, if supplied | Prevents a historical quote from being treated as an offer |
| Failure reason | Distinguishes no data from no price change |
Choose your sampling schedule from the authorized source’s limits and the task’s freshness requirement. Do not translate a concurrency allowance into a guaranteed crawl frequency. For persistence, use an idempotent key for each source observation and retain failed polls as separate events. Only send an alert after the comparison is valid, and show the conditions alongside the price.
Use SERP change monitoring for search-result changes and the freshness measurement method to measure retrieval time. Neither method proves ticket availability. The tracker above makes no token-savings, unlimited-concurrency or legal-compliance claim.