Quick answer
We assumed reducedPrice was a raw integer, because in the single listing we'd probed it, the field was null — and null has no type to infer from. A mandated live run against real listings surfaced the truth: when a Fotocasa listing actually carries a discount, reducedPrice is a formatted string, "75.000 €", the exact same shape as the regular price field. Ship the integer assumption and the parser silently skips every discounted listing — no error, no warning, just a quietly incomplete dataset on precisely the rows a buyer cares about most. The Fotocasa Spain Property Scraper parses it as a formatted string, same as price, before converting to reduced_price_eur.
Can you infer a field's type from a null sample?
No — and that's the whole lesson here. A field that reads null in your one probed sample has no observable shape; you cannot know from an absence whether the real value, when it exists, is an integer, a formatted string, or something else entirely. The only fix is running against enough live data that the field actually appears populated at least once. We did — and reducedPrice turned out to share the exact same "75.000 €"-style formatting as the primary price field, not the raw integer a schema guess would produce. reduced_price_eur in our output is legitimately null on listings with no discount; it's only populated, as an integer in euros, on the listings that actually have one.
Does this scraper need a browser to run?
No. Fotocasa.es server-renders each listing's full structured data into a <script id="__initial_props__"> block on the search-results page itself — price, address, coordinates, features, photo URLs, and the description all arrive in that one JSON payload. No separate API calls, no client-side rendering to wait on. We read it with curl-cffi under browser TLS impersonation, no headless browser required, which keeps the run cheap and the dataset arriving fast.
A field that's
nullin your sample carries no type information at all — the only way to learn its real shape is a live run against data where it's actually populated.
What the Actor gives you
One validated row per listing: price in euros (price_eur, plus the human-formatted price_display) and reduced_price_eur when discounted, building_type/building_subtype, rooms, bathrooms, surface_m2, full address (address_province, address_city, address_district, address_neighborhood, address_zip_code), latitude/longitude, dynamic_features, up to ~18 image_urls, the full Spanish-language description, and agency_alias/agency_type/phone when the seller publishes them.
Honest limitations 🚧
Search-results-page fields only in v1 — no per-listing detail-page fetch, since the results page already embeds everything above. Rent (alquiler) URLs are supported best-effort; sale (comprar) is the wire-confirmed path. Price filters apply after fetching, so a narrow band still costs the request for out-of-range listings on that page. propertyTypeId passes through as Fotocasa's raw internal ID — there's no confirmed ID-to-label table yet.
FAQ
Why did the original spec get reducedPrice's type wrong?
The only sample probed at spec time had it as null, and null doesn't reveal a type. A live run against listings that actually carry a discount showed it's a formatted string like price, not a raw integer.
Do I need a Fotocasa account or API key?
No — Fotocasa publishes no public API for this data; this Actor reads the same public search-results pages a browser loads and parses the JSON already embedded in them.
Why is the proxy pinned to residential Spain (ES) by default?
Because a Spain-only portal can render the wrong currency, or block outright, from a foreign exit — we force the ES residential exit regardless of other settings.
$0.20 per run plus $0.004 per validated listing — $4.20 per 1,000 results. No data, no charge beyond the start fee.
→ Fotocasa Spain Property Scraper on Apify
Built by Devil Scrapes. We rotate browser fingerprints, rotate residential proxies, retry with backoff, and ship clean typed rows — and we don't guess a field's shape from a sample where it's empty.
Top comments (0)