# Changelog of Priceline hotel scraper: prices, ratings, deals (`mrbridge/priceline-hotel-scraper`) Actor

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

## Changelog

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

### v0.4.4 (2026-09-26) - one currency, and no more stays in the past

#### Fixed

- **A stay in the past is refused instead of billed.** Priceline answers a past stay with today's offer, and that offer was delivered and billed under the dates you asked for. A check-in before yesterday (UTC) now stops the run before any paid request, nothing is charged, and the reason is in the `run-error` record. A check-out on or before the check-in, an incomplete date and a day the month does not have are refused the same way.
- The same search could come back in a different currency from one run to the next, because each request reached Priceline through a connection in a varying country. Every request now goes through a US connection, so prices normally arrive in US dollars.
- When a run met more than one currency, later rows were converted with the first currency's rate and labelled with the wrong original currency. Each hotel is now converted with the rate of its own currency, and `originalCurrency` names the currency Priceline actually returned for it.
- A failed exchange-rate lookup used to deliver the prices unconverted, in a currency you had not asked for. If you set a currency and no rate is available, the run now fails before delivering any hotel and writes the reason to the `run-error` record. Nothing is billed.

#### Changed

- **Dates are optional.** With no dates, the Actor searches a stay starting 30 days from today, for 3 nights, instead of the fixed June 2026 dates it used to fill in, which had already passed. With a check-in and no check-out, the stay lasts 3 nights.

#### Added

- The run summary (`OUTPUT`) reports `scrapedCurrency` and `scrapedCurrencies`, the currency or currencies Priceline returned during the run.

### 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.json` `ActorInput`: added `currency` property with the 15-value enum aligned to `input_schema.json`
- `openapi_schema.json` `HotelResult`: added `originalCurrency`, `exchangeRate`, `grandTotal` properties
- `openapi_schema.json` `priceMin`/`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.
