# Fotocasa Scraper - Spain Real Estate Listings, Sale & Rent (`s-r/fotocasa-scraper`) Actor

Collect Fotocasa property listings for sale and rent in Spain: price, m2, bedrooms, bathrooms, address, coordinates, agency and photos. Search by town or paste a Fotocasa search URL. Built for property investors, analysts and proptech teams.

- **URL**: https://apify.com/s-r/fotocasa-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** Real estate, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 listing returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Fotocasa Scraper - Spain Real Estate Listings, Sale & Rent

This Fotocasa scraper turns Spanish real estate searches into clean dataset rows for investors, analysts and proptech teams. Search by a Spanish municipality, province or district, or paste a Fotocasa search URL with the filters you selected. Each row includes the property identifier, original listing link, displayed address, asking price, area in m2, bedrooms, bathrooms, agency and photos when available.

Fotocasa is one of the largest property portals in Spain, with homes for sale and for rent from agencies and private owners. Use the results to compare asking prices across neighbourhoods, monitor available rental stock, build a property research dataset or connect Spanish listings to an existing property analysis workflow. The actor collects published asking prices and listing information. It does not estimate transaction prices, property values or investment returns.

### What the Fotocasa scraper does

1. You choose a place (for example Valencia, Sevilla or Getafe) with buy or rent and a property category, or you paste one or more Fotocasa search URLs.
2. The actor walks through the search result pages in order and keeps one row per unique property.
3. Rows land in the Apify dataset, ready to download as JSON, CSV or Excel, or to read through the Apify API and integrations.
4. When a large search is not finished within one run, a continuation token lets you pick up where it stopped.

### Fotocasa Scraper input

Set `location` to a place such as Valencia, Sevilla or Getafe, or supply `start_urls` as a list of Fotocasa search URLs. Non-empty search URLs take priority over a supplied location, with a warning that the location was ignored. Empty or null input fields are ignored. For a location search, `operation` selects `buy` or `rent`; the default is `buy`. The location lookup selects the first matching geographic suggestion. For a specific district or a place name with several possible matches, use the search URL from the Fotocasa website to retain your exact selection.

`property_type` defaults to `homes`. Supported categories include flats, penthouses, duplexes, houses, terraced houses, country houses, commercial premises, offices, garages, land, storage rooms, buildings and industrial properties. The category names in the input are `flats`, `penthouses`, `duplexes`, `houses`, `terraced_houses`, `country_houses`, `commercial`, `offices`, `garages`, `land`, `storage_rooms`, `buildings` and `industrial`.

Location searches support `min_price`, `max_price`, `min_size`, `max_size`, `min_rooms` and `min_bathrooms`. Prices are in EUR and sizes are square metres. All filters use non-negative whole numbers. Minimum price and minimum size must not exceed their corresponding maximum. Sort with `sort_by`: `relevance`, `newest`, `cheapest` or `most_expensive`. These filters apply to the location route. A search URL retains its own transaction, category, filters and sort order. If you supply location filters or non-default transaction, category or sort choices alongside URLs, a warning identifies the ignored fields. The default choices produce no such warning.

`max_items` limits the number of unique properties across all searches, with a default of 100 and a maximum of 10000. The aliases `maxItems`, `startUrls` and `startUrl` support clients moving from other Fotocasa actors. The URL aliases accept one URL or a list. When both volume fields are supplied, the smaller limit applies. Search URLs must use HTTPS and the Fotocasa search-page route; property detail links are not search inputs.

This input was used in a real run:

```json
{"location":"Valencia","operation":"rent","max_items":100}
```

For an existing filtered search, this input was also used in a real run:

```json
{"start_urls":["https://www.fotocasa.es/es/comprar/viviendas/sevilla-capital/todas-las-zonas/l?maxPrice=300000&minRooms=3"],"max_items":300}
```

### Fotocasa scraper output

The default dataset contains one row per unique `listing_id`. Download it as JSON, CSV, Excel or another format supported by Apify. Every row has `url`, `fetched_at`, `listing_type`, `query` and a one-based `position` within its search. Search URLs may include filters in the path, such as `/terraza/l`, as well as query parameters. The actor preserves them during pagination. The listing type is `for_sale` or `for_rent` for the corresponding transaction. Original property URLs are retained, including the different link format used by some new-construction promotions.

This is the first row of the real Valencia rental run shown above, with a selection of its fields (the full row also carries the description, all photo URLs, neighbourhood, district, region and property flags):

```json
{
  "listing_id": "190512572",
  "url": "https://www.fotocasa.es/es/alquiler/vivienda/valencia-capital/aire-acondicionado-calefaccion-ascensor-amueblado-se-aceptan-mascotas/190512572/d",
  "listing_type": "for_rent",
  "position": 1,
  "address": "Poblats Marítims",
  "city": "Valencia",
  "postal_code": "46011",
  "state": "Valencia",
  "lat": 39.46883246430951,
  "lng": -0.3324971629813452,
  "price": 1500,
  "currency": "EUR",
  "price_period": "month",
  "price_drop": 150,
  "property_type": "Flat",
  "bedrooms": 2,
  "bathrooms": 2,
  "living_area": 96,
  "living_area_unit": "m2",
  "broker": "aProperties Real Estate Valencia",
  "phone": "+34960179239",
  "image_url": "https://static.fotocasa.es/images/ads/942eeec7-23fc-4bf5-93e5-cd5705bcfc72?rule=original",
  "published_at": "2026-08-19T12:42:24.953Z",
  "days_on_market": 47,
  "fetched_at": "2026-10-05T17:17:25.205Z"
}
```

The main property fields are `address`, `street`, `city`, `postal_code`, `state`, `country_code`, `lat`, `lng`, `price`, `currency`, `property_type`, `bedrooms`, `bathrooms`, `living_area`, `living_area_unit`, `broker` and `image_url`. Currency is EUR and area units are `m2`. The province appears in `state`; district, neighbourhood and autonomous community have separate fields. Coordinates may be approximate when `is_exact_address` is false.

The `broker` and `broker_url` fields describe professional agencies and are always null for private advertisers, even when the source displays a personal name or profile link. Private phone numbers are also omitted; `broker_id` remains an opaque advertiser identifier.

If an individual card lacks a valid listing ID or property URL, the actor skips it and delivers the remaining properties. The `searches` record reports a cumulative `skipped_items` count, with warnings and partial status. Skipped cards are not charged as listing results.

Unavailable values remain null. In particular, a listing advertised with a price of zero means price on request and produces `price: null`. Land and garage listings frequently omit bedroom and bathroom counts; the actor does not invent zero counts. Monthly rentals carry `price_period: "month"`. `price_drop` is the reduction amount, while `price` is the current asking amount.

Additional fields include `description`, `images`, `property_subtype`, `published_at`, `days_on_market`, advertiser identifiers and property flags such as occupied, auctioned, temporary rental, bare ownership or new construction. Publication times are converted from local Madrid time to UTC. `days_on_market` counts whole elapsed days from the converted date. The public agency phone is included for professional advertisers; private advertiser phone numbers are always null. Published descriptions are retained as listing text.

The `searches` key-value record contains search context, while the run output record contains warnings. An unfinished continuation token is saved in the `next_cursor` key-value record. Each search reports its source total, processed page count and whether the source imposed its page ceiling. If a search fails while another succeeds, the collected properties remain available and the output records the warning. Source totals can change while a search is running.

### Use cases for Spain real estate data

- **Property investors** screen asking prices per m2 by town or neighbourhood, filter by bedrooms and price band, and shortlist listings with a recent price drop.
- **Rental market research**: track monthly rents, supply and days on market for a city such as Madrid, Valencia or Sevilla, and compare snapshots week by week.
- **Proptech and valuation teams** feed fresh comparables (price, area, rooms, coordinates) into pricing models, maps and dashboards.
- **Agencies and lead generation**: see which professional agencies advertise in an area, with their public agency name, profile link and phone.
- **Journalists and academics** document housing supply and asking-price changes with the original listing link kept on every row.

### Fotocasa scraper compared with other options

- **Copying listings by hand** works for a handful of homes but not for thousands of rows with consistent fields. The actor returns typed numbers (price, area, rooms) and keeps null when a value is not published.
- **Writing your own Fotocasa scraper** means handling location lookup, pagination, the source page ceiling, duplicates and changes on the website yourself. Here you get that maintained, with a continuation token for long searches.
- **Other Fotocasa scrapers in the Apify Store** differ in input style and output shape: some only accept one search URL, others return the raw source structure. This actor accepts a town name or several URLs, keeps the common field names `maxItems`, `startUrls` and `startUrl`, and returns flat, documented columns with one row per unique property.
- **Commercial property data platforms** sell valuation estimates and historical transactions on a subscription. This actor gives current public asking prices and you pay per delivered listing.

### Pricing

Charges are defined by the actor's live pricing panel on Apify. The intended pricing structure has two events: one `run_start` event for a valid run and one `listing` event for each delivered dataset row. Duplicate properties do not create additional listing charges. An invalid input has no start or listing charge. A valid search with no results has no listing charge; its run-start fee still applies. Confirm the current amounts in the pricing panel before starting a run.

Non-paying accounts are limited to ten delivered properties per run. Paying accounts can request their chosen volume up to the actor limit. A smaller requested limit is respected. The row limit is a maximum rather than a guarantee: a search can end with fewer properties when the source has fewer matches or a source limit is reached.

### Pagination and coverage

The actor follows available search pages automatically. Larger searches continue in bounded steps, and the `next_cursor` key-value record retains a continuation token if collection ends before all requested work is complete. Reuse the same input with the returned `next_cursor` supplied as `cursor` to continue an unfinished collection. Process each successful continuation only once to avoid repeating the same step in your own integration.

Fotocasa limits an individual search to 333 pages, approximately 9990 properties. When that ceiling applies, split your request by neighbourhood, property category or price band to collect a wider set of results. A large city-wide search may contain more properties than one search can return. This actor covers the public listings present in the selected search; it does not include sold properties, private inventories or valuation estimates.

### FAQ

#### Can I scrape both properties for sale and rentals?

Yes. Use `operation: "buy"` or `operation: "rent"` with location input. For URL input, supply the corresponding Fotocasa sale or rental search URL. Multiple URLs can cover several searches within one run, with a shared maximum number of unique listings.

#### Why are some prices or room counts null?

Null means the source does not publish an applicable value. Price-on-request listings have no numeric asking price. Garages, land and some commercial properties do not have residential room counts. These values remain distinguishable from an actual zero.

#### Does the actor visit each property detail page?

The listing fields documented here are collected from search results. Description, agency contacts and available image URLs are already present there. The actor does not promise additional detail-page attributes, floor-code translations or estimated market values.

#### How do I search an exact neighbourhood?

Select the neighbourhood and filters on Fotocasa, then copy the search URL into `start_urls`. This preserves the selected geographic scope and browser filters. A location text search uses the first matching suggestion and may be broader than the neighbourhood you intended.

#### Are the results stable over time?

Property advertisements change, disappear and move in the search order. Record `fetched_at` and the listing identifier when comparing runs. Asking prices represent the published snapshot, and descriptions, photos or counts can change after that snapshot was collected.

#### How much does the Fotocasa scraper cost?

You pay one start event per valid run and one event per unique property delivered, at the amounts in the pricing panel. A search without results only incurs the start event. Free Apify plans receive up to ten listings per run.

#### Can I export Fotocasa listings to Excel or Google Sheets?

Yes. Every run stores its rows in an Apify dataset that you can download as CSV, Excel or JSON, or push to Google Sheets and other tools with Apify integrations and the API.

# Actor input Schema

## `start_urls` (type: `array`):

Fotocasa search URLs. Non-empty URLs take priority over location with a warning. Empty lists and null are ignored.

## `startUrls` (type: `array`):

Alias of start_urls; accepts a list or one URL. Empty or null values are ignored.

## `startUrl` (type: `string`):

Alias for one Fotocasa search URL or a list. Empty or null values are ignored.

## `location` (type: `string`):

Municipality, province or district in Spain, such as Valencia or Getafe. Empty or null values are ignored; non-empty search URLs take priority.

## `operation` (type: `string`):

Transaction for location input. Search URLs preserve their transaction.

## `property_type` (type: `string`):

Property category for location input.

## `sort_by` (type: `string`):

Sort order for location input; URL input preserves its query parameters.

## `max_items` (type: `integer`):

Maximum unique listings across all searches and cursor requests. Default: 100.

## `maxItems` (type: `integer`):

Alias of max_items. If both are supplied, the smaller limit applies.

## `cursor` (type: `string`):

Continuation token from next_cursor. Repeat the original input unchanged.

## `min_price` (type: `integer`):

Minimum EUR price for location input.

## `max_price` (type: `integer`):

Maximum EUR price for location input.

## `min_size` (type: `integer`):

Minimum area in m2 for location input.

## `max_size` (type: `integer`):

Maximum area in m2 for location input.

## `min_rooms` (type: `integer`):

Minimum bedrooms for location input.

## `min_bathrooms` (type: `integer`):

Minimum bathrooms for location input.

## Actor input object example

```json
{
  "location": "Valencia",
  "operation": "buy",
  "property_type": "homes",
  "sort_by": "relevance",
  "max_items": 100
}
```

# Actor output Schema

## `results` (type: `string`):

Sale and rental property rows.

## `output` (type: `string`):

Searches, warnings and remaining cursor.

## `searches` (type: `string`):

Resolved URLs, source totals, processed pages and source ceilings.

## `cursor` (type: `string`):

Continuation token, present only when work remains. Repeat the same input with cursor set to this token.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "location": "Valencia",
    "max_items": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/fotocasa-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "location": "Valencia",
    "max_items": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/fotocasa-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "location": "Valencia",
  "max_items": 100
}' |
apify call s-r/fotocasa-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/fotocasa-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/mZqLmurafIw2qhBEA/builds/ZKGHfLAD7yxiPbqKY/openapi.json
