# Changelog of Trip.com Hotel Data Scraper with Advanced Filters (`hotels-scrapers/trip-hotel-scraper`) Actor

- **URL**: https://apify.com/hotels-scrapers/trip-hotel-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/hotels-scrapers/trip-hotel-scraper.md

## Changelog

All notable changes to the Trip.com Hotel Scraper actor.

### \[2.0] — 2026-05-03

#### Changed

- **Complete architectural rewrite** — replaced nodriver browser automation with pure HTTP via `curl-cffi` (Chrome 124 TLS / JA4 impersonation).
- **~10× faster, ~10× lower memory**: the listing page is parsed directly from the SSR `window.IBU_HOTEL` JSON payload — no headless browser needed.
- Switched Docker base image from `apify/actor-python-playwright-chrome:3.13` to slim `apify/actor-python:3.13`. Build is now seconds instead of minutes.
- Default memory lowered from `4096 MB` to `1024 MB` (min `512 MB`, max `4096 MB`). Cuts platform cost per run by ~75% with 4× headroom over the working peak (~280 MB RSS).
- URL builder now emits the canonical legacy SSR shape (`checkIn=YYYY-MM-DD`, `lat=0`, `lon=0`, `searchType=CT`) so Trip.com routes us to the SSR-rendered shell instead of the new Next.js streaming page.
- Concurrency model: `bounded_gather` over Apify residential proxies with sticky `session_id`, retry-on-403/429/503, homepage warmup before first search request.

#### Added

- Rich normalized fields on every record: `hotel_id` (int), `lat`/`lng`, full `images` array, `last_booked` social-proof string, discount/promotion labels, review phrases, occupancy, bed types.
- Graceful fallback when "Proxy external access" is not enabled on the Apify account — the scraper logs a warning and continues without proxy.
- Pay-per-event hooks (`actor-start`, `page-start`, `hotel-extracted`, `actor-complete`).

#### Removed

- Dependencies: `nodriver`, `beautifulsoup4`, `lxml`. The actor now ships with only `apify`, `curl-cffi`, and `loguru`.
- Browser-related Dockerfile complexity (Chromium, system deps, large image layers).

#### Known limitations

- Tier A returns the first ~12 hotels per search URL (one SSR page). Pages 2+ require the `/restapi/soa2/34951/fetchHotelList` XHR endpoint and will land in a follow-up Tier B build.

### \[1.2] — 2026-04-25

#### Changed

- **Reordered input form**: structured filters are now **Option A** (the primary path) and the URL list (`filterArgs`) is **Option B** (advanced override). The form opens with sensible prefills (country = `GB`, dates two days out, 2 adults, USD/en-US) so the actor is runnable out of the box.
- README rewritten around the structured-filter flow with a `listFilters` decoding table and the full supported-country list.
- Sample `storage/key_value_stores/default/INPUT.json` switched to a structured-filter example (London / 4–5★ / 8+ rating / breakfast / free cancellation / price asc).

### \[1.1] — 2026-04-24

#### Added

- **Structured filter inputs** — configure searches without building URLs manually. New fields in the Apify input form:
  - Destination: `searchWord`, `cityId`, `countryId`, `country` (51-country ISO-2 selector)
  - Dates: `checkIn`, `checkOut` (date pickers, YYYY-MM-DD)
  - Guests: `adults`, `children`, `childAges`, `rooms`
  - Money: `currency` (34 codes), `locale` (26), `lowPrice`, `highPrice` (`-1` = no cap)
  - Quality: `starRatings` (multi-select 2–5★), `guestRatingMin`, `sortBy`
  - Amenities: `breakfastIncluded`, `freeCancellation`, `payAtHotel`
  - Context: `domestic`, `travelPurpose`
- **Country → cityId/countryId resolution**. Built-in mapping for 51 ISO-2 country codes with verified Trip.com country IDs plus a fallback representative city. Picking a country with no other location input auto-targets that country's main city (e.g. `FR` → Paris `city=192`, `US` → New York `city=633`).
- **HMT handling**. Hong Kong / Macau / Taiwan are exposed as separate options even though Trip.com shares `countryId=1` for them — the scraper emits a `city=`-based URL to avoid mixed Mainland+HMT results.
- **`listFilters` composer**. Star rating, guest rating threshold, breakfast, free cancellation, pay-at-hotel, and sort order are encoded into Trip.com's `listFilters` grammar (`type|value` segments joined by `*`) automatically.
- Typed input validation with clear `Actor.fail(status_message=...)` messages: invalid date format, checkout ≤ checkin, missing location, child-count/ages mismatch, unknown country code.

#### Changed

- **`filterArgs` is now optional**. It still takes precedence when provided, so existing runs are non-breaking. When empty, the scraper builds the URL from the structured fields above.
- Default input no longer prefills a hardcoded Paris URL — the form opens with structured filters ready to fill.
- Canonical URL encoding: dates as `YYYY%2FMM%2FDD`, `listFilters` pipe as `%7C`, segment separator `*` kept literal — matches Trip.com's own URL format.

#### Fixed

- URL builder now skips empty / whitespace-only entries in `filterArgs`.
