Search Booking.com Hotels and Resolve Canonical Property URLs

Site booking.comTask search-booking-hotel-pricesVersion v3Updated Aug 19, 2026Category travel

Search Booking.com accommodations by destination or property name, resolve opaque hotel identities to canonical hotel URLs, and optionally extract dates, occupancy, prices, and room details. This skill was captured from a live agent session on booking.com and publishes here verbatim, exactly as an agent receives it.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Purpose

Find Booking.com accommodations for a supplied destination or property-name query. For property-name lookups, resolve the matching result's canonical /hotel/{country-code}/{slug}.html URL rather than guessing an opaque property identity. When dates and occupancy are supplied, return comparable availability, prices, and room details.

When to Use

Use for hotel-name-to-Booking-URL resolution, accommodation searches, availability and price comparison, optional currency selection, filtering, or pagination. Validate similarly named properties using the returned name, address, country, and URL. This is read-only; never reserve, save, sign in, or enter payment flows.

Workflow

  1. For a property-name or destination query, construct the direct search URL without visiting the homepage or typing into a form:

https://www.booking.com/searchresults.html?ss={url-encoded-query} For priced searches append &checkin={YYYY-MM-DD}&checkout={YYYY-MM-DD}&group_adults={adults}&group_children={children}&no_rooms={rooms} and repeat &age={child-age} once per child. Append &selected_currency={ISO-4217-code} only when requested. 2. If a Booking dest_id and dest_type are already available, include &dest_id={dest-id}&dest_type={dest-type} alongside ss. For an ambiguous free-form destination, prefer Booking autocomplete (https://accommodations.booking.com/autocomplete.json?aid={aid}&query={url-encoded-query}&lang=en-us&size=10) and choose the matching (dest_id,dest_type) before composing the results URL. Never invent an opaque ID. 3. In one browser-agent call, navigate directly to the constructed URL with waitUntil: 'load' or domcontentloaded, wait briefly for hydration, and run the evaluator below. For a hotel-name lookup, select the result whose normalized name best matches {property-query} and whose address/country matches any supplied location constraint; return its canonical href. If the result href is relative, prefix https://www.booking.com. 4. If a canonical property URL is needed, navigate directly to the resolved URL, preserving only useful language/currency parameters. The observed durable form is https://www.booking.com/hotel/{country-code}/{slug}.html (for example, a name search can resolve to /hotel/it/caruso.html). Do not use an affiliate label or advertising query string as the identity. Run the same evaluator on the detail page to return the canonical URL and property metadata. 5. For price searches, extract the hydrated property cards. If more than 25 results are requested, repeat the same URL with offset=25, 50, etc., and concatenate while deduplicating by canonical URL. order=price, bayesian_review_score, class, distance_from_search, and homes_apartments_first are useful optional sort modifiers. Optional filters may be encoded in nflt, including ht_id, class, review_score, mealplan, oos, fc, hotelfacility, roomfacility, chaincode, genius, and sustainable_property.

Required evaluator

Run this evaluate() on either the hydrated search-results page or the loaded hotel detail page:

(() => {
  const clean = s => (s || '').replace(/\\s+/g, ' ').trim();
  const abs = href => { try { return new URL(href, location.origin).href.split('#')[0]; } catch { return null; } };
  const cards = [...document.querySelectorAll('[data-testid="property-card"]')];
  const rows = cards.map(card => {
    const a = card.querySelector('a[data-testid="title-link"], h3 a, a[href*="/hotel/"]');
    return {
      name: clean(card.querySelector('[data-testid="title"], h3')?.textContent),
      url: abs(a?.getAttribute('href')),
      address: clean(card.querySelector('[data-testid="address"]')?.textContent) || null,
      price: clean(card.querySelector('[data-testid="price-and-discounted-price"], [data-testid="price-for-x-nights"], span[class*="price"]')?.textContent) || null,
      roomDetails: clean(card.querySelector('[data-testid="property-card-unit-configuration"], [data-testid="unit-configuration"], [data-testid="recommended-units"]')?.textContent) || null,
      reviewScore: clean(card.querySelector('[data-testid="review-score"]')?.textContent) || null
    };
  }).filter(x => x.name || x.url);
  if (rows.length) return { page: 'search-results', results: rows };
  const hotelLink = document.querySelector('link[rel="canonical"], a[href*="/hotel/"]');
  const path = location.pathname.match(/^\\/hotel\\/([^/]+)\\/([^/]+?)(?:\\.en)?\\.html$/i);
  const name = clean(document.querySelector('h1, [data-testid="title"]')?.textContent);
  const address = clean(document.querySelector('[data-testid="address"], [data-testid="hotel-address"], [class*="address"]')?.textContent) || null;
  return {
    page: 'hotel-detail',
    results: [{
      name,
      url: abs(hotelLink?.getAttribute('href')) || (path ? `https://www.booking.com/hotel/${path[1]}/${path[2]}.html` : location.href.split('#')[0]),
      address,
      price: null,
      roomDetails: null,
      reviewScore: clean(document.querySelector('[data-testid="review-score"], [class*="review-score"]')?.textContent) || null
    }].filter(x => x.name || x.url)
  };
})()

Site-Specific Gotchas

  • Booking's canonical search endpoint is /searchresults.html; ss is the direct property-name/destination query parameter. Search results commonly expose canonical hotel links such as /hotel/it/caruso.html; the slug is not an opaque ID and must be read from the matched result.
  • Do not infer a hotel URL from a name alone: similarly named properties exist. Match the result name and validate address, city, and country before returning the URL. A known URL can be opened directly, but an unknown property identity requires search-to-result resolution first.
  • Google results and affiliate URLs may contain tracking parameters, but those are unnecessary for property identity. Prefer the clean Booking canonical path and optionally retain .en.html for English presentation.
  • Booking can show an AWS WAF challenge, consent layer, sign-in information overlay, or error page. Use a real browser session, preferably with a residential proxy; wait for hydration and dismiss only a blocking consent/sign-in overlay. A challenge is not evidence that no property exists.
  • Child ages are required individually whenever group_children is nonzero. Prices can display per night or for the whole stay; normalize both when extracting priced results.
  • Card selectors using data-testid are preferred but can drift. Null price, room, address, or score values are valid. Verify availability at room level rather than assuming a property card's amenity applies to every room.
  • Pagination uses offset and is normally 25 properties per page; deduplicate by canonical URL.

Expected Output

For a URL lookup, return {query, matchedName, address, url} and, when no unambiguous match exists, {query, candidates, reason: "ambiguous_or_not_found"}. For a priced search, return the query parameters alongside an array of {name, url, price, roomDetails, address, reviewScore} objects, preserving requested dates, occupancy, child ages, currency, filters, and pagination metadata.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=booking.com&task=search-booking-hotel-prices