# Changelog of Polovni Automobili Car Scraper (`devilscrapes/polovniautomobili-serbia-cars`) Actor

- **URL**: https://apify.com/devilscrapes/polovniautomobili-serbia-cars/changelog.md
- **Full Actor documentation**: https://apify.com/devilscrapes/polovniautomobili-serbia-cars.md

## Polovni Automobili Car Scraper — Changelog

### 0.2.1 — 2026-07-28 — brought into `main`, RESIDENTIAL proxy guard re-verified

- **Fixer pass** triggered by daily-report flag: 30-day customer success
  rate 20% (8/10 runs failed), all failures on the *customer* side —
  our own `/acts/{id}/runs` history showed zero recent failures, so the
  break was invisible from this repo's `main` because **the Actor's
  source had never been committed to `main`** — it only existed as
  deployed Apify source plus two stale, never-merged branches
  (`fix/polovniautomobili-serbia-cars-residential-guard`,
  `fix/polovniautomobili-serbia-cars-nextjs-markup`). The live 403s match
  the same class of bug already fixed once (2026-06-10, see 0.1.1) —
  datacenter proxy exit IPs get blocked by polovniautomobili.com.
- Verified the `nextjs-markup` branch tree (the more complete of the
  two, already carrying the 0.1.1 RESIDENTIAL-proxy guard **and** the
  Next.js `__NEXT_DATA__` parser rewrite) and ported it into `main` as
  the Actor's first-ever committed source tree here, so future breaks
  are diagnosable and testable in-repo instead of only on Apify.
- Re-confirmed `_resolve_proxy_url` in `src/main.py` always injects
  `groups=["RESIDENTIAL"]` when `apifyProxyGroups` is absent/empty, and
  that `.actor/input_schema.json` + `tests/fixtures/input.qa.json`
  both default/prefill `apifyProxyGroups: ["RESIDENTIAL"]` so a
  customer accepting defaults never lands on a datacenter exit IP.
- Local gates green: `pytest` (21/21), `ruff check` clean,
  `scripts/verify_input_prefill.py` OK. No functional code change beyond
  the version bump — this release exists to put the already-fixed,
  already-QA'd source under version control on `main`.

### 0.2.0 — 2026-07-10 — pushed as build 0.3.1

- Cloud build SUCCEEDED (buildId: KM8KY7ND5M8ioyuAN, actorId: wIv3c5VLteB2wvfOr).

- Cloud QA PASS: runId=cEwa4b15p9SkIHtoG, 3/3 rows returned with make,
  model, year, price (EUR), mileage, fuel, transmission, engine power,
  color, registration, seller, region, photos, description. RESIDENTIAL
  proxy resolved and consumed (0.0017 GB). PPE charges fired
  (actor-start=1, result-row=3). Dataset preview URLs (JSON + CSV) both
  200\. Maintenance badge cleared.

- **Root-cause fix for the 30-day 13% customer success rate.**
  polovniautomobili.com rewrote its frontend to Next.js; the old
  server-rendered `<article data-classifiedid>` markup, per-ad `@type=Car`
  LD-JSON, `dataLayer.push({...})` and the `uk-grid` spec table are all
  gone. `search_cars()`/`search_prices()` returned zero rows on every
  run, which the scraper correctly treated as fail-loud
  (`RuntimeError: No listings parsed`) — hence the near-total run
  failure rate.

- Rewrote `src/parser.py` and `src/scraper.py` to read the site's
  `<script id="__NEXT_DATA__">` JSON blob instead: `searchResults.results`
  on the list page (make/model/year/price/mileage/fuel/gearbox/power in
  one shot) and `productData` + `contactDetails` on the detail page
  (full spec sheet, seller, description, photo gallery).

- Photo CDN also moved `gcdn.polovniautomobili.com` ->
  `cdn.polovniautomobili.com`; the new parser builds full-size URLs
  from `productData.images[].fileName` instead of a hostname-pinned
  regex, so it no longer breaks on CDN subdomain drift.

- Dropped the now-unused `selectolax` dependency (no more HTML-tree
  parsing — everything comes from structured JSON).

- Adds `tests/fixtures/{search,detail}.html` (captured live 2026-07-10)
  and `tests/test_parser.py` — locks in the new `__NEXT_DATA__` parsing
  against real markup so a future regression fails loud in CI, not in
  production.

- Adds `tests/fixtures/input.qa.json` — mandatory cloud-QA fixture,
  missing from the previous release.

- Bumps to 0.2.0 (pyproject.toml + actor.json) to mark the breaking
  parser rewrite.

### 0.1.1 — 2026-06-10

- Force-RESIDENTIAL proxy guard: \_resolve_proxy_url now always injects
  groups=\["RESIDENTIAL"] if apifyProxyGroups is absent, empty, or lacks
  RESIDENTIAL, preventing silent fallback to datacenter IPs that 403 on
  polovniautomobili.com.
- Fail fast with RuntimeError (actionable message) instead of returning
  None when proxy is unavailable (plan restriction) or useApifyProxy=False.
- Preserves apifyProxyCountry from spec.
- Adds tests/test_proxy_guard.py: 12 tests covering \_ensure_residential
  (pure) and \_resolve_proxy_url (async monkeypatched) paths.

### 0.1.0 — 2026-06-02

- Initial release to Apify Store.
- Cloud build SUCCEEDED (buildId: rmZ55jIQBxgvOZgfE, actorId: wIv3c5VLteB2wvfOr).
- Cloud QA PASS: 5/5 rows returned with make, model, year, price (EUR), mileage, transmission, location, photos.
- RESIDENTIAL proxy resolved on FREE tier (243 KB residential traffic consumed).
- PPE pricing tier activation required in Console (charges ignored until tier is set).
