DEV Community

elliott maqy
elliott maqy

Posted on

How to scrape Agoda hotel prices with Python

Agoda lists thousands of properties in a large city: about 6,800 in Paris and 4,300 in Lisbon for a typical search. Its site shows them 45 at a time, and the prices it advertises leave the taxes out. This tutorial scrapes Agoda hotel prices with Python: one city, dates relative to today so the script never expires, the cheapest hotels first, a short pandas analysis by neighbourhood, then a daily price tracker that flags every drop. The data comes from the Agoda Hotels Scraper, an Apify Actor called through the official Python client.

What you need

  • An Apify account (the free plan is enough to try this) and its API token, from Apify Console → Settings → API & Integrations, exported as an environment variable: export APIFY_TOKEN=...
  • Python 3 and two packages:
pip install "apify-client>=3,<4" pandas
Enter fullscreen mode Exit fullscreen mode

What one row contains

One row per property of the search, with the same columns as the other travel scrapers of the studio:

  • prices in currency: pricePerNight and priceTotal (the whole stay, one room) of the cheapest offer, taxes and fees included by default, taxesAndFees for the stay, and priceBeforeDiscount, the strike-through price per night of a deal;
  • the property: title, propertyType, starRating (half stars possible), rating on 10, reviewCount, region (the neighbourhood), latitude, longitude and badges ("Insider deal", "Sponsored"…);
  • the search: checkIn, checkOut, nights, guests, searchRank, and a url that opens the property on the same stay.

isAvailable is false when a property is sold out for your dates (or Agoda had not computed its price yet); its prices are then empty, and the row is kept.

Step 1: one city, relative dates, cheapest first

This is the input of the public task "Track Agoda hotel prices in Tokyo daily", without the tracking for now:

run_input = {
    "location": "Tokyo",
    "sortBy": "PRICE_ASC",
    "checkInDaysFromNow": 30,
    "nights": 3,
    "adults": 2,
    "currency": "USD",
    "maxResults": 60,
}
Enter fullscreen mode Exit fullscreen mode
  • location is the city as typed in the Agoda search box; the run log shows the city Agoda retained. cityId (the city parameter of an Agoda search URL) is the precise alternative.
  • checkInDaysFromNow and nights describe the stay relative to today: check-in in 30 days, 3 nights. Every run searches new dates, which is what you want for a schedule. For one precise stay, use "checkIn" and "checkOut" instead (YYYY-MM-DD).
  • sortBy PRICE_ASC returns the cheapest first. Agoda sorts on its price before taxes, while priceTotal includes them by default, so the order of priceTotal can differ slightly from searchRank. REVIEW_SCORE_DESC sorts by guest score instead.
  • maxResults is the number of properties saved and charged. One request returns up to 100, so 60 properties take a few seconds.

To follow several cities with the same stay, list them in locations ("Tokyo", "Osaka", "Kyoto"): each is its own search, maxResults applies to each, and searchLocation tells the rows apart.

Step 2: run it

Save this as agoda_prices.py:

import os

import pandas as pd
from apify_client import ApifyClient

run_input = {
    "location": "Tokyo",
    "sortBy": "PRICE_ASC",
    "checkInDaysFromNow": 30,
    "nights": 3,
    "adults": 2,
    "currency": "USD",
    "maxResults": 60,
}

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("myth84/agoda-hotels-scraper").call(run_input=run_input)
rows = list(client.dataset(run.default_dataset_id).iterate_items())

for item in rows[:10]:
    print(item["searchRank"], item["title"], item["region"], item["pricePerNight"], item["priceTotal"], item["currency"])

df = pd.DataFrame(rows)
df.to_csv("agoda-tokyo.csv", index=False)
print(f'{len(df)} properties from {df["checkIn"].iloc[0]} to {df["checkOut"].iloc[0]} saved to agoda-tokyo.csv')
Enter fullscreen mode Exit fullscreen mode

A run that finds nothing fails with an explicit message in its log (city not recognised, dates in the past), so an empty file never passes for a quiet market.

Step 3: a quick analysis with pandas

Which neighbourhoods are cheapest, and which hotels give the best score for the price? Add this to the script:

priced = df[df["priceTotal"].notna()].copy()
priced["sponsored"] = priced["badges"].map(lambda badges: "Sponsored" in (badges or []))
priced = priced[~priced["sponsored"]]

by_area = priced.groupby("region").agg(
    properties=("title", "count"),
    medianPerNight=("pricePerNight", "median"),
    medianRating=("rating", "median"),
)
print(by_area.sort_values("medianPerNight").round(1).head(10))

well_rated = priced[(priced["rating"] >= 8.5) & (priced["reviewCount"] >= 100)]
print(well_rated.sort_values("pricePerNight")[["title", "region", "starRating", "rating", "reviewCount", "pricePerNight", "url"]].head(10))
Enter fullscreen mode Exit fullscreen mode

The first table gives the median nightly price and score of each neighbourhood among the cheapest properties of the city. The second keeps the properties rated 8.5 or more by at least 100 guests, cheapest first: usually the list people actually want. Paid placements are left out with the "Sponsored" badge.

taxesAndFees is the difference between Agoda's price with and without taxes for the stay. With "priceView": "exclusive", the price columns hold the prices before taxes, the ones Agoda advertises.

Step 4: a daily price tracker

Add "monitorPrices": True and the Actor compares the priceTotal of each property with your previous run of the same search, kept in a key-value store of your Apify account (agoda-price-monitor by default, set with monitorStoreName). Save this as track_agoda.py:

import os

import pandas as pd
from apify_client import ApifyClient

HISTORY = "agoda-tokyo-history.csv"

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("myth84/agoda-hotels-scraper").call(run_input={
    "location": "Tokyo",
    "sortBy": "PRICE_ASC",
    "checkInDaysFromNow": 30,
    "nights": 3,
    "adults": 2,
    "currency": "USD",
    "maxResults": 60,
    "monitorPrices": True,
})
rows = list(client.dataset(run.default_dataset_id).iterate_items())

drops = [item for item in rows if item["changeType"] == "price_changed" and item["priceDelta"] < 0]
for item in sorted(drops, key=lambda item: item["priceDelta"]):
    print(f'{item["title"]}: {item["previousPrice"]} -> {item["priceTotal"]} {item["currency"]} ({item["priceDeltaPercent"]} %)')

pd.DataFrame(rows).to_csv(HISTORY, mode="a", header=not os.path.exists(HISTORY), index=False)
Enter fullscreen mode Exit fullscreen mode

The first run is the reference: every property comes back as new. From the second run on, each row is unchanged or price_changed with previousPrice, priceDelta and priceDeltaPercent; the script prints the drops, biggest first, and appends the day to a CSV history. With "onlyChanges": True, only the new properties and the changes are saved and charged: a price drop alert that costs nothing on a quiet day.

Run it every morning with cron (with APIFY_TOKEN set in the cron environment):

0 7 * * * cd /path/to/tracker && python3 track_agoda.py >> tracker.log 2>&1
Enter fullscreen mode Exit fullscreen mode

Without code: open the public task in Apify Console, save it as your own task and add a daily schedule; the dataset of each run downloads as CSV or Excel.

With relative dates, the tracker follows the price of "a 3-night stay in 30 days": each run searches new dates, so a change mixes the hotel's pricing with the calendar (weekday, season). That is the right view of a market. To follow one stay until it happens, give fixed checkIn and checkOut dates.

Good to know

  • Cheapest first is stable. PRICE_ASC and REVIEW_SCORE_DESC are exact across pages; Agoda's own RANKING order changes between requests, so the compared set drifts.
  • A property that drops out and comes back is not reported as new: its last price is kept for 90 days.
  • Same currency only. A price is compared only with a previous price in the same currency.
  • Search results show a subset. Amenities, room details and the full photo gallery are not in Agoda's search results; the rows hold up to ten photos and the "easy cancellation" flag.
  • No personal data: property facts, the average score and the number of reviews, nothing about guests or reviewers.

Pricing is pay-per-event: $2.00 per 1,000 properties on Apify's Free plan, down to $1.29 on the higher plans (this run of 60 properties costs about $0.12). Use the data for analysis, respect the platform's terms and local law, and do not republish listings.

Disclosure: I build these Actors and may earn a commission if you sign up to Apify through these links.

Top comments (0)