Scrape structured property listings from Fotocasa.es, Spain's #1 real-estate portal — price, rooms, bathrooms, surface m², full address, coordinates, features, description, and photo URLs. Per-listing fault isolation keeps one bad record from failing the run. Export to JSON or CSV.
src/parser.py — pure extract/map/filter/URL-build. extract_initial_props
never raises; listings_and_count raises FotocasaParseError only when
the initialSearch.result path itself is absent (REQ-3), distinct from a
genuinely-empty realEstates=[]/count=0 payload (REQ-2).
build_result_row maps every spec field (nested features[key=...]
lookup, flattened address, image-type-filtered multimedia, detail_url
prefixing) and raises pydantic.ValidationError on a malformed listing
(REQ-4). passes_price_bounds (REQ-7), build_search_url (REQ-10).
src/scraper.py — curl-cffi AsyncSession, fresh proxy session_id
per attempt, exponential backoff on 408/429/503/network errors only
(REQ-8), impersonation rotation chrome131 → firefox133 after 2
consecutive non-retryable failures (REQ-12), pagination bounded by
count/len(realEstates) and max_pages/max_results (REQ-5/6).
src/main.py — input validated before the actor-start PPE charge;
batched push_data/result-scraped charge every 25 rows; REQ-3 dumps
raw HTML to the KVS (UNPARSEABLE_PAGE_HTML) before re-raising to a
non-zero exit; REQ-2 finishes SUCCEEDED with 0 rows and a status
message naming the search parameters.
Live-probe bug fix: reducedPrice arrives as a formatted string
(e.g. "75.000 €"), not the raw int spec.md's one confirmed-null
example implied — _parse_eur_amount strips non-digits so discounted
listings no longer fail validation and get silently dropped.
82 tests (models/parser/scraper/main), ruff check and pyright both
clean. Local apify run against the live site returns real,
fully-shaped rows.
Detail-page fetches remain out of scope for v1 (spec.md "Out of Scope") —
the search-results page already carries price, address, coordinates,
features, description, and photo URLs per listing.
0.0 — 2026-09-25
Actor scaffolded: .actor/ config (actor.json, input_schema.json,
dataset_schema.json, output_schema.json, pay_per_event.json), pyproject.toml,
Dockerfile, README skeleton with real PPE pricing, promo.json, and a
boot-only src/main.py stub. No scraping logic yet — see
docs/specs/fotocasa-spain-property-scraper/tasks.md (T02–T06) for the
models/parser/scraper/main.py implementation work that follows.