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
brandTypetelling 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
- There is no public reviews API. Expedia Group does not offer one, so scraping the public pages is the route.
-
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. - Reviews are loaded dynamically, not sitting in the initial HTML, so a plain request gets you a shell.
- 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
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])
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
}
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
- Doing B2B software instead of hotels? See how to scrape G2 reviews.
- Other FactDen scrapers: Hotels.com reviews and Trip.com and Ctrip hotel reviews.
Questions, or a field you wish it extracted? Drop a comment.
Top comments (0)