Scrape Booking.com hotel listings for travel research, price monitoring, and structured hotel datasets.
What does this Actor do?
Booking.com Hotel Scraper returns structured hotel listings by destination or direct hotel URL. The default output is deliberately lean: identity, canonical URL, property type, location, rating, availability, and price context. Rooms, policies, facility highlights, photos, and review details are optional per-hotel enrichments.
Use it for hotel supply research, price snapshots, destination comparisons, lead lists, and travel-data pipelines. It does not sign in, reserve rooms, solve challenges, or collect private traveler data.
Who is it for?
- Travel and hospitality analysts comparing public hotel supply and prices.
- Revenue and market-research teams building repeatable destination snapshots.
- Data engineers feeding hotel identity, availability, and price context into internal pipelines.
- AI-agent builders who need schema-described Booking.com listing data through API or MCP.
Input example
{
"destination": "Lisbon",
"checkIn": "2026-08-15",
"checkOut": "2026-08-17",
"adults": 2,
"children": 1,
"childrenAges": [5],
"rooms": 1,
"currency": "USD",
"language": "en-us",
"maxHotelsPerSearch": 10,
"maxCandidatesPerSearch": 50,
"maxHotelsPerRun": 10
}
This saves at most 10 lean hotel rows. No per-hotel enrichment is requested.
Choose the product surface
Lean listing/search data is the default. Enable only the extra surfaces your workflow needs:
{
"hotelUrls": [
"https://www.booking.com/hotel/pt/chiado-arty-flats.html"
],
"checkIn": "2026-08-15",
"checkOut": "2026-08-17",
"adults": 2,
"children": 0,
"childrenAges": [],
"rooms": 1,
"enrichRooms": true,
"enrichPolicies": true,
"enrichFacilities": true,
"enrichPhotos": true,
"enrichReviews": true,
"maxHotelsPerRun": 1
}
Each selected enrichment runs independently. If rooms fail but photos succeed, the actor keeps the base hotel row, reports the rooms warning on that row, and keeps the successful photos. There is no enrichment-specific charge event, so a failed enrichment is never charged separately.
enrichReviews returns the review score/count, labels, secondary score, and available summary metadata. It does not return individual guest review text.
Dates for scheduled runs
Use fixed checkIn and checkOut dates for one-off snapshots. For schedules, use a relative pair that resolves once when each run starts:
{
"destination": "Paris",
"checkInOffsetDays": 14,
"stayNights": 3,
"currency": "EUR",
"sortBy": "PRICE_ASC",
"maxHotelsPerSearch": 5,
"maxCandidatesPerSearch": 50,
"maxHotelsPerRun": 5
}
Do not combine the two date modes. Every row records the mode, relative values, resolved dates, and resolution time in provenance.
Search targets
Provide at least one of:
destination: one city, region, landmark, or hotel phrase.destinations: multiple independent destination searches.hotelUrls: direct Booking.com/hotel/{country}/{slug}.htmlURLs.
Direct hotel URLs can be more deterministic than broad destination searches because the actor resolves and matches the stable hotel ID before accepting a row. Neither direct URLs nor destination searches should be treated as immune to Booking.com blocking, challenges, redirects, inventory changes, or schema changes.
Input settings
| Input | Type | Meaning |
|---|---|---|
destination |
string | One destination search phrase. |
destinations |
string[] | Multiple destination searches. |
hotelUrls |
string[] | Direct Booking.com hotel URLs; each can return at most one hotel. |
checkIn, checkOut |
string | Optional YYYY-MM-DD range. Supply both or neither. |
checkInOffsetDays, stayNights |
integer | Relative date pair for schedules: offset 0–365 and stay 1–30 nights. |
adults |
integer | Adult guests; default 2. |
children |
integer | Child guests; default 0. |
childrenAges |
integer[] | Exactly one age, 0–17, for every child. |
rooms |
integer | Rooms requested; default 1. |
currency |
string | Requested three-letter currency. Returned currency is recorded separately. |
language |
string | Requested Booking.com locale, such as en-us or de-de. |
countryCode |
string | Optional country for the existing residential proxy route. It is not a blocking guarantee. |
propertyTypes |
string[] | Optional categories such as HOTEL, APARTMENT, or HOSTEL. |
minReviewScore |
integer | Optional minimum of 6, 7, 8, or 9. |
starRatings |
integer[] | Optional exact star ratings from 1 to 5. |
freeCancellationOnly |
boolean | Keep listings exposing free cancellation in the requested stay context. |
minPricePerNight, maxPricePerNight |
number | Optional bounds calculated from displayed stay total ÷ nights; dates are required. |
sortBy |
string | RECOMMENDED, PRICE_ASC, TOP_REVIEWED, STARS_DESC, or STARS_ASC. |
maxCandidatesPerSearch |
integer | Unpaid candidate rows evaluated per destination; 1–500 and at least the saved per-search cap. |
maxHotelsPerSearch |
integer | Maximum rows from each destination search; 1–100. |
maxHotelsPerRun |
integer | Hard saved-and-charged row cap across the whole run; 1–100. |
enrichRooms |
boolean | Room configurations and matched offer data. |
enrichPolicies |
boolean | Cancellation, prepayment, pet, and meal-plan signals. |
enrichFacilities |
boolean | Facility and property-USP highlights. |
enrichPhotos |
boolean | Available hotel photos. |
enrichReviews |
boolean | Review labels, secondary score, and summary metadata. |
Backward compatibility: maxItems sets both limits and includeDetails enables all enrichments. Do not combine maxItems with either explicit limit. New integrations should use the explicit fields.
Limits and charging
The three limits have different scopes:
maxCandidatesPerSearchcaps unpaid candidate rows evaluated for each destination while applying filters.maxHotelsPerSearchstops each destination search independently.maxHotelsPerRunis the total number of dataset rows that can be saved and charged across all destinations and direct hotel URLs.
Duplicate stable hotel IDs are saved once per run. Direct hotel URL targets return at most one row each. RUN_SUMMARY records all three limits plus candidatesEvaluated, filteredOutRows, saved, and chargedHotels. If filters consume the candidate allowance before any row can be saved, the run reports INPUT_CONFIGURATION_LIMIT_REACHED instead of a misleading empty success.
Output example
Example dataset item excerpt:
{
"hotelName": "Chiado Arty Flats",
"hotelId": 1814397,
"canonicalUrl": "https://www.booking.com/hotel/pt/chiado-arty-flats.html",
"childrenAges": [5],
"price": "$400.03",
"currency": "USD",
"priceContext": {
"status": "AVAILABLE",
"basis": "TOTAL_STAY",
"checkIn": "2026-08-15",
"checkOut": "2026-08-17",
"nights": 2,
"requestedCurrency": "USD",
"returnedCurrency": "USD",
"total": { "amount": 400.03, "formatted": "$400.03", "currency": "USD" },
"pricePerNight": { "amount": 200.02, "formatted": null, "currency": "USD" },
"pricePerNightBasis": "DISPLAYED_TOTAL_DIVIDED_BY_NIGHTS",
"excludedTaxesAndFees": { "amount": 42.31, "formatted": "$42.31", "currency": "USD" },
"totalIncludesTaxesAndFees": false
},
"availabilityContext": {
"status": "AVAILABLE",
"adults": 2,
"children": 1,
"childrenAges": [5],
"rooms": 1
},
"enrichment": {
"rooms": { "requested": false, "status": "NOT_REQUESTED", "warning": null },
"policies": { "requested": false, "status": "NOT_REQUESTED", "warning": null },
"facilities": { "requested": false, "status": "NOT_REQUESTED", "warning": null },
"photos": { "requested": false, "status": "NOT_REQUESTED", "warning": null },
"reviews": { "requested": false, "status": "NOT_REQUESTED", "warning": null }
},
"warnings": [],
"scrapedAt": "2026-07-20T12:00:00.000Z"
}
priceContext.total is the total Booking.com displayed for the requested stay. pricePerNight is that displayed total divided by the number of nights and rounded to two decimals; it is not a separately quoted rate. excludedTaxesAndFees remains separate and is never silently added.
Run outcomes
Read RUN_SUMMARY from the default key-value store. The status is explicit:
| Status | Meaning |
|---|---|
SUCCEEDED |
Requested base rows were saved without warnings. |
NO_MATCHES |
The target resolved but returned no matching hotels, or no destination/hotel could be resolved. A zero-row run fails instead of reporting success. |
SOLD_OUT |
The requested dates/occupancy produced only sold-out results or no availability. |
TARGET_CHALLENGE |
Booking.com returned an access challenge. |
PARTIAL |
At least one base row was saved, but a target or optional enrichment failed, or the safe deadline was reached. |
INPUT_CONFIGURATION_LIMIT_REACHED |
Input validation, a documented target/input limit, or the candidate allowance before the first filtered match was reached. Validation errors occur before the start event. |
FAILED |
An unexpected target, transport, storage, or response error stopped the run. |
When optional work fails, every retained row has a structured warning with scope, code, message, and retryable. Deadline-limited runs also write PENDING_WORK.
API usage
Node.js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('fetch_cat/booking-com-hotels-scraper').call({
destination: 'Lisbon',
maxHotelsPerSearch: 10,
maxCandidatesPerSearch: 50,
maxHotelsPerRun: 10,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
Python
from apify_client import ApifyClient
client = ApifyClient("$APIFY_TOKEN")
run = client.actor("fetch_cat/booking-com-hotels-scraper").call(run_input={
"destination": "Lisbon",
"maxHotelsPerSearch": 10,
"maxCandidatesPerSearch": 50,
"maxHotelsPerRun": 10,
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)
cURL
curl -X POST 'https://api.apify.com/v2/acts/fetch_cat~booking-com-hotels-scraper/runs?token=$APIFY_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"destination":"Lisbon","maxHotelsPerSearch":10,"maxCandidatesPerSearch":50,"maxHotelsPerRun":10}'
MCP and AI agents
The input, output, and dataset schemas are published for OpenAPI and Apify MCP use. Ask an agent to start with lean output and opt into only the enrichments it needs.
claude mcp add apify -- npx -y @apify/actors-mcp-server --actors fetch_cat/booking-com-hotels-scraper
Equivalent MCP JSON configuration:
{
"mcpServers": {
"apify": {
"command": "npx",
"args": ["-y", "@apify/actors-mcp-server", "--actors", "fetch_cat/booking-com-hotels-scraper"],
"env": { "APIFY_TOKEN": "$APIFY_TOKEN" }
}
}
}
Example prompts:
- “Collect 10 lean Booking.com hotel rows for Lisbon and preserve total-price/tax context.”
- “Schedule a Paris snapshot 14 days ahead for a three-night stay, sorted by price.”
- “Find 4- and 5-star Lisbon hotels rated 8+ and evaluate at most 50 candidates.”
- “Resolve this direct Booking.com hotel URL and add rooms and policies.”
- “Compare three destinations, cap each search at 5 hotels, and cap the whole charged run at 12 rows.”
Troubleshooting
TARGET_CHALLENGE: retry later or use a smaller target set. The actor does not solve target challenges or promise an unblock.NO_MATCHES: verify spelling or use a direct hotel URL when you already know the property.SOLD_OUT: verify dates, guest ages, and occupancy, or retry without dates to collect identity-only listings.PARTIAL: inspect rowwarningsand eachenrichmentstatus; successful base data remains usable.INPUT_CONFIGURATION_LIMIT_REACHED: checkchildrenAges, the chosen date mode, target count, price range, and all three explicit limits. With filters, raisemaxCandidatesPerSearchor relax the filters.- Returned currency differs from requested currency: use
provenance.requested.currencyandpriceContext.returnedCurrencyrather than assuming conversion.
Legality and responsible use
Use this actor for lawful public-data research. Follow Booking.com’s terms, applicable laws, and privacy requirements. Do not use it for personal traveler data, reservations, account-only data, abusive traffic, or attempts to bypass access controls.
Support
For a reproducible issue, include the Apify run ID or run URL, exact input JSON, RUN_SUMMARY, expected output, actual output, and a reproducible public URL when one exists. Never post API tokens, cookies, or private traveler data.