# Spain Property Tracker: Fotocasa, Habitaclia, pisos.com (`apizilla/spain-property-tracker`) Actor

Get homes for sale or rent from Fotocasa, Habitaclia and pisos.com in one clean list. The same home on many portals is merged. Monitor mode tells you what is new, what dropped in price and what was removed.

- **URL**: https://apify.com/apizilla/spain-property-tracker.md
- **Developed by:** [APIzilla](https://apify.com/apizilla) (community)
- **Categories:** Real estate, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 listing scrapeds

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

### What does Spain Property Tracker do?

Spain Property Tracker gets homes for sale or rent from three big Spanish portals in one run:

- [Fotocasa](https://www.fotocasa.es)
- [Habitaclia](https://www.habitaclia.com)
- [pisos.com](https://www.pisos.com)
- [Idealista](https://www.idealista.com) (beta, off by default)

You get one clean list with the same fields for every portal. When the same home is on more than one portal, you get it once. The cheapest copy is the main one, and the others are listed in `alsoListedOn`.

Turn on monitor mode and run it on a schedule. Each run tells you which homes are new, which dropped or rose in price, which were removed and which came back.

### Why use it?

- **One list, not three.** No need to check each portal by hand.
- **No duplicates.** The same flat on Fotocasa and Habitaclia shows up once.
- **Price alerts.** See price drops the day they happen.
- **Days on market.** See how long a home has been listed. Old listings are often open to offers.
- **Price per m2.** Compare homes and areas fast.

It is useful for buyers, renters, real estate agents, investors and market analysts.

### What data do you get?

| Field | Example |
| --- | --- |
| Price, price per m2 | 595000, 10084.75 |
| Size, rooms, bathrooms, floor | 59, 1, 1, "2" |
| Address, city, province, postcode | "Carrer d'Exemple, 1", "Barcelona" |
| Latitude and longitude | 41.38, 2.17 |
| Agency name, private seller or not | "Agencia Ejemplo", false |
| Up to 5 photos | image URLs |
| Features | elevator, terrace, parking |
| Description | only with "Include details" |
| First seen, last seen, days on market | dates and a number |
| Price history | list of date and price |
| Change type | new, price_drop, price_rise, removed, back, unchanged |
| Other portals with the same home | portal, URL, price |

### How to use it

1. Open the Actor and go to the **Input** tab.
2. Add one or more searches. You can type a city, or paste a search URL from a portal.
3. Pick the portals.
4. Click **Start**.
5. Open the **Output** tab, or download the data as JSON, CSV or Excel.

To get alerts, turn on **Monitor changes**, then create a schedule (for example once a day) with the same input. Use the "Changes" view of the dataset, or connect the dataset to email, Slack or a webhook with Apify integrations.

### How much does it cost?

This Actor uses pay per event pricing. You only pay for results. Failed pages cost nothing.

| Event | When you pay | Price per 1,000 |
| --- | --- | --- |
| `listing-scraped` | Once per unique home in the results | $1.50 |
| `browser-listing` | Once per saved home that comes from Idealista (on top of `listing-scraped`) | $4.00 |
| `listing-details` | Once per home, only when Include details is on | $1.00 |
| `change-event` | Once per change (new, price drop, price rise, removed, back), only in monitor mode | $0.50 |
| `apify-actor-start` | When a run starts, set by Apify. One event per GB of memory | $1.25 (about $0.005 per run at the default 4 GB) |

Why is Idealista more expensive? Idealista blocks simple scrapers. We need a real browser and a home proxy for it. That costs us more, so a home from Idealista has an extra `browser-listing` charge. A home that is also on Idealista but comes from a cheaper portal does not pay it.

A removed home only costs a `change-event`. Unchanged homes do not cost a `change-event`.

You can set a maximum cost per run. The Actor checks the total price of all events for a home before it saves it. If they do not all fit, it stops. You never pay for a home that was not saved.

To keep costs low, leave Include details off, keep Max listings per portal small, and turn off the beta portal if you do not need it.

The Pricing tab shows the exact prices.

### Input

A search can be a city:

```json
{
    "searches": [
        { "location": "Barcelona", "operation": "sale", "propertyType": "homes", "maxPrice": 600000, "minRooms": 2, "minSize": 60 }
    ],
    "portals": ["fotocasa", "habitaclia", "pisos"],
    "maxListingsPerPortal": 200,
    "includeDetails": false,
    "monitor": true
}
```

Or a URL copied from a portal after you set your filters there:

```json
{
    "searches": ["https://www.pisos.com/venta/pisos-madrid_capital/"]
}
```

Main options:

- `searches`: cities or portal URLs. `operation` is `sale` or `rent`. `propertyType` is `homes` (default), `any`, `flat`, `house`, `penthouse`, `duplex`, `studio`, `room`, `office`, `commercial`, `garage` or `land`.
- `portals`: `fotocasa`, `habitaclia`, `pisos`, `idealista` (beta).
- `maxListingsPerPortal`: stop after this many homes per portal. Default 200.
- `includeDetails`: add full description and more features.
- `monitor`: remember homes between runs and mark changes.
- `monitorId`: optional name for the saved history. Keep it the same to keep the history after you edit the searches.
- `proxyConfiguration`: Apify proxy settings.

Cities that work by name: Madrid, Barcelona, Valencia, Sevilla, Malaga, Zaragoza, Bilbao, Alicante, Palma, Marbella, Sitges. For other places, paste a search URL from the portal.

### Output

One item per home. Example (shortened, made up data):

```json
{
    "id": "3f2a9c1b7d4e5f60",
    "country": "ES",
    "portal": "fotocasa",
    "portalListingId": "100000001",
    "url": "https://www.fotocasa.es/es/comprar/vivienda/barcelona-capital/100000001/d",
    "alsoListedOn": [{ "portal": "habitaclia", "url": "https://www.habitaclia.com/...", "price": 599000 }],
    "title": "Piso en Carrer d'Exemple",
    "operation": "sale",
    "propertyType": "flat",
    "price": 595000,
    "currency": "EUR",
    "pricePerM2": 10084.75,
    "sizeM2": 59,
    "rooms": 1,
    "bedrooms": 1,
    "bathrooms": 1,
    "floor": null,
    "address": "Carrer d'Exemple, 1, Barri Gotic",
    "city": "Barcelona",
    "region": "Barcelona",
    "postcode": "08002",
    "lat": 41.38,
    "lng": 2.17,
    "agencyName": "Agencia Ejemplo",
    "isPrivateSeller": false,
    "images": ["https://..."],
    "features": ["elevator", "air_conditioner"],
    "firstSeenAt": "2026-10-01T08:00:00.000Z",
    "lastSeenAt": "2026-10-22T08:00:00.000Z",
    "daysOnMarket": 21,
    "priceHistory": [{ "date": "2026-10-01", "price": 620000 }, { "date": "2026-10-15", "price": 595000 }],
    "changeType": "price_drop",
    "scrapedAt": "2026-10-22T08:00:00.000Z"
}
```

A short run summary is saved in the key-value store as `OUTPUT`. It shows pages, listings and blocks per portal.

### Tips

- Start small. Try `maxListingsPerPortal: 20` first.
- In monitor mode, a home is only marked `removed` when its portal was read fully in that run. If a portal hit the limit or a page failed, nothing is marked removed. So set the limit high enough for your search.
- Narrow searches (city plus price and rooms) are faster and cheaper.
- Fotocasa and Habitaclia often hide the exact location. Duplicates are then found by street and number. Homes without a street number are not merged.

### Idealista (beta)

Idealista has strong bot protection. Plain requests are blocked, even with a residential proxy. So the Actor opens Idealista in a real browser (Firefox), with a residential proxy in Spain, one page at a time, like a normal visitor. Listing pages are read from the same browser, so they are fast and cheap. This is slower than the other portals and costs a bit more. The Actor does not solve captchas. If Idealista asks for one, Idealista is skipped and the run summary says so. Idealista often hides the exact map position, so many Idealista homes have no coordinates. Idealista is off by default. You can turn off the browser with "Use a real browser for the beta portal".

### FAQ

**Is it legal to scrape property portals?**
This Actor only collects public listing data that anyone can see without logging in. It does not collect phone numbers or emails. Listings can still contain personal data, for example a private seller's name. Personal data is protected by GDPR in the European Union and by other laws. Do not use personal data unless you have a legitimate reason. If you are not sure, ask a lawyer.

**Why do I get fewer homes than the portal shows?**
The limit `maxListingsPerPortal` stops early. Some filters (rooms, size, type) are applied after reading the page, so portals may return homes that are then skipped.

**Something does not work.**
Please open an issue on the Issues tab with your input. Portals change their pages often and we fix them fast.

# Actor input Schema

## `searches` (type: `array`):

What to search. Each item is either a search URL copied from a portal, or an object like {"location": "Madrid", "operation": "sale", "propertyType": "homes", "minPrice": 200000, "maxPrice": 500000, "minRooms": 2, "minSize": 60}. operation is "sale" or "rent". propertyType is one of: any, homes, flat, house, penthouse, duplex, studio, room, office, commercial, garage, land. A URL search only runs on its own portal. A location search runs on every chosen portal.

## `portals` (type: `array`):

Which portals to search. Idealista is beta and off by default. It has strong bot protection and needs the Apify residential proxy.

## `maxListingsPerPortal` (type: `integer`):

Stop after this many listings on each portal, for all searches together.

## `includeDetails` (type: `boolean`):

Add the full description and more features. On pisos.com and Idealista this opens each listing page, so it is slower. Charged as an extra event per listing.

## `browserForBeta` (type: `boolean`):

Idealista (beta) blocks plain requests. When this is on, the Actor opens Idealista in a real browser (Firefox) with a residential proxy, like a normal visitor. It is slower and costs more. It does not solve captchas: if Idealista asks for one, Idealista is skipped. Other portals always use fast plain requests.

## `monitor` (type: `boolean`):

Remember listings between runs and mark each one as new, price_drop, price_rise, removed, back or unchanged. Run it on a schedule with the same input.

## `monitorId` (type: `string`):

Optional name for the saved monitor state. Use it to keep the same history after you change the searches. If empty, the ID is made from your searches and portals.

## `proxyConfiguration` (type: `object`):

Apify proxy is used by default. Idealista always uses the Spanish residential proxy when it is turned on.

## Actor input object example

```json
{
  "searches": [
    {
      "location": "Barcelona",
      "operation": "sale",
      "propertyType": "homes",
      "maxPrice": 600000,
      "minRooms": 2
    }
  ],
  "portals": [
    "fotocasa",
    "habitaclia",
    "pisos"
  ],
  "maxListingsPerPortal": 20,
  "includeDetails": false,
  "browserForBeta": true,
  "monitor": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `listings` (type: `string`):

No description

## `summary` (type: `string`):

No description

# 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 = {
    "searches": [
        {
            "location": "Barcelona",
            "operation": "sale",
            "propertyType": "homes",
            "maxPrice": 600000,
            "minRooms": 2
        }
    ],
    "maxListingsPerPortal": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("apizilla/spain-property-tracker").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 = {
    "searches": [{
            "location": "Barcelona",
            "operation": "sale",
            "propertyType": "homes",
            "maxPrice": 600000,
            "minRooms": 2,
        }],
    "maxListingsPerPortal": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("apizilla/spain-property-tracker").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 '{
  "searches": [
    {
      "location": "Barcelona",
      "operation": "sale",
      "propertyType": "homes",
      "maxPrice": 600000,
      "minRooms": 2
    }
  ],
  "maxListingsPerPortal": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call apizilla/spain-property-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,apizilla/spain-property-tracker"
        }
    }
}
```

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/DckfSFi0wUHN5VAUF/builds/8dOZuG4uQ9gmYh6ei/openapi.json
