# Changelog of Priceline hotel reviews scraper: guest scores (`mrbridge/priceline-hotel-reviews-scraper`) Actor

- **URL**: https://apify.com/mrbridge/priceline-hotel-reviews-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/mrbridge/priceline-hotel-reviews-scraper.md

## Changelog

All notable changes to **Priceline Hotel Reviews Scraper** are documented here.

### v0.8.2 (2026-09-26)

#### Changed

- The Ratings Breakdown view no longer shows the Quality column, which had been empty since Priceline stopped sending an Overall entry in September 2026. `ratings.categories.overall` stays in every row as `null`, and `ratings.overallScore` still carries the overall guest score, shown in the same view.
- `unprocessedCount` and `timeBudgetReached` are now declared in the key-value store schema, each with a description of what it counts.

#### Docs

- The README speed figures follow the 0.8.1 measurements: the default 10-hotel run finishes in about a minute.

### v0.8.1 (2026-09-25)

#### Fixed (speed, Apify QA timeout)

- Each request to Priceline now takes 3 to 7 seconds instead of 30 seconds to 2 minutes. Since September 2026 the proxy's automatic mode took that long for every request, so the default run (New York, 10 hotels, 100 reviews each) no longer finished within the 5 minutes of Apify's daily quality test, and the Actor was marked "Under maintenance". The Actor now sends a plain request and keeps the automatic mode for the last retry only. Measured on 2026-09-24: 6 of 6 plain requests passed (search page, hotel details and reviews, twice each) at 3 to 7 seconds, at the same proxy cost per request as before.
- Hotels are processed three at a time. Rows still come out in the order of the search results.
- When the run timeout gets close, the Actor stops starting new hotels, pushes the rows it has and ends SUCCEEDED. The status message says how many hotels were left out, and the `OUTPUT` record gains `unprocessedCount` and `timeBudgetReached`. Hotels left out are not charged.
- A 403 from Priceline is now retried like a proxy block instead of failing the hotel on the first attempt.

#### Fixed (null instead of false values)

- `ratings.categories.overall` is now `null` instead of a false 0 when Priceline sends no Overall score. Priceline stopped sending it in September 2026, so the field is `null` on every hotel for now. The overall guest score is still in `ratings.overallScore`.
- `ratings.ratingLabel` is `null` instead of "Poor" when a hotel has no guest rating.
- `descriptionLanguage` no longer labels English text as French or Spanish because a hotel name contains "de" or "la". It is `null` when the description is too short or mixes languages, and it now also recognises Portuguese, Italian, Dutch and Japanese. On 13 live descriptions checked, 13 are now labelled correctly, against 7 before.
- Review `language` uses the same detector. A short review it cannot decide on keeps the `en` default. A longer review that stays ambiguous is `null`, and so is a short review written in a non-Latin script.
- `description` and `descriptionLanguage` accept `null` in the dataset schema, so a row with no description or an undecided language is stored instead of refused.

#### Changed

- `ACTOR_VERSION` and `dataVersion` are now `0.8.0`, the `actor.json` version is `0.8`, and `package.json` is synced.

### 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.

#### Changed

- `ACTOR_VERSION`/`dataVersion` → `0.7.0`; `actor.json` `version` → `0.7`; `package.json` synced. No dataset/output-shape change.

***

### v0.6.2 (2026-06-25)

#### Fixed (Apify QA timeout)

- 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`.
- `ACTOR_VERSION`/`dataVersion` → `0.6.1`; `package.json` synced.

***

### v0.6.0 (2026-06-22)

#### Added (coverage: audit Lot C)

- 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.

#### Changed

- `ACTOR_VERSION`/`dataVersion` → `0.6.0`; `actor.json` `version` → `0.6`; `package.json` synced.

***

### v0.5.7 (2026-06-22)

#### Changed

- **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.

***

### v0.5.5 (2026-06-19)

#### Fixed (resilience: audit 2026-06-01 SEV-3 / PR-C1)

- 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`.

#### Fixed (consistency: audit 2026-06-01 SEV-2 / 2026-06-12 H4 / PR-C2)

- 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

- `ACTOR_VERSION`/`dataVersion` → `0.5.5`; `package.json` synced.

***

### v0.5.4 (2026-06-02)

#### 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.totalReviewCount` `min(1)→min(0)` (a new hotel with a complete profile and zero reviews is valid, chargeable data) and review `overallRating` `min(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.json` `version: "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.
- `roomTypes`, `nearbyPOIs`, `mandatoryFees` remain empty placeholders; they require additional GraphQL queries (deferred).

### v0.4.2 (2026-05-28)

#### Fixed

- 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.
