# Changelog of Realtor.ca Listings Scraper (`devilscrapes/realtor-ca-listings-scraper`) Actor

- **URL**: https://apify.com/devilscrapes/realtor-ca-listings-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/devilscrapes/realtor-ca-listings-scraper.md

## Realtor.ca Listings Scraper — Changelog

### 0.2 — 2026-09-24 (cloud QA fix)

- First cloud run crashed before any request: `ProxyConfiguration.new_url()`
  is a coroutine and was never awaited, so curl-cffi got a coroutine as its
  proxy URL. Now awaited, passed via `proxy=`, and `None` when proxy is off.
  The test fake was sync, which is why 81 local tests stayed green.

### 0.2 — 2026-09-24

Pre-cloud-QA publish-gate fixes:

- `actor-start` raised $0.02 -> **$0.20**, the fleet standard
  (`scripts/publish_actors.py`'s `_pricing_manifest_gate` refuses anything
  else; caught this build on the stepstone/WTTJ pattern, commit `dc73208b`).
  Per-1,000 headline moves $3.00 -> **$3.20**; `result` stays $0.003/row.
- Proxy default AND prefill changed from bare `{"useApifyProxy": true}`
  (resolves to DATACENTER — `scripts/verify_proxy_default.py`) to
  `apifyProxyGroups: ["RESIDENTIAL"], apifyProxyCountry: "CA"`. The local
  recon that showed a fingerprint-only Cloudflare block (clears on
  firefox/safari impersonation from a residential/office IP) does not prove
  the same holds from Apify's own datacenter egress — `truth-social-scraper`
  died on exactly that gap the same day. `src/models.py`'s `ActorInput`
  default now matches so a programmatic caller and a Console user get the
  same proxy.
- New hollow-run guard in `main.py`: if a page reports `TotalRecords > 0`
  (the server's own positive signal there WAS data) but every record fails
  to parse, `emitted` stays `0` and the run now fails loud (`SystemExit(1)`)
  instead of reading SUCCEEDED with an empty dataset. This is distinct from
  REQ-5's genuine-empty-search case (`TotalRecords == 0`), which still
  succeeds with zero rows per the org's EMPTY-IS-NOT-A-FAILURE rule — only a
  search that actually matched something and still delivered nothing now
  fails loud.

### 0.1 — 2026-09-24

Initial build, local-green (T02–T08, T10–T11 per
`docs/specs/realtor-ca-listings-scraper/tasks.md`).

- Live-confirmed `api2.realtor.ca` `PropertySearch_Post` search: form-encoded
  body, mandatory `GET https://www.realtor.ca/` cookie warm-up before every
  `POST` (REQ-2).
- Firefox/Safari-only impersonation (`BROWSER_PROFILES = ("firefox133",
  "firefox147", "safari180", "safari184")`) — no Chrome profile ever used
  against this endpoint; `chrome131` 403s with a Cloudflare block page on
  every observed attempt (REQ-3).
- Fresh-session-per-attempt retry: every attempt, including the first,
  tears down and rebuilds the whole session (new cookies, new
  impersonation profile, new proxy `session_id`) rather than retrying on
  the same connection — empirically required to reliably clear an
  intermittent block during this build's recon (REQ-8).
- `CurrentPage` pagination stopping on whichever comes first: an
  under-filled page, an empty page, or the `maxResults`/`TotalRecords`/
  `MaxRecords=600` budget being reached (REQ-7).
- `price` parsed from `Property.PriceUnformattedValue` (numeric string),
  falling back to stripped `Property.Price` display text, `null` with one
  `WARNING` if both are absent/unparseable (REQ-6).
- `agentEmail` left `null` (with one `WARNING`) when the real API returns a
  `ContactId` reference instead of a plaintext email (REQ-6).
- Dedup by listing `id` across pages within a run (REQ-4/13); PPE
  `actor-start` ($0.02) + `result` ($0.003) charging (REQ-1/11).
- REQ-5 (empty search SUCCEEDS with zero rows) and REQ-10 (zero pages ever
  succeeded FAILS loud) both covered by integration tests against
  `src/main.py`'s orchestration loop.
- QA fixture and Store prefill both use `maxResults=48`, spanning 4 pages
  of the server's real 12-per-page size, so cloud QA and the "Try for
  free" default both exercise pagination.
