Extract hotel prices, ratings, amenities, star levels and deals from Priceline.com. Search any destination with date, room and guest filters, sort by price, rating or stars, and return up to 500 results per search. Export data, run via API, schedule runs, or integrate with other tools. By MrBridge
All notable changes to Priceline Hotel Scraper are documented here.
v0.4.1 (2026-08-14) - sortBy now actually sorts
Fixed
A resumed run could charge you twice for the same hotel. Each hotel is now recorded before it is written, not after, so an interruption at the worst possible moment costs you one missing row instead of a second charge. Restarts also read the hotel IDs back from the dataset and merge them with their own record, and a run that cannot account for every row it has already billed you for stops without writing anything.
A resumed run that had already reached your maxResults reported a failure instead of finishing quietly with nothing left to add.
A resumed run could also exceed your maxResults by one row. A hotel saved just before an interruption, too late for the counter to move, looked like a free slot on restart, and the run bought a replacement for a result you already had. maxResults is now measured against slots committed rather than rows confirmed.
Hotels that Priceline returns without an identifier are no longer saved. Such a row cannot be recognised by a later run, so it would eventually be delivered and charged twice. The count of dropped rows is reported in the run summary.
sortBy had no effect on the results you received. Priceline's listings page ignores the sort parameter in the URL, so every run came back in Priceline's own recommended order whatever you selected. Confirmed with two identical Paris searches, one on price_low and one on recommended: 24 of the same 25 hotels, the first ten in the same positions, neither ordered by price. Sorting is now applied to the extracted pool before any result is written, so price_low really does return the cheapest hotels of the search, and it decides which ones you get when maxResults or the run charge limit truncates the list.
Changed
Results are written after the search completes rather than one by one during it. Ordering a list requires holding it first. Runs are short and the dataset is complete at the same moment as before.
Sorting is deterministic. Equal prices or ratings break ties on hotel ID, so the same search returns the same order.
Known scope
Sorting covers the hotels Priceline returns for a search, roughly 30. It reorders that pool. It cannot reach a cheaper hotel that Priceline never listed. The input schema and the README now say so.
v0.4.0 (2026-08-14) - PPE billing integrity + source-safe deployment
Fixed
Push each validated hotel independently so persistence and synthetic PPE billing stay aligned.
Stop before the next dataset item when the run charge limit cannot cover it.
Persist pagination and deduplication state during aborts and migrations.
Exit failed runs with a non-zero status instead of reporting false successes.
Update production transitive dependencies to versions with no known npm audit findings.
Changed
Bundle project TypeScript with tsup before deployment.
Deploy from an explicit allowlist so source files, tests, fixtures, and internal docs are never uploaded to Apify.
Build the runtime image from the prebuilt bundle with production dependencies only.
Hotels are now pushed one by one as they are validated, instead of in a single batch at the end of the run. Results appear in the dataset while the run is still going.
Changed (breaking)
A run that cannot extract anything now finishes with status FAILED instead of SUCCEEDED. Previously every run reported success and the failure was only visible in the run-error key-value record, which made monitoring and scheduling unreliable. Consumers that treat SUCCEEDED as "data is present" keep working. Consumers that ignored run status and always read the dataset should now check the status.
Schema impact
kvs_schema.json: documented the OUTPUT run summary, and added the new CHECKPOINT record holding the pagination offset plus already-delivered hotel IDs used to resume without duplicates.
README FAQ on blocking updated: it previously claimed runs always end SUCCEEDED.
v0.3.3 (2026-06-01) - B1 spike conclusion (no behavior change)
Reverted
Removed the v0.3.2 js_instructions click-loop experiment. The Apollo SSR transport blob does NOT refresh after clicking "Load More" - the raw HTML keeps 32 unique hotel IDs regardless of how many clicks fire (verified empirically in run v19UOjlAYQTrjUVLK, 30 listings returned, $0.002 cost).
Decision
B1 deferred. Pursuing > 32 hotels per search would require a DOM-card parser (custom selectors on rendered cards) AND would lose the rich Apollo fields (brand, lat/lng, timezone, themes, amenities, urgencyMessaging, savingsPercentage). Effort L + quality regression. Status quo is the historical baseline since v0.1.
Why
Audit B1 backlog item: Priceline lazy-loads results via a "Load More" button (no scroll triggers, no ?offset=N URL pagination). The Apollo SSR transport blob may or may not refresh post-click. Single-run spike to resolve the open question before committing to a full DOM-parser refactor (L effort).
v0.3.0 (2026-06-01)
Added (SEV-3 audit, 6 high-value Apollo fields)
brand and brandOwner - parent group of the hotel (e.g. "Doubletree" / "Hilton Worldwide"). Null when independent.
latitude and longitude - GPS coordinates from Apollo geoCoordinate. Null when not provided.
timezone - IANA timezone identifier from location.timeZone (e.g. "America/New_York").
hotelThemes - travel-theme tags Priceline associates with the property (e.g. ["Family", "Business"]). Empty array when none.
savingsPercentage - discount expressed as a percent off the strikethrough price (numeric, parsed from Apollo string). Null when not advertised.
urgencyMessaging - scarcity banner string ("We only have a few rooms left at $270"). Null when no banner.
Changed (breaking)
address is now populated from city + stateCode + countryCode (e.g. "New York, NY, US"). Previously hard-coded to "". Empty string when no location parts are available.
propertyType field removed from output. It was never populated by the Apollo response and was always an empty string. Downstream consumers must update their schemas.
Changed
defaultMemoryMbytes lowered from 512 to 256 MB. The hot path (single HTML fetch + regex parse + array map) fits comfortably under 256 MB; verified via local fixture parse and smoke run.
All internal log strings translated from French to English (e.g. "Tentative" -> "Attempt", "Démarrage" -> "Starting", "Aucun hôtel trouvé" -> "No hotels found"). Per the global English-only-internal-artifacts doctrine.
Em-dashes removed from log strings (zero em-dash policy).
Schema impact
dataset_schema.json: added 8 fields, removed propertyType, added a third "Location & Brand" view that surfaces brand, GPS, timezone.
openapi_schema.json: HotelResult schema updated to match.
v0.2.0 (2026-06-01)
Fixed (SEV-4 x 2)
totalPrice semantics corrected. Was computing pricePerNight x nights (taxes-excluded estimate) via abuse of the FX-conversion helper, which diverged 15-25% from grandTotal (Apollo-provided total including taxes & fees). Now totalPrice uses grandTotal when available (correct including-tax total), falling back to an estimated pricePerNight x nights only when Priceline does not surface a grand total. Dataset schema description updated.
Pagination scaffolding added. url-builder now accepts an offset parameter and main.ts loops through Priceline's listings pages, deduplicating by hotelId across pages, until maxResults is reached, no new hotels appear, totalAvailableHotels is exhausted, or a 20-page safety cap is hit. Known limitation: as of 2026-06-01 Priceline's GraphQL listings query does not expose pagination variables and the SSR layer largely ignores ?offset=N on the listings URL (returns the same first-30 hotels with minor reordering at the boundary, yielding only 1-2 incremental hotels per extra page). The loop scaffolding is kept in place so that this Actor will automatically benefit when Priceline restores SSR-driven pagination. Until then, results remain practically capped at ~30-32 hotels per search regardless of the maxResults input.
Added
hotelId field exposed on every dataset item (stable Priceline identifier, also used internally for cross-page deduplication).
OUTPUT KV record now reports pagesScraped, totalAvailableHotels, reachedCap, uniqueHotelsCollected, resultsReturned, maxResultsRequested for run-level observability.
v0.1.1 (2026-05-18)
Added
## FAQ section with 6 Q&A (login walls, scheduling, currencies, Express Deals, data freshness, blocking)
Changed
actor.json description rewritten to 3 canonical sentences with platform-features close (dropped "ready for analysis or integration" fluff)
README data table and JSON output example now include the grandTotal field
openapi_schema.jsonActorInput: added currency property with the 15-value enum aligned to input_schema.json
openapi_schema.jsonpriceMin/priceMax wording: "in USD" replaced with "in the selected currency (or scraped currency if none selected)"
Date defaults rolled forward: check-in 15/May/2026 → 20/June/2026, check-out 18/May/2026 → 23/June/2026 (input_schema, openapi defaults, openapi example, README data table, README input configuration table)
README L138 "pay-per-result" → "Pay per result" (canonical Apify nomenclature)
README L163 (EUR, GBP, etc.) → (EUR, GBP, and more)
Removed etc. from openapi currency description
Removed all em-dashes (zero em-dash policy 2026-05-18)
v0.1 (earlier)
Initial Store release. Pay-per-result pricing at $0.005 per hotel result. ZenRows-based extraction with destination + date + filter inputs, Apollo SSR JSON parsing, currency conversion across 15 currencies.