kestrel actors docs

kestrel actorsGuides

How to monitor hotel reputation across OTAs

A hotel's reputation is spread across six or eight sites that rate on four different scales, in a dozen languages, and none of them will send you the other sites' reviews. Here is the shape of a daily cross-OTA feed that costs cents per property per month, and the three things that make it survive contact with the data.

The short version

  1. Pick the OTAs the property actually sells on. Booking and TripAdvisor almost always; Agoda for Asia-Pacific, Trip.com for Chinese-speaking guests, HRS and Kurzurlaub for DACH, Zoover for the Dutch and Flemish market, Despegar for Latin America, Hostelworld for hostels.
  2. Resolve each property once, then run on ids. Names are resolved through each site's own lookup. Do it once, store the ids, and every later run is faster, cheaper and unambiguous.
  3. Run daily with a date cut and a rating ceiling. A relative date cut keeps the schedule honest; a rating ceiling keeps the bill to the reviews that need an answer.
  4. Normalise to one scale before you aggregate. Booking, Agoda, HRS and Trip.com rate 0-10, TripAdvisor and Airbnb 1-5, Hostelworld 0-100, Kurzurlaub 1-6 where 6 is best. Every row also carries the score out of five.

The daily feed, in one shape

The pattern is the same for every source: yesterday's reviews, only the ones that need attention, deduplicated on the review id.

from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
jobs = [
    ("kestrel/booking-reviews-scraper",
     {"hotelIds": ["536251"], "maxRating": 6, "requireText": True, "maxReviewsPerHotel": 50}),
    ("kestrel/tripadvisor-reviews-scraper",
     {"locationIds": ["4509998"], "maxRating": 3, "sinceDate": "2 days", "requireText": True}),
    ("kestrel/agoda-reviews-scraper",
     {"hotelNames": ["Memmo Alfama Lisbon"], "maxRating": 5, "requireText": True,
      "reviewsSort": "most_recent", "maxReviewsPerHotel": 50}),
]
inbox = []
for actor, run_input in jobs:
    run = client.actor(actor).call(run_input=run_input)
    for r in client.dataset(run["defaultDatasetId"]).iterate_items():
        if r["type"] == "review":
            inbox.append({
                "source": actor.split("/")[1].split("-")[0],
                "id": r["review_id"],
                "score_5": r.get("rating_5") or r["rating"],
                "date": r.get("review_date"),
                "text": r.get("text") or r.get("negatives"),
                "answered": bool(r.get("response") or r.get("host_reply")),
            })
inbox.sort(key=lambda x: x["score_5"])
for row in inbox[:20]:
    print(row["source"], row["score_5"], row["date"], "ANSWERED" if row["answered"] else "OPEN",
          (row["text"] or "")[:70])

That is the whole product: one sorted list of what went wrong yesterday, across every channel, with the ones nobody has answered marked.

Three things that make it work

1. Normalise the scale first. Booking, Agoda, HRS and Trip.com rate 0-10. TripAdvisor, Airbnb and Trustpilot rate 1-5. Hostelworld rates 0-100. Kurzurlaub rates 1-6 where 6 is the best — a scale that will silently invert your dashboard if you assume higher numbers are better everywhere and forget the maximum differs. Every non-five-point source here also emits the score out of five, so use that column and keep the native score for auditing.

2. Push the filter into the source where you can. Booking's endpoint sorts by score, so asking for reviews at or below a threshold, lowest first, stops at the first review above it — one page instead of thirty-six. TripAdvisor cannot: its pages are newest-first with no rating sort, so a rating filter there reads pages and drops rows. Pair it with a date cut so the pages read are only yesterday's.

3. Let empty runs be empty. Most properties, on most days, have no new complaints. A run that delivers nothing should cost nothing — that is what makes a daily cadence cheaper than a weekly one on a per-signal basis, because you are paying for signals rather than for polls.

What each source adds that the others do not

The review scraper comparison has all of them side by side with the ceilings.

Cost, honestly

The arithmetic is simple because billing is per delivered row. A property with three new reviews a day on two OTAs is six rows, about three cents. Fifty properties on two OTAs each, filtered to complaints, is usually $10-30 a month — the number moves with how many complaints you get, which is a pleasantly aligned incentive.

The one thing that blows a budget is asking for a full corpus daily instead of what changed. Use a date cut on the sources that support one and a rating ceiling on the ones that do not, and check the status row's filtered count to see what you did not pay for.

Wiring it to Slack or a ticket queue

Every actor writes an Apify dataset, so the delivery layer is whatever you already use: an n8n or Make schedule calling the run-sync endpoint and posting rows below a score threshold, a Zapier hook, or a cron job that reads the dataset and opens a ticket per unanswered complaint. The n8n price-tracking guide has a complete workflow shape — schedule, HTTP call, compare-with-last-run code node, alert — that transfers directly to reviews.

Run it

Start with the two that cover most properties: Booking.com Reviews and TripAdvisor Reviews, both $0.005 per delivered review. Add Agoda, Trip.com, HRS, Hostelworld, Zoover or Despegar as your guest mix requires. Related: building a voice-of-customer dataset when the goal is analysis rather than alerting.

FAQ

What does daily monitoring actually cost?

A property that gets three new reviews a day on two OTAs is six rows a day, about $0.03 — roughly ninety cents a month. A fifty-property portfolio watching two OTAs each, with a rating ceiling so only the complaints are delivered, typically lands between $10 and $30 a month. Empty runs cost nothing at all, which is what makes a daily cadence affordable rather than a weekly one.

How do I compare a 8.6 on Booking with a 4.3 on TripAdvisor?

Every actor that uses a non-five-point scale also emits the same score expressed out of five, so a cross-source average is a column read rather than a conversion you have to remember. Do not average the raw scores — a 0-10 and a 1-5 scale do not have the same midpoint or the same distribution.

Which OTA is the best single source?

Booking for volume and traveller-type detail, TripAdvisor for the six sub-ratings and trip type, Agoda for Asia-Pacific depth, Trip.com when a quarter of your guests write in Chinese. If you can only run one, run the one that sells the most room nights — but the point of a cross-OTA feed is that complaints do not distribute evenly across channels.

Do I get the property's replies?

From most sources, yes: Booking, TripAdvisor, Agoda, Airbnb, HRS, Trip.com, Hostelworld and Zoover all carry the management response on the row. That makes "which complaints have we answered" a filter rather than a project. Kurzurlaub, Despegar and Amazon do not publish replies on the pages these read.

Run Booking.com Reviews Scraper on Apify →

Other actors used on this page: apify.com/kestrel/tripadvisor-reviews-scraper, apify.com/kestrel/agoda-reviews-scraper, apify.com/kestrel/airbnb-reviews-scraper, apify.com/kestrel/trip-com-reviews-scraper, apify.com/kestrel/hrs-reviews-scraper, apify.com/kestrel/hostelworld-reviews-scraper, apify.com/kestrel/despegar-hotel-ratings.

More guides