Fotocasa.es $1💰 Buy, Rent & Property Details
Pricing
from $1.00 / 1,000 listing scrapeds
Fotocasa.es $1💰 Buy, Rent & Property Details
From $1/1K. Scrape Spain property listings from fotocasa.es. Get price, surface, rooms, baths, address, GPS, agency, phone, energy rating, photos and 140+ fields. Buy and rent, all regions, all property types.
Pricing
from $1.00 / 1,000 listing scrapeds
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
4
Total users
0
Monthly active users
19 hours ago
Last modified
Categories
Share
Fotocasa.es Scraper: Property Listings, Prices & Agency Contacts
Fotocasa.es Scraper turns fotocasa.es into a clean property-data feed for Spain. Get price, surface, rooms, bathrooms, full address, GPS coordinates, agency name and phone, and 140+ fields per listing, for both buy and rent, every region and every property type. Search by location and filters, or paste direct fotocasa.es search-result URLs, switch on per-listing detail enrichment for energy rating and agent contact, then export to JSON, CSV or Excel, or pull results straight into your app through the API.
Why This Scraper?
- 140+ fields per listing. Every record carries both camelCase and snake_case field names, so the output drops into any existing pipeline without remapping.
- Buy and rent, every region, every property type. Flats, houses, duplexes, penthouses, studios, lofts, garages, offices, warehouses, land and more.
- Rich location data. City, neighborhood and precise latitude/longitude on every listing, straight from the search results.
- Agency and contact details on every listing, plus optional agent name, email and listing reference when detail enrichment is on.
- Two input modes. Search by location with filters, or feed direct fotocasa.es search-result URLs; either way you choose whether to fetch full listing details.
- Built for monitoring. Incremental mode tracks one saved search across scheduled runs and classifies each listing as new, updated or unchanged by default (reappeared needs Emit expired listings), without you pasting any run id.
- Fails loudly, not silently. A run where every request was refused stops with a clear error instead of billing an empty result as if the search itself came back with nothing.
Use Cases
- Real estate investors: track price per square metre across neighborhoods to spot undervalued listings.
- Proptech platforms and aggregators: build a structured buy-and-rent property feed for Spain to power search or comparison tools.
- Relocation and expat services: monitor new listings in target cities so clients hear about matches quickly.
- Market researchers and appraisers: build historical price and surface datasets for a city or region.
- Agencies and portals: track how competitor listings price and how long they stay on the market, using Incremental mode's change history.
Data You Get
Sample shape: values are illustrative placeholders, not from a live listing.
| Field | Example |
|---|---|
id | "1_000000000" |
propertyId | "000000000" |
title | "Calle de Ejemplo, Centro" |
rawPrice | 295000 |
currency | "EUR" |
pricePerSqm | 4538.46 |
surface | 65 |
rooms | 3 |
baths | 2 |
floor | "4" |
propertyType | 2 |
propertySubtype | 1 |
operation | "sale" |
city | "Madrid" |
neighborhood | "Centro" |
latitude | 40.4168 |
longitude | -3.7038 |
agencyName | "Example Agency SL" |
clientAlias | "Example Agency" |
clientType | "professional" |
phone | "+34000000000" |
images | ["https://static.fotocasa.es/images/ads/0000?rule=original"] |
photosCount | 12 |
url | "https://www.fotocasa.es/es/comprar/vivienda/madrid-capital/centro/000000000/d" |
Every listing also carries a description, publication date, promotion/highlight flags (isNew, isNewConstruction, isVirtualTour, isTop, ...) and dozens of id/type fields (transactionTypeId, typeId, subtypeId, clientTypeId, ...) in both camelCase and snake_case. energyCertificate, reference, extraFeatures, constructionType, groundSurface, qualityRate, purchaseType, agentName, agentEmail, contactName and creationDate stay empty on every record unless fetchDetails is on, since they only come from the listing's own detail page. When Incremental mode is on, every returned record also carries changeType (NEW / UPDATED / UNCHANGED / REAPPEARED / EXPIRED), changedFields, firstSeenAt and lastSeenAt.
How to Use
- Pick a mode:
search(location + filters) orurl(direct fotocasa.es search-result URLs). - For search mode, set Locations, Operation, Property Type, Sort By and any price/rooms/baths/surface filters. For URL mode, paste one or more fotocasa.es search-result URLs; filter fields are ignored in this mode.
- Turn on Fetch full details if you need energy rating, full features, reference and agent/contact name and email from each listing's own page.
- Set Max items to control run size and cost, then click Start. Download the dataset as JSON, CSV or Excel, or read it through the API.
Search a city for homes to buy:
{"mode": "search","locations": ["madrid-capital"],"operation": "comprar","propertyType": "viviendas","maxListings": 50}
Rent search with filters and sorting:
{"mode": "search","locations": ["barcelona-capital"],"operation": "alquiler","minPrice": 800,"maxPrice": 1500,"minRooms": 2,"sortBy": "price_asc","maxListings": 100}
Direct search URLs with full detail enrichment:
{"mode": "url","urls": ["https://www.fotocasa.es/es/comprar/viviendas/valencia-capital/todas-las-zonas/l"],"fetchDetails": true,"maxListings": 200}
Daily monitoring of one search, with expiry tracking:
{"mode": "search","locations": ["madrid-capital"],"operation": "comprar","propertyType": "pisos","incrementalMode": true,"emitExpired": true,"maxListings": 0}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/fotocasa-es-scraper").call(run_input={"mode": "search","locations": ["madrid-capital"],"operation": "comprar","maxListings": 50,})for listing in client.dataset(run["defaultDatasetId"]).iterate_items():print(listing["title"], listing["rawPrice"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/fotocasa-es-scraper').call({mode: 'search',locations: ['madrid-capital'],operation: 'comprar',maxListings: 50,});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
Full-catalogue runs and resuming
To walk an entire search (not just a page or two), set Max items to 0 and leave Max Pages Per Search empty (or 0): the actor then paginates until the site itself runs out of results. If a full-catalogue run is interrupted (platform migration, manual abort, or a fresh run days later to pick up new listings), pass the previous run's id as Resume from run ID: the actor pages through that run's dataset, seeds its dedup set with the listing ids already collected there, and then scrapes only new listings. An interrupted run also checkpoints its own seen-listing state during the run and resumes from it automatically on a platform migration; that checkpoint is cleared once a run finishes cleanly.
Incremental mode and REAPPEARED/EXPIRED
resumeFromRunId continues one specific interrupted run. Incremental mode is for the opposite case: you schedule this actor to run the same search again and again (daily, weekly) and want only what changed, without pasting any id. Turn it on and the actor keeps a baseline in its own key-value store, keyed on the search/URL and every filter and fetchDetails setting (raising or lowering Max items/Max Pages between runs never forks a new baseline). Each listing is then classified:
- NEW: not seen before.
- UPDATED: a tracked field changed;
changedFieldslists which ones. - UNCHANGED: nothing changed. Suppressed by default (not returned, not billed); turn on
emitUnchangedto get the full snapshot every run instead. - REAPPEARED: this only ever fires for a listing whose stored state was already marked not-present by an earlier run, which only happens once that earlier run had
emitExpiredon and completed a full, uncapped, non-resumed scan. It is never the outcome for an ordinary unchanged or first-seen listing. - EXPIRED: was tracked, no longer found. Only produced when
emitExpiredis on, and only once a run has proven a complete scan of the tracked search: amaxListings/maxPagescap, or aresumeFromRunIdrun, skips expiry detection entirely for that run rather than risk tombstoning a listing that simply wasn't reached yet.
Turning on incrementalMode together with resumeFromRunId is only allowed to bootstrap a brand-new monitoring baseline; if that state key already has tracked listings from an earlier run, the actor fails fast, before any scraping, and asks you to drop Resume from run ID or pick a different State key.
Send results into your apps (MCP connectors)
Beyond the dataset, this Actor can send a condensed summary of each listing straight into the tools you already use, via Model Context Protocol (MCP) connectors. The full record is always kept in the dataset; the connector receives a readable summary (title plus key fields) per item.
Supported connectors: Notion, Linear, Airtable, and Apify.
- Authorize the connector once under Apify → Settings → Integrations.
- Select one or more in the
mcpConnectorsinput. - (Notion only) Set
notionParentPageUrlto the page under which one page per listing is created. - Optionally cap how many listings are forwarded with
maxNotifyListings(default 50).
{"mode": "search","locations": ["madrid-capital"],"operation": "comprar","maxListings": 50,"mcpConnectors": ["notion"],"notionParentPageUrl": "https://www.notion.so/your-workspace/Listings-0000"}
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search (location + filters) or url (direct URLs). |
locations | array | no default (prefill: ["madrid-capital"]) | Location slugs, e.g. madrid-capital, barcelona-capital/centro. Search mode only; leave empty for all of Spain. |
operation | string | comprar | comprar (buy) or alquiler (rent). Search mode only. |
propertyType | string | viviendas | Property category, e.g. viviendas, pisos, casas. Search mode only. |
sortBy | string | relevance | relevance, newest, price_asc, price_desc, surface_asc or surface_desc. |
minPrice | integer | no default | Minimum price filter (EUR). Search mode only. |
maxPrice | integer | no default | Maximum price filter (EUR). Search mode only. |
minRooms | integer | no default | Minimum number of rooms. Search mode only. |
maxRooms | integer | no default | Maximum number of rooms. Search mode only. |
minBaths | integer | no default | Minimum number of bathrooms. Search mode only. |
maxBaths | integer | no default | Maximum number of bathrooms. Search mode only. |
minSurface | integer | no default | Minimum surface in square metres. Search mode only. |
maxSurface | integer | no default | Maximum surface in square metres. Search mode only. |
urls | array | no default (prefill: one sample search URL) | Direct fotocasa.es search-result URLs. URL mode only; filter fields above are ignored in this mode. |
fetchDetails | boolean | false | Adds energy certificate, full feature list, reference and agent/contact name and email from the listing detail page. Slower and increases cost per listing. |
maxListings | integer | 20 | Maximum total listings to scrape. This is the run's sole cap. Set 0 for unlimited. |
maxPages | integer | no default | Optional safety limit on result pages walked per search (~31 listings/page). Does NOT cap the run; leave empty (or 0) for unlimited and let Max items decide when to stop. |
resumeFromRunId | string | no default | Optional: paste a previous run id (or its dataset id) to continue that run. Listings already saved there are skipped, so this run only adds new ones. |
incrementalMode | boolean | false | Turn on for daily or recurring monitoring of the same search: classifies every listing NEW / UPDATED / UNCHANGED by default (REAPPEARED and EXPIRED need Emit expired listings too). |
stateKey | string | no default | Optional, incremental mode only. Names a monitoring campaign so its state stays stable, or so two differently-configured runs can deliberately share one baseline. |
emitUnchanged | boolean | false | Incremental mode only. Also return (and bill) listings unchanged since the last run, marked UNCHANGED. |
emitExpired | boolean | false | Incremental mode only. Also return (and bill) synthetic EXPIRED rows for tracked listings that no longer appear, produced only after a complete, uncapped scan of the tracked search. |
proxy | object | Residential proxy, Spain (ES) exit | Proxy used for all requests. Residential with an ES exit is enabled by default, which fotocasa.es requires for reliable access. |
residentialCountries | array | ["ES"] | Country codes used for the residential proxy fallback rotation. |
maxResidentialRequests | integer | 10000 | Caps how many requests may use the residential proxy in one run, to control cost. Once spent, the run auto-downgrades to a cheaper connection tier. Set 0 to disable the residential tier entirely. |
backupProxyUrl | string | no default | Optional. A full proxy URL used as a last-resort failover when the Apify proxy is unavailable (for example on a restricted plan). |
mcpConnectors | array | no default | Optional: send a summary of each listing to apps you authorized under Integrations. |
notionParentPageUrl | string | no default | Notion connector only: page under which one page per listing is created. |
maxNotifyListings | integer | 50 | Cap on listings forwarded to each connector per run. Does not affect the dataset. |
Output Example
Sample shape: values are illustrative placeholders, not from a live listing. Shown with
fetchDetailson, which is why the detail-only fields below are populated.
{"id": "1_000000000","propertyId": "000000000","title": "Calle de Ejemplo, Centro","rawPrice": 295000,"currency": "EUR","pricePerSqm": 4538.46,"surface": 65,"rooms": 3,"baths": 2,"floor": "4","propertyType": 2,"propertySubtype": 1,"operation": "sale","transactionTypeId": 1,"city": "Madrid","neighborhood": "Centro","address": { "locality": "Madrid", "neighborhood": "Centro", "coordinates": { "lat": 40.4168, "lng": -3.7038 } },"latitude": 40.4168,"longitude": -3.7038,"agencyName": "Example Agency SL","clientAlias": "Example Agency","clientType": "professional","phone": "+34000000000","reference": "REF-00000","energyCertificate": { "energyEfficiencyRatingType": "C" },"images": ["https://static.fotocasa.es/images/ads/0000?rule=original"],"photosCount": 12,"description": "Bright reformed home in a well-connected central area.","url": "https://www.fotocasa.es/es/comprar/vivienda/madrid-capital/centro/000000000/d"}
Plan Requirement
The default proxy setting (residential, Spain exit) works out of the box and is what fotocasa.es needs for reliable access; you can change it under Connection. Large or frequent runs should keep an eye on Residential request budget to control cost, since the run automatically falls back to a cheaper connection tier once that budget is spent.
FAQ
How much does it cost?
You pay per run start, per listing returned, and an optional per-listing surcharge for detail enrichment only when fetchDetails is on. The Pricing tab shows the current rates. Use Max items to cap the cost of any run.
Is it legal to scrape Fotocasa.es?
This actor collects only publicly available property listing data. You are responsible for how you use it: follow fotocasa.es's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listing facts like price and surface are generally usable, but photos, descriptions and agency branding may be subject to third-party rights.
Can I get only new or changed listings on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new and updated listings by default; unchanged ones are not returned or billed unless you turn on Emit unchanged listings, and reappeared listings only show up once you also use Emit expired listings.
Why does a pasted "URL" search page ignore my filter fields?
In url mode the search-result URL fully defines the query, exactly as it was built on fotocasa.es, so the price/rooms/baths/surface filter fields in the input are ignored on purpose. Build the filters into the URL on the site itself, or switch to search mode to use the filter fields.
Why did my run fail instead of returning an empty dataset?
If every request to fotocasa.es was refused (rate-limited, blocked, or no readable response at all), the run fails with a clear message instead of silently completing as if the search itself came back empty, so "nothing was found" is never confused with "nothing could be read". Retry after a few minutes, or switch to a residential proxy with a Spain (ES) exit. A genuinely empty search, on the other hand, still succeeds and reports quietly with no billable row.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.
🔗 Want more real estate data?
Pair this actor with these related scrapers from the same team:
| 🏠 Idealista Scraper From $1/1K. Scrape Idealista properties across Spain, Italy, and Portugal. Extract 35+... | 🏠 Funda in Business Scraper Scrape Funda in Business commercial listings: offices, retail, industrial, catering and... |
| 🔌 MediaMarkt.es Scraper Scrape MediaMarkt Spain (mediamarkt.es) products: current price, strike-through original... | 🏠 Zonaprop Argentina Scrape Zonaprop.com.ar property listings across Argentina. Extract 140+ fields including... |
| 🛒 Decathlon.es Scrape Decathlon Spain (decathlon.es) sporting goods: current price plus strike-through... | 🛒 LeroyMerlin.es Scraper Scrape Leroy Merlin Spain (leroymerlin.es) DIY and home-improvement products: price... |
💬 Support & custom scrapers
- 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- 🛠️ Need another site, extra fields or a private build? Email abotapi@proton.me or message Telegram @abotapi.
- ⭐ Enjoying it? A quick review on the actor page helps other users find it.