DEV Community

Cover image for How to Scrape Expedia Hotel Reviews in 2026 (Python + a No-Code Shortcut)
Factden
Factden

Posted on

How to Scrape Expedia Hotel Reviews in 2026 (Python + a No-Code Shortcut)

Here is the thing most people miss about scraping Expedia: Expedia, Hotels.com, Travelocity, Orbitz, Wotif, CheapTickets, and ebookers all run on the same backend. The same hotel shows up across all seven brands, and their reviews pool together. If you scrape only expedia.com, you leave most of the data on the table.

This guide covers what you can pull from an Expedia Group 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 Expedia 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 telling you which of the seven brands the review came from
  • An LLM-ready markdown block per review

The brandType field plus the shared backend are what let you pull every brand's reviews for a property in one pass.

Why scraping Expedia is hard

  1. There is no public reviews API. Expedia Group does not offer one, so scraping the public pages is the route.
  2. Seven brands, different URL formats. Six brands embed the global property id in a .h<id>. path; Hotels.com uses a legacy /ho<id>/ id that has to be resolved. A scraper built for one brand misses the rest.
  3. Reviews are loaded dynamically, not sitting in the initial HTML, so a plain request gets you a shell.
  4. Bot protection and rate limits on top.

So the work is resolving ids across brands, hitting the review endpoints, and staying unblocked, on repeat.

Three ways to get the data

DIY Python Expedia Reviews Scraper (actor) Official API
Setup time Hours to days ~30 seconds Not available (no public reviews API)
All 7 Expedia Group brands Build a scraper per brand One run 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 hotel page returns a JavaScript shell, not the reviews:

import httpx

url = "https://www.expedia.com/Las-Vegas-Hotels-Bellagio.h140596.Hotel-Information"
r = httpx.get(url, headers={"User-Agent": "Mozilla/5.0"})
print("reviews" in r.text)   # usually False; reviews load via internal endpoints
Enter fullscreen mode Exit fullscreen mode

To get the data you reverse-engineer the review endpoints, map each brand's URL format to the global property id (including the Hotels.com /ho<id>/ resolution), and normalize the responses. Fine for one hotel, a lot of moving parts for a portfolio.

Option B: the no-code / API shortcut

When you want clean rows, the Expedia Reviews Scraper on Apify takes any Expedia Group hotel URL (or a bare property id), resolves it, and returns structured JSON. No login, no proxy setup.

from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("factden/expedia-hotel-reviews-scraper").call(run_input={
    "hotelUrls": [
        "https://www.expedia.com/Las-Vegas-Hotels-Bellagio.h140596.Hotel-Information",
        "140596",
        "https://www.hotels.com/ho115902/"
    ],
    "maxReviewsPerHotel": 200,
    "sortBy": "newest",
})
for review in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(review["brandType"], review["overallRating"], review["reviewText"][:60])
Enter fullscreen mode Exit fullscreen mode

Mix brands and bare ids freely. sortBy takes newest, oldest, highestRating, or lowestRating; bound results with fromDate / toDate and minRating / maxRating; and use reviewSources to keep only certain brands' reviews.

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": "Expedia",
  "hotelName": "Bellagio",
  "overallRating": 10,
  "ratingLabel": "Exceptional",
  "subRatings": ["Cleanliness: 10", "Service: 10", "Hotel condition: 10", "Amenities: 10"],
  "reviewText": "Rooms were great. Buffet was awesome. Nice casino.",
  "travelerCategories": ["Friends"],
  "checkInDate": "2026-05-01",
  "checkOutDate": "2026-05-04",
  "language": "en",
  "helpfulVotes": 3
}
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 Expedia sample (CSV/JSON) here: factden.com/sample-expedia. Load it into pandas and the /10 ratings and sub-ratings are ready to plot.

FAQ

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

Is there an official Expedia reviews API? No public one. Scraping the public pages is the route for most teams.

Can I scrape Hotels.com and the other brands too? Yes. All seven Expedia Group brands share the backend, so one actor covers Expedia, Hotels.com, Travelocity, Orbitz, Wotif, CheapTickets, and ebookers, with the same schema tagged by brandType.

Are ratings on the 5-star or 10-point scale? Expedia Group uses /10, and that is what the actor returns for overallRating and the sub-ratings.

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

Related

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

Top comments (0)