# Changelog of SeLoger Scraper 🇫🇷 Pige : Tél. Vendeur, Particulier/Agence (`tagadanar/french-real-estate-seloger`) Actor

- **URL**: https://apify.com/tagadanar/french-real-estate-seloger/changelog.md
- **Full Actor documentation**: https://apify.com/tagadanar/french-real-estate-seloger.md

## Changelog

### 2026-09-19

- **Every run now ends by saying what it delivered, what it was asked for and,
  when it returns little or nothing, why.** A run that returns no listings now
  tells you whether SeLoger's bot protection refused the pages, whether the
  search genuinely has no ads (on SeLoger's own result counter), or whether the
  pages came back but could not be read — and a run cut short now names the
  cause in plain words, including how many of your search URLs were never run
  and what the browser fallback being used up means for them.

### 2026-09-02

- The run now ends with a status message in the Console (the "Done" line was log-only, so a run could not explain itself in the Store).

### 2026-08-09 (pre-push review fixes)

- **A network blip no longer fails the whole run.** Three consecutive transport
  errors on one search URL (proxy connect / ECONNRESET — routine on residential)
  used to throw out of `crawlSearchUrl`, which runs at module top level with no
  try/catch: the run exited FAILED, the remaining search URLs were never crawled,
  the browser fallback was never reached and `Actor.exit()` never ran. The fetch
  layer now reports a dead rung as walled so the ladder escalates, and every
  per-URL crawl is additionally wrapped so a URL that breaks is routed to the
  browser rung instead of killing the run.
- **The browser fallback is now budgeted.** It may serve at most **20 %** of a
  run's billed listings — the Chromium rung costs $1.214 per 1,000 listings
  against $0.0879 for the HTTP rungs, so an unbounded fallback is a loss on every
  row it serves. The derivation lives next to the constant in
  `src/browser-budget.js` and is coupled to the list price: change the price and
  the cap has to be recomputed. When the budget is reached the actor stops
  escalating, ships what it collected and says so in the run's status message.
  A run in which *every* URL walls gets a budget of zero, deliberately.
- `maxMemoryMbytes` set to **2048**, matching the actor's current live default —
  the cap clamps live runs the moment it is pushed, and dropping a running actor
  from 2048 to 1024 in the same push that adds a Chromium fallback rung risks an
  OOM, which is a FAILED run and costs more than the memory saves.
- **Tests:** the BFF pagination tests are no longer tautological. They now run
  against two live fixtures captured together from a datacenter IP — a real
  bootstrap page that actually contains the `engine.searchEngine.christieModel`
  block (the old fixture had no `engine` key at all, so the extraction the whole
  new pagination depends on was asserting `null === null`) and a real
  `POST /search-bff/api/externaldata` response. The API-vs-browser equivalence
  check is now a genuine cross-source diff of the same 25 listings.

### 2026-08-09 (later) — Playwright → impit HTTP, and the pagination trap behind it

- **No more browser on the normal path.** `src/fetch.js` fetches the search page
  with impit (browser-grade TLS fingerprint) and pages through SeLoger's own BFF
  endpoint. Measured the same day from two vantage points: `list.htm` answers
  HTTP 200 with the full `window["initialData"]` payload from a FR residential IP
  **and** from a datacenter IP (AS16276), on every impit fingerprint tried.
- **Found and fixed a trap that would have silently broken pagination.**
  `LISTING-LISTpg=N` is a client-side-only param — the server ignores it and
  returns page 1 every time (p1/p2/p3 = the same 25 ids). Real pagination is
  `POST /search-bff/api/externaldata?from=<record offset>&size=50`, with the
  search model lifted from the bootstrap HTML. `size` caps at 50; 60 and above
  return HTTP 400.
- **Output is unchanged, field for field.** The BFF returns the same listing
  objects under a different envelope; the only difference was the photo URL
  prefix, which `selectApiListings()` now strips exactly as SeLoger's own bundle
  does. A test asserts API-sourced and browser-sourced records normalize
  identically.
- **Proxy ladder** (`datacenter → residential-FR → Chromium + residential-FR`),
  with every rung's availability logged and no silent `catch` around
  `createProxyConfiguration`. The old browser path survives verbatim in
  `src/browser.js` as the last rung, so a closing wall degrades instead of
  breaking. Progress survives an escalation: a wall at offset 900 resumes at 900.
- **Guardrails:** `maxMemoryMbytes: 1024` added, default run memory 2048 → 512,
  Dockerfile no longer passes `--omit=optional` (it would strip impit's native
  binary).
- Projected cost per 1,000 listings: **$0.095** on residential, **$0.006** on
  datacenter, against $0.880 measured on the browser path. Modelled from
  measured page weights — one platform run still has to confirm it.

### 2026-08-09

- Docs only, not pushed: README Pricing section now states the actual all-in
  cost range (~$2.4–$2.6/1,000 listings incl. platform usage) instead of
  leaving platform usage unquantified, so buyers see the real total before
  running. No code, price, or title change — see
  `research/we-pay-review-2026-08-09/french-real-estate-seloger.md` for the
  full we-pay/positioning review (verdict: stays user-pays, 1.2× margin vs the
  3× flip bar).

### 2026-07-12

- Invalid input no longer fails the run. The run now finishes cleanly with a clear explanation in its status message, so a typo or an empty list is easy to spot and fix.
- List inputs now accept datasets piped straight from another scraper: entries
  that are objects are read through their usual key names (url/link,
  query/keyword/title, name/company and similar) instead of being rejected.

### 2026-07-11

- Billing: on paid runs every row is billed before it is written, and a run
  now stops visibly if billing fails instead of continuing unbilled.

### 2026-07-09

- Input is now tolerant of common shapes that previously failed the run: a bare
  string / bare object / delimited string for searchUrls is coerced into a list
  instead of erroring. Empty input still errors.

### 2026-07-06

- Simplified input: pagination depth is now handled automatically (technical field removed).

### 2026-07-05

- Published on Apify Store.

### 2026-07-04

- Initial release.
- Up to 2,500 SeLoger listings per search with the seller's PHONE NUMBER.
- Private-seller vs agency flag, €/m², surface, rooms, DPE and features.
- Paste any SeLoger search URL — pagination handled automatically.
