Search Booking.com Hotels with Availability, Reviews, and Map Coordinates

Site booking.comTask search-hotelsVersion v9Updated Aug 23, 2026Category travel

Search Booking.com accommodations, resolve property identities safely, and extract date-specific availability, prices, reviews, coordinates, 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

Search Booking.com accommodations by destination, property name, or known canonical hotel URL. Resolve unknown property names without guessing slugs, then inspect the property's date-specific room inventory to determine whether it appears bookable, including prices, room details, cancellation terms, review metrics, coordinates, and representative reviews. A property's own website being unavailable is not itself evidence of unavailability; Booking room-level availability for the requested dates is the relevant signal. This is strictly read-only: never reserve, save, sign in, or enter payment flows.

When to Use

Use for hotel-name resolution, accommodation discovery, date-specific availability and price comparison, enriching known Booking hotel URLs, optional currency/sort/filter selection, map searches, pagination, or checking whether a property has rooms bookable for supplied dates and occupancy. Validate similarly named properties using name, address, city, country, and canonical URL.

Workflow

  1. Use one real browserless_agent session with a residential proxy and retain the same proxy/session throughout. Booking may require AWS WAF clearance; use waitUntil: 'load' or 'domcontentloaded', wait 3–7 seconds for hydration, and never wait for networkidle. Report an uncleared challenge rather than treating it as zero availability.
  2. If a canonical URL is supplied, navigate directly to it and append only requested parameters: checkin={YYYY-MM-DD}&checkout={YYYY-MM-DD}&group_adults={adults}&group_children={children}&no_rooms={rooms}&selected_currency={ISO-4217-code}. Add one age={child-age} per child when children are present. The durable detail form is https://www.booking.com/hotel/{country-code}/{slug}.en-gb.html (or .html) with these query parameters. Strip affiliate label, sid, and tracking parameters.
  3. If given a property name or destination, first use https://www.booking.com/searchresults.html?ss={url-encoded-query}. For destination searches, resolve (dest_id,dest_type) through https://accommodations.booking.com/autocomplete.json?aid={aid}&query={url-encoded-query}&lang=en-us&size=10, obtaining {aid} from window.utag_data.aid after the Booking session is loaded. Select the result matching the requested name/address/country. Never invent an opaque ID or slug.
  4. Compose one direct results URL with ss, resolved dest_id/dest_type, dates, occupancy, currency, and optional order, offset, and URL-encoded nflt. Useful sorts include price, bayesian_review_score, bayesian_review_score_and_price, class, class_asc, distance_from_search, and homes_apartments_first. Useful filters include ht_id, class, review_score, mealplan, oos (free cancellation), fc (no prepayment), hotelfacility, roomfacility, chaincode, genius, sustainable_property, distance, pri, price, and di.
  5. On hydrated results, extract [data-testid="property-card"] cards. Follow selected clean canonical /hotel/{country-code}/{slug}.html links directly when room-level availability or richer reviews/coordinates are required. Do not click “See availability,” Reserve, Save-to-list, sign-in, or payment controls.
  6. For a known or resolved property, use the direct detail URL with the requested dates and occupancy, then run the availability evaluator below. Treat visible room rows/offers with a price and a booking/reservation control as evidence of apparent bookability; treat explicit “no rooms,” “not available,” “sold out,” or “no availability” text as unavailable. A visible Reserve control may be detected but must never be clicked. If the page is blocked, return an anti-bot status rather than bookable:false.
  7. For more than 25 search results, repeat the same URL with offset=25, 50, etc., concatenate, and deduplicate by canonical URL. Booking normally returns 25 properties per page and caps pagination at offset=1000.
  8. Run this concrete evaluator on the loaded search-results or hotel-detail page:
(() => {
const clean=x=>(x||'').replace(/\s+/g,' ').trim();
const abs=h=>{try{return new URL(h,location.origin).href.split('#')[0]}catch{return null}};
const canonical=h=>{const u=abs(h);if(!u)return null;const m=new URL(u).pathname.match(/^\/hotel\/([^/]+)\/([^/?#]+?)(?:\.en(?:-[a-z]{2})?)?\.html$/i);return m?`https://www.booking.com/hotel/${m[1]}/${m[2]}.html`:u.split('?')[0]};
const body=clean(document.body.innerText), blocked=/awswaf|challenge\.js|captcha|verify you are human/i.test(body);
const cards=[...document.querySelectorAll('[data-testid="property-card"]')].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"],[data-testid="price"]')?.textContent)||null,roomDetails:clean(card.querySelector('[data-testid="recommended-units"],[data-testid="unit-configuration"],[data-testid="property-card-unit-configuration"]')?.textContent)||null,reviewScore:clean(card.querySelector('[data-testid="review-score"]')?.textContent)||null}}).filter(x=>x.name||x.url);
const rows=[...document.querySelectorAll('#hprt-table tr,[data-testid="property-section--content"] table tr,[data-testid*="room"]')].map(x=>clean(x.innerText)).filter(x=>x.length>10);
const reserve=[...document.querySelectorAll('button,input[type=submit],a')].map(e=>clean(e.value||e.textContent)).filter(x=>/reserve|book now|i['’]?ll reserve/i.test(x)).slice(0,10);
const soldOutLanguage=/no rooms|not available|sold out|no availability/i.test(body);
const link=document.querySelector('link[rel="canonical"]')?.getAttribute('href');
const path=location.pathname.match(/^\/hotel\/([^/]+)\/([^/?#]+?)(?:\.en(?:-[a-z]{2})?)?\.html$/i);
const name=clean(document.querySelector('h1,[data-testid="title"]')?.textContent);
const address=clean(document.querySelector('[data-testid="PropertyHeaderAddressDesktop-wrapper"],.hp_address_subtitle,[data-testid="address"],[data-testid="hotel-address"],[class*="address"]')?.textContent)||null;
const c=document.querySelector('[data-atlas-latlng]')?.getAttribute('data-atlas-latlng'), n=(c||'').match(/-?\d+(?:\.\d+)?/g);
return blocked?{ok:false,reason:'awswaf_challenge_or_captcha'}:cards.length?{page:'search-results',results:cards}:{page:'hotel-detail',name,url:canonical(link)||(path?`https://www.booking.com/hotel/${path[1]}/${path[2]}.html`:location.href.split('?')[0]),address,availability:{bookable:!soldOutLanguage&&rows.length>0,explicitlyUnavailable:soldOutLanguage,roomRows:rows.slice(0,30),reserveLabels:reserve},coordinates:n&&n.length>=2?{lat:Number(n[0]),lng:Number(n[1])}:null,reviewScore:clean(document.querySelector('[data-testid="review-score"],[class*="review-score"]')?.textContent)||null};
})()
  1. When extracting richer detail data, prefer [data-atlas-latlng] for coordinates, [data-testid="featuredreview-text"], .c-review__body, [data-testid="review-positive-text"] for excerpts, [data-testid="review-score"] for score, and [data-testid="price-and-discounted-price"],[data-testid="price-for-x-nights"] for room prices. Preserve raw price text and, when total and dates are known, emit both total-stay and per-night values.

Site-Specific Gotchas

  • Booking's canonical search endpoint is /searchresults.html; property identity uses clean /hotel/{two-letter-country-code}/{slug}.html paths. Localized .en-gb.html detail URLs accept the same availability parameters.
  • A property name alone is not enough to construct a URL. Resolve the result and validate name, address, city, country, and href before opening the detail page.
  • Booking may show AWS WAF, consent, sign-in, hCaptcha, or error overlays. Use a residential-proxy browser session, retain it throughout, wait for hydration, and report an uncleared challenge. A challenge is not evidence that the property has no rooms.
  • The absence of the property's independent website does not determine Booking availability. Only room-level offers for the requested dates, occupancy, and room count establish apparent bookability; availability can change and a visible offer is not a completed reservation.
  • Do not click reservation or availability CTAs. Detect their labels and inspect surrounding room rows instead. roomRows.length === 0 alone is inconclusive if the page is still hydrating, so wait before evaluating and distinguish explicit sold-out text from missing markup.
  • Child ages are mandatory whenever group_children is nonzero. Prices may be nightly or total-stay in the UI; preserve raw text and normalize both when possible.
  • [data-atlas-latlng] is the preferred coordinate source; return null rather than guessing. Review excerpts and coordinates are usually absent from search cards, so enrich selected properties through direct canonical detail URLs.
  • nflt values are URL-encoded semicolon-delimited filters. Common observed values include ht_id=204 hotel, 201 apartment, 203 hostel, review_score=90|80|70|60, oos=1 free cancellation, fc=2 no prepayment, mealplan=1 breakfast, hotelfacility=2 parking, 17 airport shuttle, 54 EV charging, and popular_activities=2 pool. Confirm drift by reading the resulting URL when needed.
  • Currency display can be per night or total stay; when possible set both selected_currency={currency} and the same-origin cur_curr cookie after WAF clearance. Never use affiliate parameters as identity.
  • The in-page same-origin /dml/graphql FullSearch request may provide more reliable structured search data, but it is permissible only after WAF clearance and must be fetched from page context with credentials; fall back to the DOM evaluator when unavailable.

Expected Output

For a resolved property availability check, return {ok, query, matchedName, address, url, checkin, checkout, occupancy, availability:{bookable, explicitlyUnavailable, roomRows, reserveLabels}, price, roomDetails, reviewScore}. Use bookable:null with an explicit reason for a WAF/challenge or still-loading page. For searches, return request parameters, resolved destination or canonical URLs, pagination metadata, and rows shaped as {name,url,address,price,roomDetails,reviewScore}. For ambiguous names return {ok:false,reason:"ambiguous_or_not_found",candidates:[...]}; for an uncleared anti-bot wall return an explicit WAF failure rather than an empty result set.

Call it

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