Scrape hotel reviews from Priceline: review text, pros/cons, reviewer info, and individual ratings. Get guest scores, category ratings, GPS coordinates, and neighborhood data for 107 cities or specific URLs. Export data, run via API, schedule runs, or integrate with other tools.
All notable changes to Priceline Hotel Reviews Scraper are documented here.
v0.7.1 (2026-06-29)
Changed (input form UX)
Restructured the input form: Input Type and its four mode inputs (City, Country, Hotel URLs, Hotel IDs) are now grouped together at the top; the maxHotels/minStars/minScore filters moved to a dedicated Search filters section.
City and Hotel URLs no longer carry a prefilled value — the dropdowns/lists open empty (a "Select…" placeholder) so you actively pick what to scrape. (Country keeps its "United States" suggestion.)
City Search now falls back to New York when run without a city selected, so the default run (City is the default mode) still produces data and exits SUCCEEDED instead of erroring.
Changed
ACTOR_VERSION/dataVersion → 0.7.1; package.json synced. No dataset/output-shape change.
v0.7.0 (2026-06-26)
Added (Country Search)
New Country Search input mode (inputType: "country"). Pick a country from a 49-country dropdown (searchCountry) and the scraper pulls hotels from that country's vetted cities, merges them (deduplicated by hotel ID), and applies the same minStars/minScore/maxHotels filters as City Search. A country is a static group of the existing 107 vetted cities (e.g. France → Paris, Nice, Lyon; United States → its 25 mapped cities), so coverage is reliable. Priceline has no country-level hotel geo, so a dynamic autocomplete approach was evaluated and rejected as too fragile (keyword-match false positives); the static mapping mirrors how City Search already works.
Lowered the default maxHotels from 100 to 10. Apify's automated quality tests run the prefilled City Search input and cap each run at 5 minutes; processing 100 hotels sequentially took ~13 minutes and timed out, which flagged the Actor "Under maintenance". The default 10-hotel city pull now finishes in about two to three minutes, well inside the limit. Set maxHotels higher (up to 500) to scrape a full city. Reviews per hotel are unchanged.
Docs
Corrected the per-hotel speed figures in the README (now ~5–20 seconds per hotel depending on review volume; ~2–3 minutes for 10 hotels), replacing the earlier 3–5 second estimate that omitted the per-hotel reviews page.
Changed
ACTOR_VERSION/dataVersion → 0.6.2; package.json synced. No dataset/output-shape change.
v0.6.1 (2026-06-22)
Changed (run-summary accuracy)
The OUTPUT run summary now reports a dedicated incompleteCount, and errorCount no longer counts _incomplete rows. Previously a hotel pushed as _incomplete (degraded data — validation failed or reviews unrecoverable, never charged) was lumped into errorCount, overstating failures for a run whose profiles were otherwise usable. errorCount now counts only genuine fetch/extraction errors. Dataset row shape is unchanged.
Internal
Removed stale "Phase 1/Phase 2" planning comments from types.ts.
New ratings.travelerTypes field: review counts by guest segment (Traveling as Couple, Family with Young Children, Group, Solo, Business, Senior, …), mapped from Priceline's reviewRatingSummary. No extra network cost — the data was already fetched per hotel and previously discarded. Priceline's "Total" row is excluded (it counts rating responses, not text reviews, and its sum differs from totalReviewCount).
Removed (BREAKING CHANGE — dead placeholder fields)
Dropped ratings.distribution and ratings.aiSummary. Both were always null: a verification spike (2026-06-22) confirmed Priceline's API exposes neither a rating histogram nor an AI summary. Downstream consumers referencing these columns should remove them; the values were never populated.
City Search is now the default input mode (was Hotel URLs). The input form opens on a city dropdown (prefilled with New York) and surfaces the star/score filters first, so a first run works with no configuration. URLs and IDs remain available via the Input Type selector.
Removed
Discovery Mode debug option removed from the input schema and codebase, along with its raw-response / search-HTML Key-Value Store collections. It only dumped debugging artifacts and was not useful in production.
Docs
README reworked for clarity and depth: added API integration code examples (cURL, Node.js, Python), scheduled-runs and no-code integration guidance, Performance and Tips sections, and a concrete two-paragraph intro. Coherence fixes throughout.
ACTOR_VERSION/dataVersion → 0.5.7; package.json synced. No dataset/output-shape change.
v0.5.6 (2026-06-19)
Fixed (resilience / error taxonomy — audit Lot B)
ZenRows 401/402 (bad key / out of credits) now abort the run early with a structured ConfigError record and exit SUCCEEDED, instead of being treated as a success body that produced per-hotel parse noise across every hotel.
Reviews that are attempted but unrecoverable (soft-block / exhausted retries) no longer bill as a genuine empty: the hotel row is pushed _incomplete and not charged. A hotel that genuinely has zero reviews is still a billable extraction.
City search now reports ParserDrift when a fetched page parses to zero hotels (a soft-block / drift) instead of the misleading "no hotels found" — Priceline city IDs are audited/valid, so zero parsed is never a genuine empty city.
Invalid input dates (broken format, or check-out on/before check-in) now warn and fall back to the auto-generated defaults rather than passing malformed values to the API.
Changed
ACTOR_VERSION/dataVersion → 0.5.6; package.json synced. No dataset/output-shape change.
ZenRows HTTP 422 on the GraphQL endpoint (a transient proxy/session block on this PerimeterX-protected target) now retries with backoff like 5xx instead of failing the hotel on the first attempt. Removed the never-wired alternativeUrl fallback that left 422 as a dead-end null.
Review pagination now deduplicates by server reviewId across pages. Overlapping pages (common with sort=null while new reviews arrive mid-walk) previously inflated review counts and distorted the pagination stop condition. Reviews without a reviewId are always kept (cannot be safely deduped).
Fixed (charge correctness — audit 2026-06-12)
Input hotel IDs are deduplicated before processing, preserving first-occurrence order. Passing the same hotel twice (duplicate IDs, or URLs differing only by query string) previously fetched and charged it twice.
Removed (cleanup — audit 2026-06-12)
Deleted the dead GRAPHQL_HEADERS constant. ZenRows only forwards caller headers with custom_headers=true (never set); the constant had zero references.
Changed (SEV-2 schema honesty — removed dead placeholder fields)
Removed 5 fields that were hardcoded null in the mapper and never present in the getHotelDetails response: chain, brand, propertyType, yearBuilt, yearRenovated. They promised data the API does not provide. (Hotel brand/type exist in Priceline's listings query, not details — a future coverage sprint could probe adding them.) ACTOR_VERSION/dataVersion → 0.5.4. Audit 2026-06-01 finding X-3.
v0.5.3 (2026-06-01)
Fixed (SEV-2 revenue leak — valid hotels were not charged)
Relaxed two over-strict zod constraints that marked complete hotels _incomplete (pushed but NOT charged): ratings.totalReviewCountmin(1)→min(0) (a new hotel with a complete profile and zero reviews is valid, chargeable data) and review overallRatingmin(1)→min(0) (a single review lacking an "overall" score entry — mapped to 0 — no longer fails the whole hotel). A 99-good-review hotel with one partial review was billing $0. Audit 2026-06-01 finding. ACTOR_VERSION/dataVersion bumped to 0.5.3.
v0.5.0 (2026-06-01)
Removed (BREAKING CHANGE - SEV-3 audit)
roomTypes, nearbyPOIs, mandatoryFees fields dropped from output. These were declared in the HotelDetail type and always shipped as empty arrays ([] as never[]), advertised internally as "Phase 3+" deliverables that were never implemented. Rather than continue to expose placeholder columns, the fields are removed entirely. Each of them requires a separate GraphQL query against Priceline; if/when implemented, they will be added back behind a feature flag with real data.
Downstream consumers that referenced roomTypes, nearbyPOIs or mandatoryFees on output rows must update their parsers; the columns will no longer appear in JSON / CSV / Excel exports.
Changed
ACTOR_VERSION bumped to 0.5.0; actor.jsonversion: "0.5"; package.json synced. dataVersion on every output row now reads "0.5.0".
v0.4.5 (2026-06-01)
Fixed (SEV-4 review coverage)
Reviews pagination implemented. The actor previously fetched only the first page of reviews (max 100) regardless of maxReviewsPerHotel, and the documented value 0 (advertised as "all available reviews") returned 0 due to a guard inversion. Now: 0 means "fetch all up to a 1000-review safety cap"; any positive value loops through pages of 100 until the target is reached or totalPages is exhausted.
New reviewsTruncated: true flag on hotel rows where the actual reviews count is less than totalReviewCount (clear signal for downstream consumers that there are more reviews than fetched).
Cost impact: each additional review page = 1 ZenRows call. With Pullman Paris (2748 reviews), maxReviewsPerHotel=0 would issue ~10 ZenRows calls per hotel.
v0.4.4 (2026-05-31)
Fixed
reviews field now populates with individual guest reviews. The legacy hotelDetails.guestReviews field is permanently null in Priceline's API; the scraper now calls the dedicated getReviewsByHotelIds GraphQL operation (same endpoint, same proxy) that powers Priceline's "Show All Reviews" modal. Each review carries text, pros, cons, overallRating, reviewer.{name, location, travelerType}, reviewDate, and a server-side reviewId when provided.
Added
HOTEL_REVIEWS_QUERY constant in src/constants.ts — the reverse-engineered getReviewsByHotelIds operation (verbatim from assets.pclncdn.com/web/relax/.../RELAX.main.*.js).
fetchHotelReviews(proxy, hotelId, { limit, offset }) in src/priceline-api.ts — limit capped at 100 to avoid oversized responses; uses 1-indexed offsets (matches the bundle defaults).
mapReviewsV2(rawReviews) in src/parsers/graphql-mapper.ts — maps the new GraphQL response shape (scores[], reviewerInfo, entryDate) onto the existing HotelReview interface; preserves server-side reviewId and falls back to text-hash when absent.
Changed
main.ts reviews step decoupled from fetchHotelDetails: after profile mapping, it calls fetchHotelReviews (best-effort, never fails the hotel) and only when maxReviewsPerHotel > 0. PPE charging is unchanged: still a single Actor.pushData(hotel, 'hotel-extracted') per hotel.
Legacy mapReviews is retained for backwards compatibility but no longer called from main.ts.
ACTOR_VERSION bumped to 0.4.4; package.json + package-lock.json synced.
v0.4.3 (2026-05-29)
Added
amenities: array of structured amenity objects (code, name, type, free, displayable) mapped from hotelFeatures.hotelAmenities. Was previously hardcoded [].
amenitiesFlat: array of highlighted amenity codes mapped from hotelFeatures.highlightedAmenities. Was previously hardcoded [].
highlights: array of feature strings mapped from hotelFeatures.features (e.g., "Daily disinfection", "Family rooms"). Was previously hardcoded [].
photos: array of photo objects with thumbnail and full URLs mapped from hotel.images. Was previously hardcoded [].
policies: object with checkInTime, checkOutTime, petDescription, childrenDescription, importantInfo[] mapped from hotel.policies. Was previously hardcoded null.
Changed
Pure mapper enhancement: no changes to the GraphQL query (already requested these fields), the proxy client, or the schema validator (.passthrough() keeps backward compatibility). The fields were already arriving in the API response and were being silently dropped during mapping.
dataVersion bumped from 0.4.0 to 0.4.3 to reflect the enriched payload.
Known issues
reviews returns [] because Priceline's hotelDetails GraphQL endpoint now returns guestReviews: null. Recovering individual reviews requires reverse-engineering a separate reviews endpoint and is out of scope for this release.
Spending-limit log message no longer prints the raw chargedCount value. Apify SDK v3.7.2 doubles chargedCount when pushing to the default dataset with an explicit event (auto-schedules both hotel-extracted and synthetic apify-default-dataset-item), which made the log read chargedCount=2 and suggest double-billing. Only the explicit hotel-extracted event is billed by the Platform; the synthetic dataset event is local-only tracking. No revenue impact, only a cosmetic log fix.
v0.4.1 (2026-05-18)
Changed
Pricing section: fabricated "$49/month Scale plan" replaced with real "$29/month Starter plan" ($29 credits, ~5,800 hotels per month)
Terminology unified: "actor" replaced with "scraper" across body text, cross-promo table header ("Best actor" → "Best scraper"), REST API description, error-handling FAQ, and integrations heading
SEO title format aligned with portfolio (pipe separator)
Removed all em-dashes (zero em-dash policy 2026-05-18)
Removed etc. from scheduling FAQ
v0.4 (earlier)
Initial Store release. Pay-per-event pricing at $0.005 per hotel extracted. GraphQL-based extraction with city search, hotel URL, and hotel ID input modes.