DEV Community

huynhtanan
huynhtanan

Posted on

Daily price tracking for competitor hotels, with Python and SQLite

If you run a hotel, a travel site, or just a spreadsheet of competitors you want to keep an eye on, the question that comes up every morning is simple: are my competitors cheaper than me today, and by how much?

Google Hotels is the easiest place to answer it. For every hotel it shows the lowest price from 20-40 booking sites plus the hotel's own website, for the exact dates you pick, in any currency. There is no official API, but you can get the same data with a one-page Python script and a daily schedule.

This post shows the full setup. Code is in github.com/huynhtanan/google-hotels-api (MIT). Disclosure: I built the Apify scraper this uses; it's paid per result, new Apify accounts get free monthly credit that covers a few thousand hotels.

The script: one SQLite table, one function per concern

Give it a list of hotel names (or hotel URLs from Google Hotels) and a check-in date. Each run saves one row per hotel to a local SQLite file and prints how prices moved versus the previous observation.

# track_prices.py — see github.com/huynhtanan/google-hotels-api/blob/main/python/track_prices.py
import os, sqlite3
from datetime import date, timedelta
from pathlib import Path
from apify_client import ApifyClient

HOTELS   = ["Hilton Paris Opera", "Pullman Paris Tour Eiffel", "Le Meurice Paris"]
CHECK_IN = date.today() + timedelta(days=30)
DB       = Path("prices.sqlite3")

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("zet46/google-hotels-scraper").call(run_input={
    "queries": HOTELS, "maxHotelsPerQuery": 1,
    "checkIn": CHECK_IN.isoformat(),
    "checkOut": (CHECK_IN + timedelta(days=1)).isoformat(),
    "currency": "EUR",
})
items = list(client.dataset(run.default_dataset_id).iterate_items())

with sqlite3.connect(DB) as con:
    con.execute("""CREATE TABLE IF NOT EXISTS prices (
        run_at TEXT, hotel TEXT, check_in TEXT, nightly REAL,
        PRIMARY KEY (run_at, hotel, check_in))""")
    for h in items:
        con.execute("INSERT OR REPLACE INTO prices VALUES (?,?,?,?)",
                    (str(run.started_at), h["name"], CHECK_IN.isoformat(), h.get("pricePerNight")))
Enter fullscreen mode Exit fullscreen mode

Full version (with the "DOWN 5.4% (was 280.00 on 2026-10-15)" comparison printing) is in the repo.

First run, then the next day

$ python track_prices.py
...Status: SUCCEEDED, Message: Done: 3 hotels from 3 queries

Prices on 2026-10-15 for check-in 2026-11-14 (EUR):

  Hilton Paris Opera                               280.00   (first observation)
  Le Meurice                                      1535.00   (first observation)
  Pullman Paris Tour Eiffel                        320.00   (first observation)
Enter fullscreen mode Exit fullscreen mode

The next time the script runs it compares against the previous row for the same (hotel, check_in) and prints the direction and the diff:

  Hilton Paris Opera                               265.00   DOWN  5.4%  (was 280.00 on 2026-10-15)
  Le Meurice                                      1535.00   flat  0.0%  (was 1535.00 on 2026-10-15)
  Pullman Paris Tour Eiffel                        340.00     UP  6.3%  (was 320.00 on 2026-10-15)
Enter fullscreen mode Exit fullscreen mode

Because the data lives in SQLite, you can plot the whole history or alert yourself with one SQL line:

-- competitors who undercut their own 7-day average by 10% or more
SELECT hotel, nightly, avg7
FROM (
  SELECT hotel, nightly, run_at,
         AVG(nightly) OVER (PARTITION BY hotel, check_in
                            ORDER BY run_at ROWS BETWEEN 7 PRECEDING AND 1 PRECEDING) AS avg7
  FROM prices)
WHERE avg7 IS NOT NULL AND nightly < avg7 * 0.9
ORDER BY run_at DESC;
Enter fullscreen mode Exit fullscreen mode

Running it every day

Two options, both free:

  • Apify Scheduler (zero infrastructure). On the actor page click Schedule actor, pick a cron (e.g. daily 09:00), save your input (hotel list + dates). Then from any machine run the script above with client.actor(...).last_run().get() to pull the latest completed dataset. No local cron, no server.
  • Local cron / Windows Task Scheduler. Call python track_prices.py daily. Each run also triggers a fresh scrape, so this is enough on its own.

Cost

The scraper charges $2 per 1,000 hotels. Ten competitors tracked daily is ~300 hotels/month, so about $0.60/month. Turn on includeProviderPrices: true only if you also want the per-site breakdown (Booking.com vs Expedia vs the hotel's website); it doubles the per-hotel cost but lets you catch rate-parity breaks on specific channels.

A couple of things that bit me

  • pricePerNight is the nightly price including taxes that Google displays. totalPrice is the whole stay including taxes and fees. If you compare across sources, pick one and stick with it.
  • Dates and currency are not URL query parameters on Google Hotels; they are encoded in the ts protobuf blob. The scraper handles that for you, which is why calling it directly is a lot less work than parsing Google's HTML yourself.
  • Hotel names are matched by Google's own search, so "Hotel Lutetia Paris" may silently return 0 results if Google's suggest doesn't lock on; pass the hotel's Google Hotels URL (/travel/hotels/entity/<token>) via hotelUrls when you need to be exact.

Feedback welcome

If you'd like a specific feature (price by room type, SQLite -> Google Sheets sync, a Discord alert webhook), the Apify actor repo issues are open and I ship fixes within a day. Code and more examples: github.com/huynhtanan/google-hotels-api.

Top comments (0)