Scrape used-car listings from Njuškalo.hr — Croatia's #1 classifieds marketplace. Get make, model, year, price in EUR, mileage, fuel type, gearbox, engine power (kW + hp), first registration, seller type (dealer/private), location, photos and the full Croatian description. Export to JSON or CSV.
Fix: 80% customer success rate (7/36 30-day FAILED) traced to
_Fetcher._attempt catching every curl_cffi transport error
((OSError, RuntimeError) — timeout, connection reset, DNS failure,
proxy error, since curl_cffi.curl.CurlError subclasses OSError) and
returning (None, blocked=False), which skipped the retry loop
entirely. A single flaky-proxy hiccup on page 1 of the search — a
realistic event on a residential exit — was returned immediately with
zero retries; _iter_listings then saw no listings on page 1 and
raised BlockedError, failing the WHOLE run even though the target
never actually blocked anything. This also silently violated the
documented retry contract ("retry on 408/429/503 and network errors" —
DEVILSCRAPES-CONTEXT.md § Anti-blocking stack), which the code's own
docstring claimed to follow but did not implement for the network-error
branch.
Fix: transport errors, HTTP 403/408/429/503, and CAPTCHA challenges now
all retry with backoff + a fresh proxy session (RETRYABLE_STATUS_CODES
now includes 408; network errors return retry=True instead of
blocked=False). Only a genuine non-retryable HTTP status (e.g.
404/410 — listing gone) returns a soft None without exhausting
retries.
Verified live build has been deployed unchanged for the full 30-day
failure window (taggedBuilds.latest finished 2026-06-07, 80 days
before this fix) — the failures are on the shipped code, not a stale
measurement window.
Verified proxy-exit-per-attempt rotation (ProxyFactory /
new_proxy_url) was already correct on this Actor — NOT the
sticky-session bug fixed on standvirtual-portugal-cars. Confirmed via
code read: main.py::_build_proxy_factory mints a fresh session_id
per call and scraper.py::_Fetcher._rotate calls it on every retry.
Verified geo (random-country vs country-HR residential exit) is NOT
currently a failure cause: 8/8 random-country + 1/1 HR-pinned probe
requests against https://www.njuskalo.hr/auti all returned 200 with
a parseable __INITIAL_STATE__ — no geo-splash reproduced. Left the
proxy country unpinned; revisit if evidence emerges.
Added tests/test_network_retry.py — regression test that fails on
the old code (network error -> zero retries, immediate None/no
BlockedError) and passes once transport errors are retried like any
other block signal.
0.1 — 2026-06-05
First cloud publish. Verified SUCCEEDED on Apify platform (runId=MfJ5fQjSOGELa6wCi).