# Idealista Property Scraper (`devilscrapes/idealista-property-scraper`) Actor

Scrape normalized property listings from idealista.com (Spain), idealista.it (Italy), and idealista.pt (Portugal) in one run — unified EUR/m² schema, sale or rent, plus agency and lister contact enrichment neither incumbent ships together.

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

## Pricing

Pay per event

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

<div align="center">
  <img src=".actor/icon.svg" width="160" alt="Devil Scrapes mark" />

## Idealista Property Scraper

**💰 $5.20 / 1 000 results**  ·  pay only for results  ·  no credit card to try

*We do the dirty work so your dataset stays clean.* 😈

Scrape normalized property listings from idealista.com (Spain), idealista.it (Italy), and idealista.pt (Portugal) in one run — unified EUR/m² schema, sale or rent, plus agency and lister contact enrichment neither incumbent ships together.

</div>

***

### 🎯 What this scrapes

Idealista is the shared real-estate platform behind idealista.com (Spain), idealista.it (Italy), and idealista.pt (Portugal) — the #1 property portal in every one of those markets. Cross-border investors and PropTech teams today stitch together three separate scrapes with three different schemas just to compare a Madrid flat to a Lisbon apartment. This Actor wraps all three TLDs into a single run and a single normalized schema — EUR pricing, m² area, a shared property-type taxonomy, and EU energy ratings — so a country field is the only thing that changes between rows. On top, it enriches each listing with agency and lister details (name, phone when publicly exposed, particular vs. professional, agent reference id) that most Idealista scrapers skip entirely.

### 🔥 What we handle for you

- 🛡️ **A real browser engine, not a bare HTTP client** — we drive an anti-detection browser through a same-context warm-up sequence so the target sees genuine browser traffic, never a bot signature.
- 🌐 **Residential proxy rotation** via Apify Proxy, pinned per country — a fresh exit IP on every retry, never the same flagged exit twice.
- 🧱 **A geo-splash guard** on every response — a mismatched exit can return a different country's listings at a clean 200; we catch and reject that before it ever reaches your dataset.
- 🔁 **Automatic retries** with a freshly rotated proxy session on any block signal.
- 🧊 **Clean, typed dataset rows** — Pydantic-validated, ISO-8601 timestamps, stable IDs, JSON / CSV / Excel export straight from the Apify Console.
- 💰 **Pay-Per-Event pricing** — you only pay for results that hit your dataset. No data, no charge.

### 💡 Use cases

- **Cross-border deal screening** — compare sale listings in Madrid, Rome, and Lisbon side by side without normalizing three different scrapers' output yourself.
- **Rental market benchmarking** — track price-per-m² across ES/IT/PT rental markets in one dataset.
- **Agency/lister lead lists** — collect agency names, phone numbers (when public), and agent reference codes behind active listings.
- **Relocation and expat services** — build a unified property feed spanning three Southern European markets for client-facing search tools.

### ⚙️ How to use it

1. Click **Try for free** at the top of the page.
2. Fill in the input form — most fields have sensible defaults.
3. Click **Start**. Output streams into the run's dataset.
4. Export from **Storage → Dataset** as JSON, CSV, or Excel — or fetch via the API.

### 📥 Input

| Field | Type | Required | Default | Notes |
|---|---|:--:|---|---|
| `countries` | `array` | no | \['ES'] | Countries to run (ES/IT/PT). Each country runs independently — one blocked country doesn't stop the others. |
| `searchUrls` | `array` | no | '—' | Direct idealista.com / idealista.it / idealista.pt search URLs to scrape as-is. Country is inferred from the TLD. Up to… |
| `location` | `string` | no | 'madrid-madrid' | idealista's own two-part search slug — municipality-province, e.g. 'madrid-madrid' (most municipalities do NOT share… |
| `operation` | `string` | no | 'sale' | Sale vs. rent. Country-agnostic — mapped to the right per-TLD slug internally. |
| `propertyType` | `string` | no | 'homes' | Shared property-type taxonomy across all three TLDs. |
| `maxResultsPerCountry` | `integer` | no | 100 | Cap on rows per country; each country's cap is independent. |
| `enrichAgencyContact` | `boolean` | no | True | Fetch each listing's detail page for agency/agent fields (name, phone when exposed, particular vs. professional, agent… |
| `proxyCountryMap` | `object` | no | {'ES': 'ES', 'IT': 'IT', 'PT': 'PT'} | Override the proxy exit country per target country. Idealista hard-localizes by IP — a mismatched exit silently returns… |
| `proxyConfiguration` | `object` | no | {'useApifyProxy': True, 'apifyProxyGroups': \['RESIDENTIAL']} | Mandatory-by-default RESIDENTIAL — country\_code is resolved per-request from Proxy country map. |

#### Example input

```json
{
  "countries": [
    "ES"
  ],
  "location": "madrid-madrid",
  "operation": "sale",
  "propertyType": "homes",
  "maxResultsPerCountry": 3,
  "enrichAgencyContact": true,
  "proxyCountryMap": {
    "ES": "ES",
    "IT": "IT",
    "PT": "PT"
  },
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

### 📤 Output

Every row is one dataset item.

| Field | Type | Notes |
|---|---|---|
| `listing_id` | `string` | Idealista ID, per-TLD. |
| `url` | `string` | Canonical detail URL. |
| `country` | `string` | Normalization key — 'ES', 'IT', or 'PT'. |
| `operation` | `string` | 'sale' or 'rent', as requested. |
| `property_type` | `string` | Shared taxonomy value. |
| `title` | `string` | Listing headline. |
| `price` | `['number', 'null']` | Asking price. |
| `currency` | `string` | Always 'EUR'. |
| `price_per_sqm` | `['number', 'null']` | Published or computed. |
| `area_sqm` | `['number', 'null']` | Normalized to m². |
| `rooms` | `['integer', 'null']` | Room count. |
| `bathrooms` | `['integer', 'null']` | Bathroom count. |
| `floor` | `['string', 'null']` | Raw label, not normalized (per-market variance). |
| `energy_rating` | `['string', 'null']` | EU EPC letter A–G. |
| `location_address` | `['string', 'null']` | Full address, when public. |
| `location_neighborhood` | `['string', 'null']` | Neighborhood, when public. |
| `location_city` | `string` | City. |
| `location_province` | `['string', 'null']` | Province/region. |
| `latitude` | `['number', 'null']` | WGS-84 map pin latitude. |
| `longitude` | `['number', 'null']` | WGS-84 map pin longitude. |
| `agency_name` | `['string', 'null']` | Enrichment field. |
| `agency_phone` | `['string', 'null']` | Only if publicly exposed. |
| `listing_owner_type` | `['string', 'null']` | 'particular' or 'professional'. |
| `agent_reference_id` | `['string', 'null']` | Agency's own reference code. |
| `listed_date` | `['string', 'null']` | ISO YYYY-MM-DD. |
| `updated_date` | `['string', 'null']` | ISO YYYY-MM-DD. |
| `description_snippet` | `['string', 'null']` | First ~300 chars. |
| `image_urls` | `array` | Gallery photos (empty OK). |
| `scraped_at` | `string` | ISO-8601 UTC row-creation timestamp. |

#### Example output

```json
{
  "listing_id": "34567890",
  "url": "https://www.idealista.com/inmueble/34567890/",
  "country": "ES",
  "operation": "sale",
  "property_type": "homes",
  "title": "Piso en Malasa\u00f1a, Madrid",
  "price": 425000.0,
  "currency": "EUR",
  "price_per_sqm": 5312.5,
  "area_sqm": 80.0,
  "rooms": 2,
  "bathrooms": 1,
  "floor": "3\u00ba ext",
  "energy_rating": "D",
  "location_address": null,
  "location_neighborhood": "Malasa\u00f1a-Universidad",
  "location_city": "Madrid",
  "location_province": "Madrid",
  "latitude": 40.4265,
  "longitude": -3.7038,
  "agency_name": "Example Inmobiliaria",
  "agency_phone": "+34910000000",
  "listing_owner_type": "professional",
  "agent_reference_id": "REF-8891",
  "listed_date": "2026-07-02",
  "updated_date": "2026-08-05",
  "description_snippet": "Precioso piso reformado a estrenar en pleno barrio...",
  "image_urls": [
    "https://img.idealista.com/example1.jpg"
  ],
  "scraped_at": "2026-08-11T12:00:00+00:00"
}
```

### 💰 Pricing

Pay-Per-Event — you pay only when these events fire:

| Event | USD | What it is |
|---|---:|---|
| `actor-start` | $0.2 | One-off warm-up charge per run |
| `result-row` | $0.005 | Per unique dataset item |

Example: 1 000 results at the rates above ≈ **$5.20**. No subscription, no minimum, no card to start — Apify gives every new account $5 of free credit.

### 🚧 Limitations

v1 covers Spain, Italy, and Portugal only — Idealista doesn't operate in other markets. New-development/promoter microsites, the mortgage calculator, area-guide pages, and saved-search/alert features behind an agency login are out of scope. agency\_phone ships verbatim, unverified, only when publicly rendered on the listing. floor stays a raw label — market-specific floor conventions aren't normalized across countries.

### ❓ FAQ

**Do I need an Idealista account or API key?**

No. This Actor scrapes Idealista's own publicly listed property data across all three TLDs — no login, no API key.

**Why does this default to a residential proxy pinned per country?**

Idealista localizes by IP — a mismatched exit can silently return a different country's listings. We default every request to a RESIDENTIAL proxy pinned to the target country so ES/IT/PT runs never cross-contaminate.

**Can I mix countries in one run?**

Yes — set countries to any combination of ES/IT/PT (or supply direct searchUrls), and each country is fetched independently; one blocked country never stops the others.

**Does agency\_phone always populate?**

Only when enrichAgencyContact is true and the number is publicly exposed on the listing; otherwise it's null.

### 💬 Your feedback

Spotted a bug, hit a weird edge case, or need a new field? Open an
issue on the Actor's **Issues** tab on Apify Console — we ship
fixes weekly and we read every report.

***

<div align="center">

Built by **[Devil Scrapes](https://apify.com/DevilScrapes)** 😈 — a small fleet of
opinionated public-data Actors. Honest pricing, real engineering, zero fine print.

</div>

# Actor input Schema

## `countries` (type: `array`):

Countries to run (ES/IT/PT). Each country runs independently — one blocked country doesn't stop the others.

## `searchUrls` (type: `array`):

Direct idealista.com / idealista.it / idealista.pt search URLs to scrape as-is. Country is inferred from the TLD. Up to 50. One of Search URLs or (Countries + Location) is required.

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

idealista's own two-part search slug — municipality-province, e.g. 'madrid-madrid' (most municipalities do NOT share their province's name; find the exact slug by searching the location on idealista.com and copying it from the resulting URL). Required (together with Countries) if Search URLs is unset.

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

Sale vs. rent. Country-agnostic — mapped to the right per-TLD slug internally.

## `propertyType` (type: `string`):

Shared property-type taxonomy across all three TLDs.

## `maxResultsPerCountry` (type: `integer`):

Cap on rows per country; each country's cap is independent.

## `enrichAgencyContact` (type: `boolean`):

Fetch each listing's detail page for agency/agent fields (name, phone when exposed, particular vs. professional, agent reference id). OFF by default: a measured 100-row run costs ~17x more residential-proxy transfer per row than leaving it off, and the detail-page parser is not yet extracting any additional fields on real listings. Leave this off until a future release confirms it works.

## `proxyCountryMap` (type: `object`):

Override the proxy exit country per target country. Keys are a subset of ES/IT/PT. Idealista hard-localizes by IP — a mismatched exit silently returns the wrong country's listings.

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

Mandatory-by-default RESIDENTIAL — country\_code is resolved per-request from Proxy country map, not set directly here.

## Actor input object example

```json
{
  "countries": [
    "ES"
  ],
  "location": "madrid-madrid",
  "operation": "sale",
  "propertyType": "homes",
  "maxResultsPerCountry": 20,
  "enrichAgencyContact": false,
  "proxyCountryMap": {
    "ES": "ES",
    "IT": "IT",
    "PT": "PT"
  },
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

All dataset items as JSON.

## `datasetItemsCsv` (type: `string`):

Same data exported to CSV.

## `datasetView` (type: `string`):

Open the run dataset in the Console.

# 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 = {
    "countries": [
        "ES"
    ],
    "location": "madrid-madrid",
    "operation": "sale",
    "propertyType": "homes",
    "maxResultsPerCountry": 20,
    "enrichAgencyContact": false,
    "proxyCountryMap": {
        "ES": "ES",
        "IT": "IT",
        "PT": "PT"
    },
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("devilscrapes/idealista-property-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 = {
    "countries": ["ES"],
    "location": "madrid-madrid",
    "operation": "sale",
    "propertyType": "homes",
    "maxResultsPerCountry": 20,
    "enrichAgencyContact": False,
    "proxyCountryMap": {
        "ES": "ES",
        "IT": "IT",
        "PT": "PT",
    },
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("devilscrapes/idealista-property-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 '{
  "countries": [
    "ES"
  ],
  "location": "madrid-madrid",
  "operation": "sale",
  "propertyType": "homes",
  "maxResultsPerCountry": 20,
  "enrichAgencyContact": false,
  "proxyCountryMap": {
    "ES": "ES",
    "IT": "IT",
    "PT": "PT"
  },
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call devilscrapes/idealista-property-scraper --silent --output-dataset

```

## MCP server setup

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