Monitor Agoda Hotel Reputation for Multi-Property Groups
Track ratings, review volume and guest sentiment across every hotel you operate with scheduled Agoda review pulls, per-property scorecards and drift alerts.

Agoda publishes a public score for every property you operate, and it moves weeks before an internal guest-satisfaction survey catches up. Thirdwatch's Agoda Reviews Scraper pulls every public review for a list of Agoda hotel URLs — rating, rating band, title, comments, stay dates, plus the Agoda, Booking.com and combined aggregate scores — so a hotel group can run one scheduled job across the whole portfolio and see exactly which properties are drifting.
Skip the setup: Run this as a ready-to-go task on Apify — pre-loaded with the configuration from this guide.
Why scrape Agoda reviews for portfolio reputation monitoring
A hotel group's reputation is not one number. It is a distribution across properties, and the operator's job is to find the two hotels in twenty that are sliding before the aggregate score moves enough for a traveller to notice. That is hard to do in a browser: Agoda shows one property at a time, paginates its review feed, and gives you no way to diff this week against last week.
The job-to-be-done is the same across most groups. A regional operations director wants a weekly scorecard of every managed property with its current rating, its review volume and the share of stays scored below six. A guest-experience lead wants the actual review text behind any property whose low-rating share doubled. A brand-standards team wants to know whether a refurbishment moved the score at all, and by how much, over the two quarters after reopening.
All three reduce to the same pipeline: a watchlist of Agoda hotel URLs, a scheduled incremental pull, a per-property aggregate, and a diff against the previous snapshot. Once the review rows are structured, the analysis is ordinary dataframe work. The hard part is getting clean, deduplicated, fully paginated review rows for thirty properties on a repeatable schedule — which is exactly what the Actor does.
How does this compare to the alternatives?
Three ways to get portfolio-wide Agoda review data:
| Approach | Cost model | Reliability | Setup time | Maintenance |
|---|---|---|---|---|
| DIY Python script | Your engineering time | Breaks on pagination and feed changes | Days to weeks | You own every upstream change |
| Generic scraping API | Subscription, per-request | Returns raw HTML you still have to parse | Hours | You own the parser |
| Thirdwatch Agoda Reviews Scraper | Pay per review row | Structured rows, deduplicated, paginated | Minutes | Thirdwatch tracks Agoda changes |
A DIY script is cheap on day one and expensive on day ninety, because the failure mode is silent: pagination stalls, you get the first page for every hotel, and the weekly scorecard quietly reports stale numbers. A generic scraping API solves fetching but not shaping — you still write and maintain the code that turns a property page into review rows. The Agoda Reviews Scraper actor page gives you the finished rows, and you only pay for reviews that actually land in the dataset.
How to monitor portfolio reputation in 5 steps
Step 1: How do I point the Actor at every hotel in my portfolio?
Pass your properties as Agoda hotel URLs in hotelUrls. The Actor resolves each property's internal ID for you, so you never have to look one up.
{
"hotelUrls": [
"https://www.agoda.com/marina-bay-sands/hotel/singapore-sg.html",
"https://www.agoda.com/the-savoy/hotel/london-gb.html",
"https://www.agoda.com/burj-al-arab-jumeirah/hotel/dubai-ae.html",
"https://www.agoda.com/the-taj-mahal-palace-mumbai/hotel/mumbai-in.html"
],
"maxReviewsPerHotel": 250,
"enabledProviders": ["Agoda", "Booking.com"],
"sortBy": "recent",
"cutoffDate": "2026-08-01",
"language": "all",
"aggregateRatings": true
}cutoffDate is what keeps a scheduled run incremental: set it to the date of your last successful run and older reviews are skipped while pagination continues past them. hotelIds accepts numeric Agoda property IDs if you already store them, and can be combined with hotelUrls in the same run.
Step 2: How do I run the job and get the rows back?
Use the Apify client and read the default dataset. One call covers the whole watchlist.
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run_input = {
"hotelUrls": [
"https://www.agoda.com/marina-bay-sands/hotel/singapore-sg.html",
"https://www.agoda.com/the-savoy/hotel/london-gb.html",
"https://www.agoda.com/burj-al-arab-jumeirah/hotel/dubai-ae.html",
"https://www.agoda.com/the-taj-mahal-palace-mumbai/hotel/mumbai-in.html",
],
"maxReviewsPerHotel": 250,
"sortBy": "recent",
"cutoffDate": "2026-08-01",
"aggregateRatings": True,
}
run = client.actor("thirdwatch/agoda-reviews-scraper").call(run_input=run_input)
reviews = list(client.dataset(run["defaultDatasetId"]).iterate_items())
print(f"{len(reviews)} reviews across {len({r['hotelId'] for r in reviews})} properties")The per-hotel aggregate payload is also written to the run's key-value store under aggregate_review_data, which is useful when you want the headline scores without loading every review row.
Step 3: How do I turn raw reviews into a per-property scorecard?
Group by hotelId and summarise. The field that matters most for operations is ratingCategory, Agoda's own rating band, because it gives you a share-of-bad metric that is stable even when review volume swings.
import pandas as pd
df = pd.DataFrame(reviews)
df["reviewDate"] = pd.to_datetime(df["reviewDate"], utc=True, errors="coerce")
scorecard = (
df.groupby(["hotelId", "hotelName"])
.agg(
reviews=("hotelReviewId", "nunique"),
mean_rating=("rating", "mean"),
headline_score=("combinedAggregateReviewScore", "first"),
agoda_score=("agodaAggregateReviewScore", "first"),
booking_score=("bookingAggregateReviewScore", "first"),
low_share=("ratingCategory", lambda s: s.isin(["below_expectation", "good"]).mean()),
)
.reset_index()
.sort_values("low_share", ascending=False)
)
print(scorecard.head(20))low_share is the operational number. A property whose recent low-rating share is well above its own trailing average has a live problem, even when the headline combinedAggregateReviewScore has barely moved — the aggregate is a lagging average over the property's entire review history.
Step 4: How do I detect drift between weekly snapshots?
Persist each run's scorecard, then diff. Deduplicate on hotelReviewId so re-pulled reviews never double-count.
import json, pathlib, datetime
stamp = datetime.date.today().isoformat()
out = pathlib.Path(f"snapshots/agoda-portfolio-{stamp}.json")
out.parent.mkdir(exist_ok=True)
out.write_text(scorecard.to_json(orient="records"))
snaps = sorted(pathlib.Path("snapshots").glob("agoda-portfolio-*.json"))
if len(snaps) >= 2:
prev = pd.read_json(snaps[-2])
merged = scorecard.merge(prev, on="hotelId", suffixes=("", "_prev"))
merged["score_delta"] = merged.headline_score - merged.headline_score_prev
merged["low_share_delta"] = merged.low_share - merged.low_share_prev
drifting = merged[(merged.score_delta <= -0.2) | (merged.low_share_delta >= 0.10)]
print(drifting[["hotelName", "score_delta", "low_share_delta"]])Two thresholds cover most portfolios: a drop of 0.2 or more on the combined aggregate score, or a ten-percentage-point rise in low-rating share. The second fires earlier, which is the point.
Step 5: How do I schedule it and route alerts to the right GM?
Create a schedule on the platform and attach a webhook to the run so the scorecard job fires the moment the data lands. Both are covered in the Apify schedules documentation and the webhooks documentation.
import os
import requests
SLACK_WEBHOOK = os.environ["SLACK_WEBHOOK"]
for _, row in drifting.iterrows():
requests.post(
SLACK_WEBHOOK,
json={
"text": (
f"*{row.hotelName}* score {row.score_delta:+.2f} week over week, "
f"low-rating share {row.low_share_delta:+.0%}"
)
},
)Route by property rather than broadcasting to one channel. A general manager who gets an alert only about their own hotel reads it; a group-wide firehose gets muted within a month.
Sample output
Each dataset row is one review, already flattened, with the property's aggregate scores attached when aggregateRatings is enabled:
{
"hotelId": 185945,
"hotelName": "Marina Bay Sands",
"hotelUrl": "https://www.agoda.com/marina-bay-sands/hotel/singapore-sg.html",
"hotelReviewId": 1056793755,
"reviewProviderText": "Agoda",
"rating": 5.6,
"ratingText": "Below expectation",
"ratingCategory": "below_expectation",
"reviewTitle": "Long wait at check-in",
"reviewComments": "Room was excellent but we queued for over an hour to check in.",
"reviewPositives": "Room and view",
"reviewNegatives": "Check-in queue, slow luggage delivery",
"reviewDate": "2026-08-24T09:12:00+08:00",
"checkInDate": "2026-08-21",
"checkOutDate": "2026-08-24",
"reviewerCountryName": "Australia",
"reviewerGroupName": "Couple",
"reviewerRoomTypeName": "Deluxe King",
"reviewerLengthOfStay": 3,
"agodaAggregateReviewScore": 8.5,
"bookingAggregateReviewScore": 8.7,
"combinedAggregateReviewScore": 8.6,
"combinedReviewsCount": 41230
}rating is the numeric score for this stay, ratingCategory is the band the score falls into, and the three aggregate fields are the property-level scores as displayed. reviewPositives and reviewNegatives arrive as separate strings, which is why complaint mining on this data is far cleaner than on a single blended comment field. hotelReviewId is your deduplication key across runs.
Common pitfalls
Four things go wrong in portfolio review monitoring. Comparing aggregate scores across properties of different ages — a hotel with forty thousand lifetime reviews cannot move its aggregate the way a two-year-old property can, so drift thresholds must be relative to each property's own history, not to a group benchmark. Reading a single week for a low-volume property — under roughly twenty reviews a month, week-over-week deltas are noise; batch those properties monthly. Ignoring the provider mix — Agoda and Booking.com review populations differ in traveller origin and scoring behaviour, so a shift in provider mix can move a blended average with no change in guest experience; segment on reviewProviderText before you conclude anything. Re-pulling full history every run — without cutoffDate you pay for and re-process reviews you already have.
Thirdwatch's Actor handles pagination, deduplication, retries and the filtering so you can focus on the scorecard rather than the fetch loop.
Related use cases
- Track how Agoda management responses affect hotel ratings
- Analyze Agoda guest sentiment by room and traveller type
- Benchmark Booking.com guest sentiment against rival hotels
- Mine Booking.com reviews for recurring service complaints
- Track Booking.com pricing for hotel revenue for the rate side of the same properties
- The complete guide to scraping reviews
- All Thirdwatch use-case guides
Frequently asked questions
What does the Agoda Reviews Scraper return per review?
Each row carries hotelId, hotelName, hotelReviewId, rating, ratingText, ratingCategory, reviewTitle, reviewComments, separate reviewPositives and reviewNegatives, reviewDate, check-in and check-out dates, reviewer country, traveller group, room type and length of stay.
Does it also cover Booking.com reviews?
Yes. Agoda property pages surface reviews from both Agoda and Booking.com, and the enabledProviders input controls which of the two you keep. Every row carries reviewProviderText so you can segment or compare the two review populations after the run.
How often should a hotel group refresh its review data?
Weekly is the right default for most portfolios. Properties taking fewer than roughly twenty reviews a month produce noisy week-over-week deltas, so run those monthly. Move to daily only during a live incident, a renovation or a post-rebrand watch window.
Can I limit a run to recent reviews only?
Yes. Set cutoffDate to an ISO date such as 2026-08-01 and older reviews are skipped while pagination continues. Combined with sortBy set to recent, that keeps scheduled incremental runs small instead of re-pulling a property's full review history.
How do I compare a property against its own aggregate score?
Leave aggregateRatings enabled. Every row then carries agodaAggregateReviewScore, bookingAggregateReviewScore and combinedAggregateReviewScore, plus the per-category detail arrays, so recent review sentiment can be read directly against the headline number a traveller sees.
Is any of this behind a login?
No. The Actor reads the same public review feed a traveller sees on an Agoda property page. It does not authenticate, access bookings or collect contact details. Reviewer display names and countries are still personal data, so handle them accordingly.
Related
100 free credits, no credit card.
About 30 real searches. Add the MCP to Claude or Cursor in two minutes.