# Idealista Scraper — Spain, Portugal & Italy Property Listings (`tortuga/idealista-scraper`) Actor

Scrape Idealista property listings for sale and rent in Spain, Portugal and Italy: price, €/m², size, rooms, floor, agency, photos, coordinates, energy rating.

- **URL**: https://apify.com/tortuga/idealista-scraper.md
- **Developed by:** [Trevor Ortega](https://apify.com/tortuga) (community)
- **Categories:** Real estate
- **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 scrapeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## Idealista Scraper

Scrape property listings from **Idealista** in Spain (idealista.com), Portugal (idealista.pt) and Italy (idealista.it), for sale and to rent: price, price drop, price per m², size, rooms, floor and lift, parking, tags, agency, coordinates, photos and the listing's own description snippet. Turn on details for the full description, every photo and floor plan, bathrooms, year built, the full feature list, the energy certificate (rating, consumption, emissions), the last-update date and the location hierarchy.

Paste any Idealista search URL with your filters applied, or just type a location slug and set price, size and sort in the input. No login and no browser on your side: one request returns 30 listings. Export to JSON, CSV or Excel, or use the API. You pay per listing you get.

### What data does Idealista Scraper extract?

Every listing from a search page (the `result` event):

| Field | Description |
|---|---|
| `id`, `url` | Idealista listing id and listing URL (`/inmueble/123/`, `/imovel/123/`, `/immobile/123/`) |
| `title`, `propertyType`, `address` | Card title, its property type (`Piso`, `Ático`, `Apartamento T2`, `Bilocale`, `Flat / apartment`, ...) and the address part (street when published, neighbourhood, town) |
| `operation`, `country` | `sale` or `rent`; `ES`, `PT` or `IT` |
| `price`, `currency`, `priceText` | Price in EUR (monthly rent for rentals) and as displayed (`3.000€/mes`) |
| `originalPrice`, `priceDropPercent` | Previous price and drop when Idealista shows a reduction |
| `pricePerSqm` | Price per m² as Idealista prints it (computed from price and size when the card has none) |
| `size`, `sizeLabel` | Size in m² and its qualifier where shown (`área bruta` on idealista.pt) |
| `rooms` | Bedrooms on idealista.com (`3 hab.`) and idealista.pt (`T2` = 2, `T0` = 0); total rooms (`locali`) on idealista.it |
| `floor`, `hasLift`, `exterior` | Floor text as shown (`1ª planta exterior con ascensor`), lift yes/no, exterior/interior |
| `parking`, `parkingIncluded`, `parkingPrice` | Parking line (`Garaje incluido`, `Box 30.000 €`, `Garaje opc. 70 €/mes`), whether it is included and its price |
| `tags`, `ribbon`, `newDevelopment`, `highlight` | Card tags (`Lujo`, `Vistas al mar`, `Alquiler de temporada`), "New homes" ribbon, paid highlight (`Top+`, `Destacado`, `Premium`) |
| `listedLabel` | Recency label on new listings (`11 horas`, `03 oct`) |
| `descriptionSnippet` | The description excerpt shown on the card |
| `agencyName`, `agencyUrl`, `agencyLogo`, `isProfessional` | Advertising agency and its Idealista page; `false` for private advertisers |
| `latitude`, `longitude`, `locationExact` | Map coordinates. `locationExact: false` means the advertiser hid the exact address and the point is Idealista's approximate one |
| `photoCount`, `photos` | Number of photos and the first 3 photos in large size (all photos with details on) |
| `hasFloorPlan`, `hasVideo`, `hasVirtualTour` | Media available on the listing |
| `position`, `page`, `sourceUrl`, `scrapedAt` | Rank on the page, page number, the search it came from, time of scraping |

With **Include listing details** on, or for listing URLs (the extra `detail` event), each listing also gets:

| Field | Description |
|---|---|
| `description` | Full description as plain text with line breaks |
| `bathrooms`, `yearBuilt`, `usableSize` | From the feature list when published |
| `features`, `featureGroups` | Every feature line (`Armarios empotrados`, `Calefacción individual: Gas natural`, `Terraço e varanda`), also grouped by section (basic features, building, equipment, energy) |
| `energyRating`, `energyConsumption` | Energy class (`A+` ... `G`) and consumption (`134 kWh/m² año`) |
| `emissionsRating`, `energyEmissions` | Emissions class and value (Spain) |
| `energyStatus` | Text when there is no class yet (`In corso`, `En trámite`, exempt) |
| `photos`, `photoTags`, `floorPlans` | All photos (large), their room tags, floor plan images |
| `updatedText`, `updatedDate` | "Listing updated on 5 October" as shown and as an ISO date (Idealista omits the year; the most recent past date is used) |
| `locationHierarchy` | Street, neighbourhood, district, city, region as listed under "Location" |
| `reference`, `advertiserType` | Advertiser's reference code; `Profesional` / `Promotora` / `Professionista` / private |
| `detailError` | Why details are missing for this listing (e.g. the listing was removed), otherwise `null` |

Idealista does not publish a precise listing date on search pages; `updatedDate` (details) is the last update the listing page shows. Phone numbers and e-mail addresses sit behind "Ver teléfono" / contact forms and are not collected. Private advertisers' names are not exported.

### How to scrape Idealista listings

1. Either paste **Start URLs**: any Idealista search page from idealista.com, idealista.pt or idealista.it with filters and sort chosen on the site (homes, rooms, offices, garages, land, new developments ... all work), or single listing URLs.
2. Or type **Locations** as they appear in Idealista URLs (`madrid-madrid`, `barcelona-barcelona`, `valencia-valencia`, `lisboa`, `porto`, `milano-milano`, `roma-roma`, or a district like `madrid/centro`), choose the **Country** and **For sale or to rent**, and optionally **Min / Max price**, **Min / Max size** and **Sort by**.
3. Set **Max items** (and **Max pages per search**) to cap the run and the cost.
4. Turn on **Include listing details** only if you need the fields from the listing page.
5. Click **Start** and download the dataset as JSON, CSV or Excel, or read it with the API.

#### Input example

```json
{
  "locations": ["madrid-madrid"],
  "country": "es",
  "operation": "sale",
  "minPrice": 200000,
  "maxPrice": 500000,
  "minSize": 60,
  "sort": "newest",
  "maxItems": 200,
  "includeDetails": false
}
```

The same with a URL: `{"startUrls": [{"url": "https://www.idealista.com/venta-viviendas/madrid-madrid/con-precio-hasta_500000,precio-desde_200000,metros-cuadrados-mas-de_60/?ordenado-por=fecha-publicacion-desc"}]}`.

#### Output example

One listing with details (long arrays shortened):

```json
{
  "id": "111278716",
  "url": "https://www.idealista.com/inmueble/111278716/",
  "title": "Piso en Calle de Ibiza, Ibiza, Madrid",
  "propertyType": "Piso",
  "operation": "sale",
  "country": "ES",
  "address": "Calle de Ibiza, Ibiza, Madrid",
  "price": 895000,
  "currency": "EUR",
  "priceText": "895.000€",
  "originalPrice": 945000,
  "priceDropPercent": 5,
  "pricePerSqm": 8606.0,
  "size": 104,
  "rooms": 3,
  "bathrooms": 2,
  "floor": "1ª planta exterior con ascensor",
  "hasLift": true,
  "exterior": true,
  "parking": null,
  "tags": [],
  "highlight": "Top+",
  "agencyName": "aProperties Real Estate Madrid",
  "agencyUrl": "https://www.idealista.com/pro/aproperties-mad/",
  "isProfessional": true,
  "latitude": 40.4182104,
  "longitude": -3.6722192,
  "locationExact": false,
  "photoCount": 49,
  "photos": ["https://img4.idealista.com/blur/WEB_DETAIL/0/id.pro.es.image.master/52/48/fe/1431430872.jpg", "..."],
  "hasFloorPlan": true,
  "hasVideo": true,
  "hasVirtualTour": true,
  "description": "PISO EN VENTA REFORMADO EN CALLE IBIZA\n\nPiso exterior en venta en Calle Ibiza – luminosidad, elegancia y confort ...",
  "features": ["104 m² construidos", "3 habitaciones", "2 baños", "Segunda mano/buen estado", "Armarios empotrados", "Orientación norte", "Construido en 1951", "..."],
  "yearBuilt": 1951,
  "energyRating": "E",
  "energyConsumption": "134 kWh/m² año",
  "emissionsRating": "E",
  "energyEmissions": "34 kg CO2/m² año",
  "reference": "VM2604052",
  "locationHierarchy": ["Calle de Ibiza", "Barrio Ibiza", "Distrito Retiro", "Madrid", "Madrid capital, Madrid"],
  "updatedText": "Anuncio actualizado el 5 de octubre",
  "updatedDate": "2026-10-05",
  "floorPlans": ["https://img4.idealista.com/blur/WEB_DETAIL/0/id.pro.es.image.master/95/f6/08/1431615377.jpg", "..."],
  "advertiserType": "Profesional",
  "position": 1,
  "page": 1,
  "sourceUrl": "https://www.idealista.com/venta-viviendas/madrid-madrid/",
  "scrapedAt": "2026-10-05T21:30:00+00:00",
  "detailError": null
}
```

Without details the same keys are present; the detail-only ones are `null` or empty.

### Monitor changes on a schedule

Turn on **Only new or changed items** to watch a search: each run remembers what earlier runs returned and outputs only listings that are new or whose price or crossed-out old price (a price drop) changed. Unchanged listings are dropped before any listing page is opened, so they are not charged. **Max items** then sets how many listings are checked per run; use the `newest` sort so new listings land in that window (30 listings = one search page = one Unblocker request). Save the input as a task and add a Schedule in Apify (for example daily), and give each watch its own **Monitoring key** if several schedules use the same search. Every output item has `changeType` (`new` or `changed`), and changed items have `previousValues` with the old values, e.g. `{"price": 895000}`.

### How much does it cost to scrape Idealista?

Pay per event, plus the Apify platform usage of your run:

- **Listing** (`result`): **$0.0007** per listing saved.
- **Listing details** (`detail`): **+$0.0007** per listing whose page was opened (Include listing details, or listing URLs).

**Proxy note.** Idealista blocks datacenter and residential IPs with DataDome, so the Actor uses Apify's **UNBLOCKER** proxy by default. Every successful request costs about **$0.0015** of proxy usage on your account. A search page is one request for 30 listings (about $0.00005 per listing); with details, each listing is one more request. Worked example for 1,000 listings: without details about $0.70 in events + 34 requests ≈ $0.05 proxy; with details about $1.40 in events + 1,034 requests ≈ $1.55 proxy. Failed requests are not billed. The run summary (key-value store, `SUMMARY`) reports `successfulRequests` so you can check.

### Does it work for Portugal and Italy, and in English?

Yes. idealista.pt and idealista.it use the same page layout; the Actor reads all three in any of their languages (`/en/`, `/ca/`, ... URLs work). Field names are the same for every country; text values (`floor`, `features`, `tags`) stay in the page's language. Note that `rooms` means bedrooms in Spain and Portugal but total rooms (*locali*) in Italy.

### How many results can I get?

Idealista shows 30 listings per page and the Actor follows the page's own "next" link, up to **Max pages per search** (default 10 = 300 listings). For whole cities split the search by district, price band or size band (several Locations or Start URLs). A listing that appears in two of your searches is saved once.

### Does it need a login or cookies?

No. It reads public search and listing pages only. Phone numbers, contact forms and anything behind a login are not touched.

### Is it legal to scrape Idealista?

The Actor collects public listing data that Idealista shows to every visitor: prices, property features, agency names and photos. It does not collect phone numbers, e-mail addresses or private advertisers' names. Descriptions can mention people; you are responsible for how you use the data under GDPR and Idealista's terms. If in doubt, consult a lawyer.

# Actor input Schema

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

Idealista search pages from idealista.com (Spain), idealista.pt (Portugal) or idealista.it (Italy), with any filters and sort order applied in the browser, e.g. https://www.idealista.com/venta-viviendas/madrid-madrid/ or https://www.idealista.pt/arrendar-casas/lisboa/. Homes, offices, garages, land and other search types work the same way. A single listing URL (…/inmueble/123/, …/imovel/123/, …/immobile/123/) returns that listing with full details. Leave empty if you use Locations instead.

## `locations` (type: `array`):

Alternative to Start URLs: location slugs exactly as they appear in Idealista URLs, e.g. "madrid-madrid", "barcelona-barcelona", "malaga-malaga", "lisboa", "porto", "milano-milano", "roma-roma", or a district such as "madrid/centro". Searches homes in the country and for the operation chosen below, with the filters below.

## `country` (type: `string`):

Used with Locations: which Idealista site to search.

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

Used with Locations.

## `minPrice` (type: `integer`):

Used with Locations. Sale price, or monthly rent for rentals.

## `maxPrice` (type: `integer`):

Used with Locations. Sale price, or monthly rent for rentals.

## `minSize` (type: `integer`):

Used with Locations.

## `maxSize` (type: `integer`):

Used with Locations.

## `sort` (type: `string`):

Used with Locations. Default is Idealista's relevance order (paid 'Top' listings first). 'Newest' sorts by publication date (by last update on idealista.pt).

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

Stop after this many listings in total (across all searches). Keeps cost predictable. Idealista shows 30 listings per page.

## `maxPagesPerSearch` (type: `integer`):

Stop each search after this many result pages (30 listings each). Use filters or several locations to get more results.

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

Also open every listing page to get the full description, all photos and floor plans, bathrooms, year built, full feature list, energy rating and consumption, last-update date, location hierarchy and reference. One extra request per listing (about $0.0015 of Unblocker proxy usage each) and an extra 'detail' event per listing.

## `onlyNewOrChanged` (type: `boolean`):

Remember what earlier runs of this search returned and output only new listings or listings whose price or crossed-out old price (price drop) changed. Use with a Schedule; unchanged listings are not charged and their listing pages are not opened. With this on, Max items is the number of listings checked per run (e.g. the newest 30 with sort 'newest', one Unblocker request per 30).

## `monitoringKey` (type: `string`):

Name for this watch, so several schedules on the same search stay separate; defaults to a key derived from the search input

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

Idealista blocks datacenter and residential IPs (DataDome), so Apify's UNBLOCKER proxy group is the default. Each successful request costs about $0.0015 of proxy usage on your account: one request per 30 listings from search pages, plus one per listing with 'Include listing details'.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.idealista.com/venta-viviendas/madrid-madrid/"
    }
  ],
  "country": "es",
  "operation": "sale",
  "sort": "",
  "maxItems": 100,
  "maxPagesPerSearch": 10,
  "includeDetails": false,
  "onlyNewOrChanged": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "UNBLOCKER"
    ]
  }
}
```

# Actor output Schema

## `results` (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 = {
    "startUrls": [
        {
            "url": "https://www.idealista.com/venta-viviendas/madrid-madrid/"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "UNBLOCKER"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("tortuga/idealista-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 = {
    "startUrls": [{ "url": "https://www.idealista.com/venta-viviendas/madrid-madrid/" }],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["UNBLOCKER"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("tortuga/idealista-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 '{
  "startUrls": [
    {
      "url": "https://www.idealista.com/venta-viviendas/madrid-madrid/"
    }
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "UNBLOCKER"
    ]
  }
}' |
apify call tortuga/idealista-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tortuga/idealista-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/CPc0ek52lSd6MIDi3/builds/rj2vQEpre6QE4KRzi/openapi.json
