Scrape Booking.com hotel details from hotel URLs: room types with amenities, facilities by group, photos, address, coordinates, review score, stars and the cheapest offer for your dates (total, taxes, free cancellation, breakfast). One flat row per hotel, no personal data.
History of booking-hotel-details-scraper (folder actors/booking-hotel-details). Versions are the Apify build numbers.
Unreleased (all-in price: taxesIncluded and priceTotalAllIn)
Brief docs/cloud-tasks/2026-10-23_travel-prices-all-in.md (2026-10-23): rows are travel-listing/1.6 (the detail schema on top of the search schema travel-listing/1.5), two columns after taxesAndFees, nothing else changes (priceTotal keeps its meaning: the cheapest offer before the charges Booking.com shows as not included).
taxesIncluded (read by parseOfferResponse, new OfferPrice.taxesIncluded): false when the offer shows an excluded amount above 0 (both priced offers of the committed captures), true for an excluded amount of 0, null without that amount, with one in another currency, or without an offer.
priceTotalAllIn = priceTotal + taxesAndFees when taxesIncluded is false, priceTotal when it is true, null otherwise (allInPriceTotal of @scraper-studio/core/schema): €289.81 + €41.39 = €331.20 for the README example, $484.40 + $85.05 = $569.45 for the aparthotel.
Dataset schema: both columns documented; Overview shows priceTotalAllIn next to priceTotal, Prices shows both after taxesAndFees. README: Which price to compare paragraph, sample output with priceTotalAllIn (instead of checkOut), FAQ Which price is in the row?, the strengths worth knowing when switching (hard maxResults cap never charged beyond, unknown pages not charged), More scrapers pointing at priceTotalAllIn.
Unreleased (quality score: input documented for AI agents)
Brief docs/cloud-tasks/2026-10-20_studio-quality-score-insights.md: the input form passes the shared Store checks (runInputQualityChecks, runSearchTextChecks of packages/core/src/testing/store-check.ts).
Descriptions rewritten as a tool documentation (what the field does, format with an example, default and its reason, bounds): checkIn, checkOut, adults, children, childrenAges, rooms, includeOffer, maxPhotos, currency, language, maxResults, proxyConfiguration, maxRequests, delayMs, debugStoreResponses.
maxResults prefilled with 50 (default still 100): Apify's daily test of the public Actor runs the prefilled form, a 3-hotel run (the three prefilled URLs); 50 rather than 3 so that a form user who pastes their own list is not cut at three hotels.
inputs/daily-check.json = the prefilled form: the same 3 Lisbon hotel URLs and maxResults 50 (was 3: same three rows; the row count of the daily check is not checked by scripts/release-actor.mjs); maxPhotos 10 removed (the form's 45 applies: same requests, longer photo lists), keys equal to the defaults removed (adults 2, includeOffer true, currency EUR, language en-us). store.json › dailyCheck.what updated; the live daily-check task follows when the pilot re-sends it.
Store texts carry "Booking.com hotel details": actor.json description ("Scrape Booking.com hotel details from hotel URLs: …") and store.json SEO description ("Scrape Booking.com hotel details by URL: …"); title and README line 1 unchanged. store.json › searchSynonyms: Booking.com scraper, room prices, room types. README input table: maxResults, maxRequests rows.
Example tasks: the 8 SEO titles now carry the long-tail search "… – Booking.com hotel details" instead of "… – Booking.com details" (e.g. "Lisbon hotel prices next month – Booking.com hotel details"); published with scripts/publish-tasks.mjs at the publication of the Actor.
No code change: rows, logs and charges are identical.
Unreleased (README visibility)
README first screen and links (brief docs/cloud-tasks/2026-10-20_studio-readme-visibility-new-actors.md, 2026-10-21), same layout as the public Actors (PR #50). The Store search indexes only the first ~3,000 README characters, so they now hold: an opening sentence with the phrases agents search ("Booking.com hotel details", "room prices", competitor benchmarking, room price tracking with scheduled runs), a "No login, no cookies, no personal data" line, the Free-plan price in figures in How much ($4.21 per 1,000 hotels; Apify's $5 monthly credit covers about 1,187), computed from pricing/2026-10-20_pay-per-event.autopilot.json (Free tier of the primary event, one apify-actor-start event deducted), and a real output snippet (12 key columns of the README example row, the cheapest room in highlights). Further down: Use it from your AI assistant (MCP URL https://mcp.apify.com?tools=myth84/booking-hotel-details-scraper and a ready-to-copy prompt with a minimal input that never expires), Alternatives and switching (official API, home-made scraper, input-name mapping table, no competitor named), More scrapers by Flatrow Data Studio (Store links to the travel Actors already public, the Booking.com search first) with an invitation to rate the Actor (no counterpart), and the non-affiliation line at the very bottom. The cost section and the MCP cost bullet quote the same figures; the line 1 heading is unchanged. Checked by runStoreChecks (readmeVisibility). No code change: rows, logs and charges are identical.
Unreleased
Output schema travel-listing/1.3 (additive, follows docs/cloud-tasks/2026-10-20_travel-extend-monitor.md, PR #34): the search schema of the travel studio gained the price-monitoring block (changeType, previousPrice, priceDelta, priceDeltaPercent) and took the tag travel-listing/1.2; the detail schema this Actor shares follows as 1.3 so that one tag means one column set. The four columns sit after scrapedAt, before the detail columns, and are always null for this Actor (no monitoring mode). Dataset schema regenerated, README example and column table, output schema description and tests updated; no other change in the rows.
Release fix (brief docs/cloud-tasks/2026-10-20_booking-fix-release.md): the first release stopped at the control step (run ZavxrNOc583Z1E8OD: 28 rows, 29 expected) because inputs/control-scale.json listed pestana-palace-lisboa, a hotel page guessed in the Booking audit (probe input booking-run3.json, label hotel-page-guess, never answered) and not a page name returned by Booking.com; an unknown page gives no row by design (HotelPageNotFound: not charged, not an error). The control input now lists the 28 pages returned by Booking.com in the captures (maxResults 28); the five example tasks that carried the same guess use a confirmed page instead (almirante, vip-executive-suites-do-marques, family-refuge-lisbon) or one page less (upscale hotels: 7, facilities audit: 28). No code change.
Test (src/tasks.test.ts): known pages come only from Booking.com responses (city landing page, search results), no longer from probe inputs; every file of inputs/ lists known pages only, without duplicates (except the deliberately unknown page of validation.json), and its maxResults equals their count — the exact row count scripts/release-actor.mjs checks.
0.1 — 2026-10-20
First version, written offline on the fixtures captured by the probe on 2026-10-20 (audit docs/audits/booking-details.md, brief docs/cloud-tasks/2026-10-20_booking-details-build.md): two calls per hotel to Booking.com's internal GraphQL API (POST /dml/graphql, query texts sent as is, no cookie, no token) — PropertyPage (hotelPageByPageName) for the hotel page, then a one-row FullSearch on the hotel itself (destType: HOTEL) for the cheapest offer of the dates and guests, asked once more after a cold answer (no block, no price, or an irene error), as measured by the probe.
Output schema travel-listing/1.2 with ratingMax: 10: description, facilities by group with their payment notes (amenityGroups), facility titles (amenities), one line per room type with its amenities (sleepingArrangements), gallery photos (maxPhotos, 45 by default), short and full address (neighborhood, neighborhoodDescription), district (region), coordinates, review score and count, stars, accessibility, Booking.com's generated summary (locationSummary), DSA trader status (listedBy); offer: priceTotal, pricePerNight, priceBeforeDiscount, taxesAndFees, cancellationPolicy (free_cancellation_until:<date>), isAvailable, badges (meal_plan_included, third_party_offer, deal badges). Spoken languages, location flags, programmes, certifications and the room type of the cheapest offer go to highlights.
Decisions: one sticky datacenter session for the whole run (four hotels at a time), replaced once on an unusable response, then the hotel is given up; HotelPageNotFound gives no row, is not charged and is not an error of the run; a GraphQL validation error (schema change) fails the run at once instead of rotating through every hotel; an offer still empty after the second call gives isAvailable: false, an offer that could not be read (request failed or errors twice) gives isAvailable: null with empty prices (unknown is not "unavailable"); propertyTypeCode is Booking's accommodation type (HOTEL, APARTHOTEL), roomType is entire_place for apartments and aparthotels, hotel_room for hotels; facilities whose group is not described on the page go under "Other"; dates default to a check-in 30 days from the run date for 3 nights so that schedules and example tasks never expire.
Not included in v1 (audit § 3, documented in the README): the per-room rate table, house rules and check-in / check-out times, category scores, availability calendar, reviews and anything about hosts.
Pay-per-event result per hotel pushed; explicit failure when no row is produced; RUN_STATS with hotel pages, offers, empty and failed offers, second offer calls and unknown pages.