Fotocasa Scraper — Spain Real Estate Data & Agencies
No login. No session cookies. No browser.
Extracts property listings for sale and rent from Fotocasa, one of Spain's main property portals, through the same public search endpoint the website calls. Every row carries the price, the surface, the GPS point, Fotocasa's eight-level location hierarchy with its numeric IDs and, for professional advertisers, the agency with its phone.
oswaldocarabano/fotocasa-scraper
{
"location": "724,14,28,173,0,28079,0,0,0",
"transactionType": "rent",
"propertyType": "home",
"maxItems": 200,
"includeAgencies": true
}- Version
- v0.1.12
- Memory
- 512 MB
- Browser
- none
- Proxy
- None by default; falls back to a Spanish residential IP if blocked
Short answer
The Fotocasa Scraper extracts Spanish property listings from Fotocasa without a login or a browser: price, surface (filled on 99.9% of 3,184 measured listings), GPS coordinates, the full location hierarchy with Fotocasa's own IDs, photos tagged by room, and the agency phone for professional advertisers (96.4% of 1,911). A listing returns a median of 45 populated fields out of 78. Pricing is $0.00095 per listing, $0.002 per agency and $0.006 per optional detail page, and duplicates, error rows and listings without a price are never charged.
Key points
- The Fotocasa Scraper reads the public search endpoint that fotocasa.es itself calls, so it needs no account, no session cookie and no browser.
- Fill rates come from 3,184 listings across 16 search profiles measured on 18 September 2026: surface 99.9%, district 90.2%, bathrooms 73.5%, rooms 67.9%.
- Fotocasa's search stops returning new listings after page 392 and then repeats the same 30 with HTTP 200, so the Actor detects the end of a search by duplicate IDs rather than by status code.
- Searches larger than 11,760 listings are split along Fotocasa's own location tree, which measured exactly disjoint in 17 of 18 partitions across 6 provinces.
- A detail page costs about 65 times the bandwidth of a listing (n = 220), which is why street, energy certificate and construction type are an opt-in, separately priced event.
- Private advertisers were 11.7% of 2,164 listings sampled in rural provinces, and the Actor redacts their contact fields, including phone numbers typed into the description.
What it does
Extracts property listings for sale and rent from Fotocasa, one of Spain's main property portals, through the same public search endpoint the website calls. Every row carries the price, the surface, the GPS point, Fotocasa's eight-level location hierarchy with its numeric IDs and, for professional advertisers, the agency with its phone.
{
"id": 186965592,
"url": "https://www.fotocasa.es/es/comprar/vivienda/campillos/no-amueblado/186965592/d",
"operation": "buy",
"property_type": "home",
"price": 93000,
"currency": "EUR",
"surface_m2": 300,
"rooms": 3,
"bathrooms": 1,
"latitude": 37.04998,
"longitude": -4.8671,
"postal_code": "29320",
"region": "Andalucía",
"province": "Málaga",
"province_id": 29,
"municipality": "Campillos",
"municipality_id": 29032,
"published_at": "2025-07-03T14:35:58.327Z",
"photo_count": 28,
"room_types_photographed": ["kitchen", "bathroom", "living room", "exterior", "bedroom"],
"agency_name": "Example Inmobiliaria",
"agency_phone": "+34900000000",
"is_professional_advertiser": true,
"energy_certificate_rating": "G",
"address_is_exact": true,
"from_cache": false,
"data_age_hours": 0
}Why this one
The location tree arrives whole
Every Fotocasa listing row carries country, region, province, municipality, district and neighbourhood with Fotocasa's numeric ID beside each name. Grouping and joining across runs then works on IDs instead of on strings that change with accents and language, and the same IDs are what the Actor uses to split a large search into disjoint pieces.
End of pagination detected by content, not status
Past page 392 Fotocasa's search keeps answering HTTP 200 with the same 30 listings. A scraper that trusts the status code loops forever or bills duplicates. The Fotocasa Scraper stops on repeated IDs and never charges a duplicate.
Filters that were checked to filter
Fotocasa silently ignores parameters it does not recognise and returns the whole catalogue with HTTP 200. Every filter and sort value in the Fotocasa Scraper's input was verified to change the result against the live API; five plausible-looking filters that change nothing were left out.
An agency dataset at zero extra requests
The agency name, phone, logo and profile URL are already inside the search response, so `includeAgencies` writes a deduplicated `agencies` dataset without a single extra request. Private advertisers never enter it.
Use cases
- Build a priced, geolocated dataset of a Spanish province, city or neighbourhood for sale or rent.
- Track asking prices and price reductions over time with a scheduled run and `published_at`.
- Build a lead list of professional real estate agencies with their business phones.
- Compare energy certificates, construction type and plot surface across a market using detail pages.
- Join listings to other data on Fotocasa's numeric location IDs instead of on place names.
Input
Every field has a default, and the defaults are deliberately small so a first run is cheap enough to inspect before you commit to a sweep. This table mirrors the Actor's own input schema field for field.
| Field | Default | What it does |
|---|---|---|
locationstring | "724,0,0,0,0,0,0,0,0" | LocationFotocasa location ID (`combinedLocationIds`). Defaults to all of Spain. Madrid city is `724,14,28,173,0,28079,0,0,0`; the Actor walks the tree below whatever you pass. |
transactionTypeselect | "sale" | Operation`sale` or `rent`. New-build is not offered because it returns zero results against the live API. |
propertyTypeselect | "home" | Property type`home`, `commercial`, `land`, `garage` or `storage`. |
sortByselect | "relevance" | Sort by`relevance`, `price`, `newest`, `surface` or `rooms` — only sort values verified to change the order, because Fotocasa ignores unknown ones silently. `sortDescending` defaults to true. |
minPriceinteger | — | Minimum / maximum price (EUR)With `maxPrice`, `minRooms`, `minSurface` and `maxSurface`. Each was verified against the live API: a minimum price moved the national count from 470,340 to 302,763. |
maxItemsinteger | 1000 | Maximum listingsHard cap for the run, up to 50,000. There is no download-everything mode. |
scrapeDetailsboolean | false | Also scrape the detail pagepersonal dataAdds street, energy certificate, creation date, construction type, plot surface and quality score. About 65 times the bandwidth of a listing, billed as a separate event. |
includeAgenciesboolean | false | Also build an agency datasetWrites a separate `agencies` dataset at zero extra requests. Private advertisers are never included. |
respectRobotsTxtboolean | false | Respect robots.txt on the websitepersonal dataOff by default. When on, the Actor honours the robots.txt of www.fotocasa.es for detail pages, which skips detail pages under some city paths. It has no effect on listings, which come from an API host that publishes no robots.txt. |
maxCacheAgeDaysinteger | 7 | Maximum cache age (days)Serve from cache when the stored copy is younger than this. 0 always fetches fresh. |
maxConcurrencyinteger | 5 | Maximum concurrency1 to 10. The API answered 24 parallel requests without a non-200; the default stays well below that. |
Output and fill rates
A field being in the schema is not the same as it having a value. The percentages below were counted on real runs; the sample sizes are in Measurements. Anything not listed here is not promised.
| Field | Filled | Meaning |
|---|---|---|
idinteger | 100% | Fotocasa's listing ID. The join key across runs. |
pricenumber | 100% | Asking price, with `currency` and `operation`. Listings without a price are not delivered or charged. |
surface_m2number | 99.9% | Surface in square metres. |
roomsinteger | 67.9% | Room count as published. |
bathroomsinteger | 73.5% | Bathroom count as published. |
floorinteger | 45.7% | Floor, where the listing states it. |
latitudenumber | 100% | With `longitude` and `coordinates_accuracy`. |
provincestring | 100% | With `region`, `municipality` and their numeric IDs (`province_id`, `municipality_id`…). |
districtstring | 90.2% | With `district_id`. |
neighborhoodstring | 64.9% | With `neighborhood_id`. |
published_atdatetime | 100% | When the listing was published on Fotocasa. |
price_reducednumber | 28.3% | Set when Fotocasa flags a price reduction. |
rent_periodicity_idinteger | not measured | Rentals only: 98.9% of 784 rentals and 0% of 2,400 sales, so the blended rate is meaningless and not shown. |
room_types_photographedstring[] | 81.5% | Rooms the photos show — kitchen, bathroom, terrace, bedroom… At least one tagged photo on 81.5% of 840 listings. |
agency_phonestring | 96.4% | Professional advertisers only; the rate is of 1,911 professional listings. Private advertisers' contacts are redacted. |
has_elevatorboolean | 44% | One of the amenity flags: air conditioning 49.4%, heating 39.9%, terrace 29.0%, pool 6.4%, parking 3.7%. |
energy_certificate_ratingstring | 99.1% | Detail pages only, n = 220. With `energy_certificate_value` (100%) and the emissions rating and value. |
street_namestring | 72% | Detail pages only, n = 150. `street_number` only when the address is exact: 22.0%. |
address_is_exactboolean | not measured | With `address_visibility_mode`, so rows can be filtered by address precision. |
from_cacheboolean | not measured | With `fetched_at` and `data_age_hours`, on every row. |
Every key is always present. A field that exists but is empty comes back as explicit null, so a parser never has to guess.
Datasets
Different record types go to different datasets, so the main table never carries columns that are blank on most rows.
defaultOne row per property listing, with the detail-page fields when `scrapeDetails` is on.billedagenciesOne row per professional agency found, when `includeAgencies` is on.billed
Pricing
Pay per delivered result. Charges are applied as each row is produced rather than in a lump at the end, so an aborted run bills only for what it actually gave you.
| Event | Price | Notes |
|---|---|---|
actor-startActor start | $0.00001 | Charged once per run. |
propertyProperty listing | $0.00095 | One delivered listing. Never charged for duplicates, errors or listings without a price. |
agencyReal-estate agency | $0.002 | One professional agency with name, phone, logo and profile URL. Opt-in. |
property-detailDetail page | $0.006 | Street, energy certificate, creation date, construction type and plot surface. Opt-in. |
Measurements
Each figure is shown with the method that produced it. A benchmark without a method is a marketing claim wearing a number's clothes.
Fill-rate sample
3,184 listings
16 search profiles against the live site on 18 September 2026. Every listing fill rate on this page comes from that sample unless another n is stated.
Fields per listing
Median 45 of 78; 58 of 78 with detail pages
45 measured on the 3,184-listing sample; 58 measured on 30 listings with detail pages.
Pagination ceiling
Page 392 (~11,760 listings per query)
Past page 392 the search API repeats the same 30 listings with HTTP 200. Detected by duplicate IDs.
Location split
Disjoint in 17 of 18 partitions
18 partitions across 6 provinces and 3 tree levels. The 18th case returns siblings instead of children; the Actor detects it and paginates instead of descending.
Detail page cost
65.3x a listing's bandwidth
Measured on 220 detail pages: about 614 KB against 9.4 KB.
Private advertisers
11.7% of 2,164 listings
Sampled in rural provinces, where private advertisers concentrate. Fotocasa published a phone for 0% of the 253 private advertisers in that sample.
Throughput
16,568 listings per hour
One reference run of 2,000 listings in 435 seconds at 512 MB, 73 requests, all direct with no proxy.
What it will not do
Stated plainly so you can judge fit before spending anything.
- No download-everything mode: `maxItems` is a hard cap, with a maximum of 50,000 per run.
- A single search query cannot page past 392 pages, about 11,760 listings; larger searches are split by location.
- New-build developments (transaction type 4) are not offered, because the live API returned zero results for them.
- Street number only when Fotocasa marks the address as exact, which was 22.0% of 150 detail pages measured.
- Detail pages are off by default and cost about 65 times a listing in bandwidth.
- Four fields that came back empty on every measured listing — storage room, furnished flag, photo-report flag and external contact — are not in the output at all.
Privacy
- Professional agencies' names and phones are business contact data and are always delivered.
- Private advertisers' contact fields are redacted, and phone numbers typed inside their descriptions (found in 2.4% of 124 private listings) are stripped.
- An advertiser type the Actor has not verified is treated as private: the row is redacted, flagged with `is_professional_advertiser: false` and not charged.
- Removal requests: privacy@actorstack.dev
See also the data removal process.
Frequently asked questions
Does the Fotocasa Scraper need an account or a login?
Can I get real estate agency phone numbers from Fotocasa?
How many Fotocasa listings can one run return?
Does the Fotocasa Scraper return the street address?
Does the Fotocasa Scraper follow Fotocasa's robots.txt?
Guides for this Actor
- Property portals comparedFotocasa, Imobiliare.ro and Zigbang publish listing data in three different shapes, behind three different access models, with three different robots.txt files. What each one gives you, where each one stops, and what the measured fill rates actually say.
- How to scrape FotocasaA working method for extracting Spanish property listings from Fotocasa: picking a location from its ID tree, filters that actually filter, the pagination wall at page 392, and when a detail page is worth 65 times the bandwidth.