# Idealista Scraper - Spain Real Estate (`scrapesage/idealista-scraper`) Actor

Scrape Spain property listings from idealista.com search URLs: price, price per m2, rooms, area, floor, agent, description and images. Bypasses idealista's anti-bot protection. Sale and rental. Paste search URLs or import a file.

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

## Pricing

from $1.65 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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

## Idealista Scraper - Spain Real Estate Listings

> This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by idealista or any of its subsidiaries. All trademarks mentioned are the property of their respective owners.

Scrape property listings from **idealista.com**, Spain's largest real-estate portal, straight into **JSON, CSV or
Excel**. Paste any idealista.com search URL - **for sale** or **for rent**, any city, province or neighbourhood -
and get clean rows with **price**, **price per m2**, **rooms**, **built area**, **floor**, **listing agency**,
**description** and **photo**.

idealista sits behind enterprise anti-bot protection that blocks ordinary HTTP scrapers *and* ordinary headless
browsers. This actor **handles that for you**: no proxy setup, no browser fingerprinting, no captcha service, and
**no API key on your side**. You paste a search URL and get rows.

### What you get per listing

| Field | Type | Meaning |
|---|---|---|
| `id` | string | idealista listing id (e.g. `110308026`) |
| `title` | string | Listing headline, e.g. *"Piso en Calle de María de Molina, Castellana, Madrid"* |
| `url` | string | Canonical `idealista.com/inmueble/<id>/` link (query strings stripped, so it de-duplicates cleanly) |
| `price` | number | Asking price for sale, or monthly rent on rental searches |
| `currency` | string | Always `EUR` |
| `pricePerSqm` | number | Price divided by built area, computed for you - the field agents and analysts actually compare on |
| `rooms` | number | Bedrooms (`habitaciones`) |
| `areaSqm` | number | Built area in m2 |
| `floor` | string | Floor **plus** exterior/interior **plus** lift, verbatim, e.g. *"3ª planta exterior con ascensor"* |
| `agent` | string | Listing agency or advertiser (e.g. *"Clikalia España"*) |
| `description` | string | The description teaser idealista prints on the results card |
| `isNew` | boolean | `true` only on new-build (*obra nueva*) listings - see note below |
| `tags` | array | Badges idealista puts on some cards, e.g. `["Lujo"]` - see note below |
| `image` | string | Cover photo URL |
| `type` | string | Record type, always `listing` (one mode, one record shape, no blank columns) |
| `sourceUrl` | string | The search URL this row came from, so you can merge many searches and still tell them apart |
| `scrapedAt` | string | ISO timestamp of the fetch |

**Field coverage, measured on a real 60-listing Madrid sale run:** `price`, `pricePerSqm`, `rooms`, `areaSqm`,
`agent`, `description`, `image`, `title`, `url` were present on **60 of 60**. `floor` on **56 of 60 (93%)** -
idealista omits it on some property types. `tags` on **14 of 60 (23%)** and `isNew` on **0 of 60**: those two are
*per-listing badges*, not fields every property has. `tags` is an empty array when idealista shows no badge, and
`isNew` is only true for the new-build subset - a resale search legitimately returns none. That is the source's
behaviour, not a gap in extraction, and every other field arrives populated.

### Example output

One real row, exactly as written to the dataset:

```json
{
  "type": "listing",
  "id": "110308026",
  "title": "Piso en Calle de María de Molina, Castellana, Madrid",
  "url": "https://www.idealista.com/inmueble/110308026/",
  "price": 790000,
  "currency": "EUR",
  "rooms": 2,
  "areaSqm": 70,
  "pricePerSqm": 11286,
  "floor": "1ª planta exterior con ascensor",
  "agent": "Clikalia España",
  "isNew": null,
  "tags": [],
  "image": "https://img4.idealista.com/blur/591_420_mq/0/id.pro.es.image.master/b4/49/2c/1449827824.jpg",
  "sourceUrl": "https://www.idealista.com/venta-viviendas/madrid-madrid/",
  "scrapedAt": "2026-08-16T21:04:21.007Z"
}
```

### Input

```json
{
  "startUrls": ["https://www.idealista.com/venta-viviendas/madrid-madrid/"],
  "maxPagesPerUrl": 5,
  "maxItems": 1000
}
```

- **idealista.com search URLs** - build the search you want on idealista.com (city, price range, rooms, m2, lift,
  whatever filters you like), then paste the URL. One per line, as many as you want. Every filter you set on the
  site is carried through, because the actor scrapes exactly the URL you gave it.
- **Import URLs from a file** - paste a whole list, or give a single link to a public `.txt`/`.csv`, a Google
  Sheet or Drive link, or an Apify key-value-store record. Blank lines and `#` comments are ignored. A file that
  cannot be read tells you so and charges nothing.
- **Max pages per URL** - up to 30 listings per page (default 10 pages).
- **Max listings** - overall cap across all URLs.
- **Output fields** - tick only the columns you want and every record is trimmed to exactly those, which keeps
  CSV and Google Sheets exports lean.

Leave everything empty and the run scrapes a small one-page sample search, so you can see the output shape before
committing to a real job.

### Which idealista URLs work

| Search type | URL pattern | Status |
|---|---|---|
| Homes for sale | `idealista.com/venta-viviendas/<city>/` | Validated |
| Homes for rent | `idealista.com/alquiler-viviendas/<city>/` | Validated |
| Any filtered search | the same paths with idealista's filter segments appended | Validated |
| Other property types (`venta-locales`, `alquiler-oficinas`, `venta-garajes`, `obra-nueva`, ...) | same result-card layout | Usually works, not part of the validated set |

Validated across **Madrid, Barcelona, Valencia, Sevilla, Málaga and Zaragoza**, sale and rent. Pagination is
automatic (`/pagina-2.htm`, `/pagina-3.htm`, ...) up to your page limit, and the actor stops early on its own when
idealista stops advancing the results, so you never pay for a page of repeats. Rows are de-duplicated by listing
URL across every start URL in the run.

### Reliability

idealista is protected by enterprise anti-bot (DataDome), which is why plain scrapers - and plain headless
browsers - come back with a challenge page instead of listings. This actor routes every request through a premium
unblocking layer that solves the challenge and returns the real server-rendered page, then parses the listing
cards.

On top of that:

- **Short-render detection.** idealista sometimes serves a partially rendered results page - anywhere from 0 to
  \~half of the 30 cards a full page holds. The actor reads each page's own result count and pagination, refetches
  any page that falls short of what the search says it should hold (keeping the best of up to 3 fetches), and
  never mistakes a bad render for the end of the search. A page that stays short even after refetching is
  disclosed in the run's status message instead of being passed off as complete.
- **Wall-clock budget.** Long runs stop cleanly before the platform timeout, save everything collected up to that
  point, and say so in the run status instead of dying as TIMED-OUT.
- **Every ending explains itself.** A run that finds nothing tells you why - bad search URL, unreadable import
  file, or a fetch that failed - and bills **$0**.

### What people use it for

- **Comparables and valuations** - pull every flat in a barrio with `pricePerSqm` already computed.
- **Rental yield analysis** - scrape the same area twice, once `venta-viviendas` and once `alquiler-viviendas`,
  and divide.
- **Market monitoring** - schedule a daily or weekly run per city and track price movements, new supply and how
  long listings last.
- **Agency and lead intelligence** - `agent` gives you who is listing what, and at what volume, in any market.
- **Investment screening** - filter on price per m2, floor, lift and area across Madrid, Barcelona, Valencia,
  Málaga, Alicante, Sevilla, Bilbao or the Costa del Sol.
- **Feeding AI agents and data pipelines** - see the MCP section below.

### Honest limits

- **`isNew` and `tags` are per-listing badges, not universal fields.** Quantified above: `isNew` is true only for
  *obra nueva* listings, `tags` fills only when idealista prints a badge such as `Lujo`. Both are empty on
  ordinary resale listings by design.
- **`description` is the results-card teaser**, which idealista itself truncates, so it can end mid-sentence. It
  is the description as published on the search page, not the full text of the detail page.
- **A page can rarely still come back short.** A full idealista results page holds 30 cards, but its anti-bot
  layer sometimes serves a partially rendered page - we have measured renders of 2, 11 and 16 cards on pages
  that carry 30. The actor detects a short render against the search's own result count and refetches it
  automatically; in our latest test battery every short render recovered to the full 30, with a 5-page Madrid
  crawl returning 150 of 150. If a page ever stays short after all refetches, the run's status message says so
  rather than presenting the result as complete - and you are only billed for rows actually delivered. Within a
  run, duplicate listings are dropped before billing, so overlapping searches cost nothing extra.
- **Runs are a little slower than an unprotected site**, because every page goes through the unblocking layer.
  That is the cost of getting past idealista's defences reliably rather than intermittently.

### Pricing

**Pay per listing** - you are charged only for listings actually written to the dataset. A run that finds nothing,
or that cannot read your import file, costs **$0**. The rate covers the premium anti-bot unblocking that makes
idealista scrapable at all, and it drops automatically on higher Apify plans (tiered pay-per-event pricing).

### Output views

- **Listings** - a table of title, price, area m2, rooms, EUR/m2 and the listing URL, ready to eyeball or export
  to CSV, Excel, JSON or Google Sheets. The full record with every field is always in the dataset behind it.

### Use with AI assistants (MCP)

Available through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp), so an assistant or
agent can pull live Spain property listings past idealista's anti-bot in a single call - compare prices per m2
across barrios, watch a market for new supply, or enrich an address list without anyone touching a browser.

### Agent-ready: autonomous payments (x402 & Skyfire)

This actor is **agent-ready** — AI agents can discover it, run it, and **pay for it autonomously**, with no Apify account and no human in the loop. It uses [pay-per-event](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event) pricing and [limited permissions](https://docs.apify.com/platform/actors/development/permissions), so it qualifies for Apify's agentic-payment standards:

- **[x402](https://docs.apify.com/platform/integrations/x402)** — an open, HTTP-native payment protocol. Agents pay per run in USDC on the Base network directly through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp) — no account, no API key.
- **[Skyfire](https://docs.apify.com/platform/integrations/skyfire)** — agent-to-service payments for fully autonomous AI-agent workflows.

Building an AI agent, MCP tool, or autonomous data pipeline? This scraper is ready to plug in and pay as it goes.

### FAQ

**Do I need an idealista account, API key or proxies?**
No. Everything needed to reach idealista is built in.

**Can I scrape rentals as well as sales?**
Yes. `alquiler-viviendas` URLs work exactly like `venta-viviendas` ones, and `price` is then the monthly rent.

**How many listings will I get per search URL?**
Up to 30 per page. `maxPagesPerUrl` decides how deep to go, and `maxItems` caps the whole run. A 5-page Madrid
search has returned 150 unique listings (30 per page). Short page renders are refetched automatically (see
Honest limits), and the status message tells you if any page stayed short. You only ever pay for the rows you
actually receive.

**Can I run it on a schedule?**
Yes - schedule the actor or any saved task in Apify and the results land in a dataset each time, ready for the
API, a webhook, or an export to Sheets, S3 or your warehouse.

**Does it get the full description and every photo from the detail page?**
No. It reads idealista's search-results cards, which carry the teaser description and the cover photo. That is
what keeps it fast and cheap across thousands of listings.

### Disclaimer

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by idealista or any of its
subsidiaries. All trademarks mentioned are the property of their respective owners. It collects only publicly
available listing information and is intended for lawful research, market analysis and comparable-pricing use. You
are responsible for how you use the data, including compliance with applicable law.

# Actor input Schema

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

idealista.com search or listing-page URLs, one per line. Build a search on idealista.com with your filters, then paste the URL. Sale and rental all work. <b>Leave empty and the run returns a small free sample.</b>

## `startUrlsFromFile` (type: `string`):

Bulk-load idealista.com URLs. Either <b>paste the whole list</b> (one per line), or give <b>a single link</b> to a public <code>.txt</code>/<code>.csv</code>, a Google Sheet/Drive link, or an Apify key-value-store record. A file that cannot be read says so and charges nothing.

## `maxPagesPerUrl` (type: `integer`):

How many result pages to follow for each start URL (about 30 listings per page).

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

Overall cap across all URLs. You are only charged for listings actually saved.

## `outputFields` (type: `array`):

Pick the fields you want and every record is trimmed to exactly those - handy for lean CSV/Sheets exports.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.idealista.com/venta-viviendas/madrid-madrid/"
  ],
  "maxPagesPerUrl": 10,
  "maxItems": 1000
}
```

# Actor output Schema

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

Each listing - price, area, rooms, floor, agent - as a JSON item in the default dataset.

# 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": [
        "https://www.idealista.com/venta-viviendas/madrid-madrid/"
    ],
    "startUrlsFromFile": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapesage/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": ["https://www.idealista.com/venta-viviendas/madrid-madrid/"],
    "startUrlsFromFile": "",
}

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

```

## MCP server setup

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