# Expedia & Hotels.com Scraper — Hotel Reviews & Prices (`tortuga/expedia-scraper`) Actor

Scrape Expedia and Hotels.com hotel reviews, hotel details (stars, address, scores, amenities, policies) and search results with nightly and total prices. Monitor new reviews.

- **URL**: https://apify.com/tortuga/expedia-scraper.md
- **Developed by:** [Trevor Ortega](https://apify.com/tortuga) (community)
- **Categories:** Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 reviews

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Expedia & Hotels.com Scraper — Hotel Reviews & Prices

Scrape **Expedia and Hotels.com hotel reviews**, **hotel details** (stars, address, coordinates, guest score and sub-scores, amenities, policies, check-in/out) and **search results with prices** for your dates (price per night, total with taxes and fees, struck-through price). One actor for both Expedia Group sites, with a monitoring mode that saves only new reviews or changed prices on a schedule.

- Works with hotel URLs of either site, Expedia property ids, search URLs or plain destination names ("Las Vegas", "Lisbon, Portugal").
- Expedia and Hotels.com keep **separate review sets** for the same hotel: pick one site or `both`.
- Newest reviews first, up to about 10,000 reviews per hotel and site; or sort by relevance or rating.
- No login, no browser: the actor reads the same data the sites' own pages load.

### What data does it extract?

#### Reviews (`mode: reviews`, default)

| Field | Example | Notes |
|---|---|---|
| `site` | `Expedia` | `Expedia` or `Hotels.com` |
| `reviewId` | `6aaf4d35ef70b65772062f33` | stable id, used by monitoring |
| `hotelId`, `hotelName`, `hotelUrl` | `140596`, `Bellagio` | Expedia property id |
| `rating`, `ratingMax`, `ratingText` | `8`, `10`, `Good` | |
| `title`, `text` | | text is empty for rating-only reviews (about 45 %); Expedia no longer shows titles on new reviews |
| `likedTopics`, `dislikedTopics` | `Cleanliness, amenities` | the topics the guest ticked |
| `reviewDate` | `2026-10-10` | |
| `stayMonth`, `lengthOfStay` | `2026-09`, `6` | Expedia shows month and nights; Hotels.com nights only |
| `reviewerName` | `Deslin` | first name as shown |
| `reviewRegion` | `US` | Hotels.com only: the region shown with the review |
| `language`, `locale` | `en`, `en_US` | |
| `isVerified` | `true` | "Verified review" label |
| `helpfulVotes` | `0` | Expedia only |
| `photos` | `["https://a.travel-assets.com/ugc/..."]` | guest photos |
| `hotelResponse`, `hotelResponseDate`, `hotelResponderName` | `Thank you for...`, `2025-10-02`, `Guest Experience - Bellagio` | management response |
| `travellerType` | | filled only if the site returns it (currently it does not) |

#### Hotel details (`mode: hotels`)

`name`, `starRating`, `propertyType`, `address` (`full`, `street`, `city`, `region`, `postalCode`, `countryCode`), `latitude`, `longitude`, `reviewScore`, `reviewScoreText`, `reviewCount`, `scoreBreakdown` (cleanliness, staff & service, amenities, property conditions), `checkInFrom`, `checkInUntil`, `checkOutUntil`, `checkInPolicy`, `checkOutPolicy`, `policies` (fees, deposits, pets, parking, children ...), `amenities` and `amenityGroups`.

#### Search results (`mode: search`)

`position`, `hotelId`, `name`, `url`, `area`, `reviewScore`, `reviewScoreText`, `reviewCount`, `checkIn`, `checkOut`, `nights`, `currency`, `pricePerNight` (nightly rate before taxes and fees, as the site shows it), `totalPrice` (whole stay), `taxesAndFeesIncluded`, `pricePerNightWithTaxes` (total / nights), `originalTotalPrice` (struck-through), `isRefundable` and `starRating` (only for the cards the site flags; empty otherwise), `available`, `image`.

### How to scrape Expedia reviews

1. Paste one or more hotel URLs into **Expedia / Hotels.com URLs**, e.g. `https://www.expedia.com/Las-Vegas-Hotels-Bellagio.h140596.Hotel-Information`.
2. Set **Max reviews per hotel** (default 100) and **Max items**.
3. Run. Example output:

```json
{
  "type": "review",
  "site": "Expedia",
  "reviewId": "6ac9b4308312a1522073a7a3",
  "hotelId": "41308",
  "hotelName": "Excalibur Hotel & Casino",
  "rating": 6,
  "ratingMax": 10,
  "ratingText": "Okay",
  "text": "We loved the Mac King comedy show. It would be nice if self checkout wasn't expected...",
  "likedTopics": "Amenities, property conditions & facilities",
  "dislikedTopics": "Staff & service",
  "reviewDate": "2026-10-10",
  "stayMonth": "2026-10",
  "lengthOfStay": 1,
  "reviewerName": "Crissy",
  "language": "en",
  "isVerified": true,
  "hotelResponse": null
}
```

Input for reviews of the top 5 hotels of a destination on both sites:

```json
{
  "mode": "reviews",
  "destinations": ["Lisbon"],
  "site": "both",
  "maxHotelsPerSearch": 5,
  "maxReviewsPerHotel": 200,
  "maxItems": 2000
}
```

### How to scrape Hotels.com reviews

Use Hotels.com hotel URLs (`https://www.hotels.com/ho118295/bellagio-las-vegas-united-states-of-america/`; links from Hotels.com search results also carry `expediaPropertyId=`), or property ids with **Site** = `Hotels.com`. Hotels.com URLs with only the `ho` number are matched to the property by name. Hotels.com shows different reviews than Expedia for the same hotel, so `both` gives you two review sets.

### How to get Expedia hotel prices for my dates

Set **What to scrape** = `Search results with prices`, add a destination (or an Expedia / Hotels.com search URL with `startDate` / `endDate`) and set **Check-in date**, **Nights**, **Adults** and **Rooms**:

```json
{
  "mode": "search",
  "destinations": ["Paris"],
  "checkIn": "2026-12-04",
  "nights": 2,
  "maxItems": 100
}
```

```json
{
  "type": "search-result",
  "site": "Expedia",
  "position": 2,
  "hotelId": "105969684",
  "name": "Ginette à la Folie",
  "reviewScore": 9.6,
  "reviewScoreText": "Exceptional",
  "reviewCount": 500,
  "checkIn": "2026-12-04",
  "checkOut": "2026-12-06",
  "nights": 2,
  "currency": "USD",
  "pricePerNight": 205,
  "totalPrice": 489,
  "taxesAndFeesIncluded": true,
  "pricePerNightWithTaxes": 244.5,
  "originalTotalPrice": 562,
  "isRefundable": true
}
```

Prices come from expedia.com / hotels.com (US sites) and are in US dollars.

### Monitor changes on a schedule

Turn on **Only new or changed items**, save the input as a task and add a Schedule in Apify (for example daily). In reviews mode each run reads the newest **Max reviews per hotel** reviews of every hotel and saves only reviews it has not saved before; **Only reviews newer than** (`1 day`, `7 days` or a date) narrows the window and stops paging early. In hotels mode a hotel is saved again only when its guest score or stars change; in search mode when the price per night, total price, availability or refundability changes (small rounding jitter is ignored; use a fixed **Check-in date** so runs compare the same stay). Unchanged items are not output and not charged. New items carry `changeType: "new"`, changed ones `changeType: "changed"` plus `previousValues` with the old values. Use **Monitoring key** to keep several schedules on the same input apart.

### Does it work without login?

Yes. Everything comes from public pages of expedia.com and hotels.com; no account or cookies are needed.

### How many reviews can I get per hotel?

Up to about 10,000 per hotel and site in one sort order (the sites stop paging there). Most hotels have fewer. Choose **Sort reviews by** = rating to reach other reviews of very large hotels.

### How much does it cost?

You pay per item saved (see the Pricing tab) plus platform usage. Usage measured on the platform: about $0.01 per 1,000 reviews (compute, dataset writes and residential proxy traffic together); a search page holds 50 hotels.

### Proxy note

Expedia's bot protection rejects nearly all datacenter traffic, so the actor uses **Apify residential proxy** by default. Each request goes out from a new residential IP; about half of the attempts are turned away with a short 429 answer and retried automatically. Residential traffic measured: about 0.6 MB per 1,000 reviews.

### Is it legal to scrape Expedia?

The actor collects publicly visible data: hotel information, prices and guest reviews with the reviewer's first name as the sites display it. It does not log in or access anything private. Reviews contain personal data (first names); make sure your use complies with GDPR and similar laws and with the sites' terms, and do not use the data to contact or profile individuals.

# Actor input Schema

## `mode` (type: `string`):

reviews: one item per guest review (rating, text, liked/disliked topics, date, stay month and nights, reviewer first name, hotel response). hotels: one details item per hotel (stars, address, coordinates, guest score and sub-scores, review count, amenities, policies, check-in/out). search: one item per hotel in a destination's search results for your dates (price per night, total price, struck-through price, refundability, score).

## `startUrls` (type: `array`):

Hotel pages of either site (https://www.expedia.com/Las-Vegas-Hotels-Bellagio.h140596.Hotel-Information, https://www.hotels.com/ho118295/bellagio-las-vegas-united-states-of-america/, other Expedia country domains work too) or search pages with a destination (https://www.expedia.com/Hotel-Search?destination=Lisbon\&startDate=2026-12-01\&endDate=2026-12-03). The URL's site decides whose reviews you get: Expedia and Hotels.com show different reviews of the same hotel.

## `hotelIds` (type: `array`):

Expedia Group property ids (the number after .h in Expedia URLs, also the expediaPropertyId in Hotels.com links), e.g. 140596. Hotels.com 'ho' numbers are a different id.

## `destinations` (type: `array`):

City, area or landmark names to search, e.g. "Las Vegas" or "Lisbon, Portugal". Looked up with the site's own destination search. In reviews and hotels mode the top hotels of each destination are processed.

## `site` (type: `string`):

Which site to read for property ids and destinations (URLs always use their own site). 'both' reads Expedia and Hotels.com: two separate review sets, or one details/search item per site.

## `maxItems` (type: `integer`):

Stop after this many items in total (reviews, hotels or search results across all inputs).

## `maxReviewsPerHotel` (type: `integer`):

Reviews mode: cap per hotel and site. Popular hotels have tens of thousands of reviews.

## `maxHotelsPerSearch` (type: `integer`):

Reviews and hotels mode: how many top hotels (the site's recommended order) of each destination or search page are processed.

## `sortReviewsBy` (type: `string`):

Order of the reviews. 'Only reviews newer than' and monitoring always use newest first.

## `reviewsNewerThan` (type: `string`):

Only reviews newer than: a date (YYYY-MM-DD) or a period such as '7 days' or '24 hours'. Reviews are read newest first and paging stops at the first page that is entirely older.

## `onlyNewOrChanged` (type: `boolean`):

Remember what earlier runs of this search returned and output only new items or items whose price/status changed. Use with a Schedule; unchanged items are not charged. Reviews: each run checks the newest 'Max reviews per hotel' reviews and saves only reviews not seen before. Hotels: compared on guest score and stars. Search: price per night, total, struck-through price, availability and refundability.

## `monitoringKey` (type: `string`):

Name for this watch, so several schedules on the same search stay separate; defaults to a key derived from the search input.

## `checkIn` (type: `string`):

Search mode: check-in date (YYYY-MM-DD). Without it the dates of the search URL are used, else 7 days from today.

## `nights` (type: `integer`):

Search mode: length of stay when the check-in date is set here (or the search URL has no check-out date).

## `adults` (type: `integer`):

Search mode: adults per room.

## `rooms` (type: `integer`):

Search mode: number of rooms.

## `sortSearchBy` (type: `string`):

Search mode: order of the hotels.

## `proxyConfiguration` (type: `object`):

Residential proxy is required: Expedia's bot protection rejects almost all datacenter traffic. Each request uses a new residential IP; rejected attempts are retried and cost only a few KB. Measured proxy traffic: about 0.6 MB per 1,000 reviews (about $0.005 of residential traffic).

## Actor input object example

```json
{
  "mode": "reviews",
  "startUrls": [
    {
      "url": "https://www.expedia.com/Las-Vegas-Hotels-Bellagio.h140596.Hotel-Information"
    }
  ],
  "hotelIds": [],
  "destinations": [],
  "site": "expedia",
  "maxItems": 100,
  "maxReviewsPerHotel": 100,
  "maxHotelsPerSearch": 10,
  "sortReviewsBy": "newest",
  "onlyNewOrChanged": false,
  "nights": 1,
  "adults": 2,
  "rooms": 1,
  "sortSearchBy": "recommended",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        {
            "url": "https://www.expedia.com/Las-Vegas-Hotels-Bellagio.h140596.Hotel-Information"
        }
    ],
    "hotelIds": [],
    "destinations": [],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("tortuga/expedia-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": [{ "url": "https://www.expedia.com/Las-Vegas-Hotels-Bellagio.h140596.Hotel-Information" }],
    "hotelIds": [],
    "destinations": [],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("tortuga/expedia-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    {
      "url": "https://www.expedia.com/Las-Vegas-Hotels-Bellagio.h140596.Hotel-Information"
    }
  ],
  "hotelIds": [],
  "destinations": [],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call tortuga/expedia-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tortuga/expedia-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/NEQ9ZP0MZQMgQDKOh/builds/B0EFemH0Fe3yaoAaU/openapi.json
