DEV Community

Cover image for How to Scrape Hotels.com Reviews in 2026 (Python + a No-Code Shortcut)
Factden
Factden

Posted on

How to Scrape Hotels.com Reviews in 2026 (Python + a No-Code Shortcut)

Hotels.com is part of Expedia Group, which is good news and a small headache. Good news: its reviews share the same backend as Expedia, Travelocity, Orbitz, and the rest, so one approach covers all of them. The headache: Hotels.com still uses its old /ho<id>/ URL format, which has to be resolved to the global property before you can pull anything.

This guide covers what you can pull from a Hotels.com review, why the DIY route is fiddly, working Python, a no-code shortcut, and a plain comparison. If you want the deeper reference, there is a full guide to scraping Hotels.com reviews too.

What you can pull from a review

Per review you can get:

  • Overall rating on the /10 scale plus a label, and category sub-ratings: Cleanliness, Service, Room comfort, Hotel condition, Amenities, Eco-friendliness
  • The full review text, detected language, and a translation flag
  • Stay context: check-in and check-out dates, travel companions, traveler category
  • Reviewer detail, verified flag, helpful votes, review photos
  • Owner responses, and a brandType field
  • An LLM-ready markdown block per review

Same clean, structured shape whether the review came from Hotels.com or any sibling brand.

Why scraping Hotels.com is hard

  1. No public reviews API and no API key on offer. Scraping the public pages is the route.
  2. The /ho<id>/ legacy id. A Hotels.com URL like hotels.com/ho119566/ does not carry the global property id directly; it has to be resolved first, or your request goes nowhere.
  3. Reviews load dynamically, so a plain request returns a JavaScript shell, not the data.
  4. Bot protection and rate limits on top.

So the work is resolving the legacy id, hitting the review endpoints, and staying unblocked, on repeat.

Three ways to get the data

DIY Python Hotels.com Reviews Scraper (actor) Official API
Setup time Hours to days ~30 seconds Not available (no API key)
/ho<id>/ resolution You build it Automatic n/a
/10 ratings + 6 sub-ratings Parse nested markup Yes n/a
Owner responses Extra parsing Yes n/a
Cost Proxies + eng time Pay-per-result n/a
Best for One-off Scheduled, at scale Not an option

Option A: DIY in Python

A plain request to a Hotels.com page returns a shell, and the /ho<id>/ id is not the one the review endpoint expects:

import httpx

url = "https://www.hotels.com/ho119566/"
r = httpx.get(url, headers={"User-Agent": "Mozilla/5.0"})
print("reviews" in r.text)   # usually False; ho-id must be resolved, reviews load separately
Enter fullscreen mode Exit fullscreen mode

To get the data you resolve the /ho<id>/ to the global property id, call the review endpoints, and normalize the response. Fine for one hotel, several moving parts for a portfolio.

Option B: the no-code / API shortcut

When you want clean rows, the Hotels.com Reviews Scraper on Apify resolves the /ho<id>/ id for you and returns structured JSON. No login, no API key, no proxy setup.

from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("factden/hotels-com-reviews-scraper").call(run_input={
    "hotelUrls": [
        "https://www.hotels.com/ho119566/",
        "242128"
    ],
    "maxReviewsPerHotel": 200,
    "sortBy": "newest",
})
for review in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(review["overallRating"], review["reviewText"][:60])
Enter fullscreen mode Exit fullscreen mode

Pass Hotels.com /ho<id>/ URLs or bare ids, and any other Expedia Group brand URL works too. sortBy takes newest, oldest, highestRating, or lowestRating; bound results with fromDate / toDate and minRating / maxRating.

What comes back: 27 fields per review

Group Fields
Core reviewId, hotelId, hotelName, hotelUrl, source, brandType, submittedAt
Rating overallRating (/10), ratingLabel, subRatings (cleanliness, service, room comfort, condition, amenities, eco)
Text reviewText, language, isMachineTranslated
Stay checkInDate, checkOutDate, travelCompanions, travelerCategories, roomTypeId
Reviewer reviewerName, reviewerLocation, verified, isAnonymous
Signals helpfulVotes, imagesCount, reviewPhotos, ownerResponse
AI-ready markdownContent

A trimmed sample row:

{
  "brandType": "Hotels.com",
  "hotelName": "The Venetian Resort",
  "overallRating": 9,
  "ratingLabel": "Wonderful",
  "subRatings": ["Cleanliness: 10", "Service: 9", "Hotel condition: 9", "Amenities: 10"],
  "reviewText": "Great suite, easy check-in, would stay again.",
  "travelerCategories": ["Couple"],
  "checkInDate": "2026-04-18",
  "checkOutDate": "2026-04-21",
  "language": "en",
  "helpfulVotes": 2
}
Enter fullscreen mode Exit fullscreen mode

Full field list and snippets are in the GitHub repo.

Grab a free sample dataset

Want to see the data first? There is a free Hotels.com sample (CSV/JSON) here: factden.com/sample-hotels. Load it into pandas and the /10 ratings and sub-ratings are ready to plot.

FAQ

Is scraping Hotels.com legal? The reviews are publicly visible. As with any scraping, check Hotels.com's Terms of Service and your local rules, and use the data responsibly.

Is there a Hotels.com API? No public reviews API and no key on offer. Scraping the public pages is the route.

What is the /ho<id>/ thing? Hotels.com uses a legacy property id in its URLs that does not match the global Expedia Group id. It has to be resolved first, which the actor does automatically.

Can I scrape Expedia and the other brands too? Yes. Hotels.com shares the Expedia Group backend, so the same actor handles Expedia, Travelocity, Orbitz, Wotif, CheapTickets, and ebookers, same schema, tagged by brandType.

How do I stop getting blocked? Residential proxies and slow pacing, or a tool that resolves the id and reads the review endpoints directly. Plain requests get a JavaScript shell.

Related

Questions, or a field you wish it extracted? Drop a comment.

Top comments (0)