How to Scrape Idealista in 2026
Idealista is the largest real estate platform in Southern Europe — over 2 million active listings across Spain, Portugal, and Italy. Whether you're tracking rental prices in Madrid, monitoring apartment availability in Lisbon, or building a property comparison tool for Milan, scraping Idealista is the fastest way to get structured real estate data at scale.
The problem: Idealista uses DataDome to block automated access. Every request is fingerprinted and scored in real time. Standard HTTP libraries return a DataDome challenge page. Selenium and Playwright get flagged within a few requests. Sessemi handles this automatically — challenge solving, IP rotation, and fingerprint management are all built in.
Why Idealista is hard to scrape
Idealista's anti-bot stack is DataDome at high sensitivity, making it one of the harder real estate sites to scrape reliably:
DataDome integration — Every request passes through DataDome's detection pipeline: TLS fingerprinting, HTTP header analysis, JavaScript fingerprinting via c.js, and IP reputation scoring. A single anomaly triggers a device check or hard block.
IP reputation by market — Idealista serves Spain, Portugal, and Italy on separate domains (idealista.com, idealista.pt, idealista.it). DataDome scores residential IPs from the target country more favorably — using a local IP significantly reduces challenge rates and increases success.
Rate limiting per region — Idealista monitors request frequency per IP and per search region. Scraping too many pages in Madrid from a single IP triggers progressive throttling, then a hard block.
Dynamic HTML structure — Listing pages change structure regularly. Class names rotate, layout shifts between property types, and some data is loaded via JavaScript after the initial page render.
A standard requests.get() returns a DataDome challenge page. Selenium gets flagged on the device check. Even Playwright with stealth patches fails because DataDome's c.js detects JavaScript-level fingerprint overrides.
Quick start: 3 lines of Python
pip install sessemi export SESSEMI_KEY=your_key_here # Free at app.sessemi.com
from sessemi import Sessemi
client = Sessemi()
result = client.scrape(
url="https://www.idealista.com/en/venta-viviendas/madrid-madrid/",
country="ES",
stealth=True
)
print(f"Status: {result.status_code}")
print(f"Size: {result.body_size:,} bytes")
print(result.text[:500])
That's it. Sessemi handles the DataDome challenge automatically — residential IP from the target country, browser fingerprint, challenge solve — and returns the real page content.
Using the CLI
If you prefer the command line:
# Search listings in Madrid sessemi scrape "https://www.idealista.com/en/venta-viviendas/madrid-madrid/" -c ES # Save to file sessemi scrape "https://www.idealista.com/en/venta-viviendas/madrid-madrid/" -c ES -o madrid.html # Full response as JSON (includes metadata) sessemi scrape "https://www.idealista.com/en/venta-viviendas/madrid-madrid/" -c ES -f json -o result.json # Check your credits sessemi credits
Status info goes to stderr, page content to stdout — so piping works: sessemi scrape URL -c ES | grep "item-price"
Scraping search results
Idealista renders listings as <article> elements with the class item. Each listing contains the price, location, size, rooms, and a link to the detail page:
from sessemi import Sessemi
from html.parser import HTMLParser
import re
client = Sessemi()
result = client.scrape(
url="https://www.idealista.com/en/venta-viviendas/madrid-madrid/",
country="ES",
stealth=True
)
if result.ok:
html = result.text
# Extract listing links
links = re.findall(r'href="(/en/inmueble/\d+/)"', html)
# Extract prices
prices = re.findall(r'class="item-price[^"]*"[^>]*>\s*([\d.,]+)\s*€', html)
# Extract details (rooms, m², floor)
details = re.findall(r'class="item-detail"[^>]*>\s*([^<]+)', html)
print(f"Found {len(links)} listings")
for i, link in enumerate(links[:5]):
price = prices[i] if i < len(prices) else "N/A"
print(f" {price}€ — https://www.idealista.com{link}")
For more reliable parsing, use a proper HTML parser like lxml or BeautifulSoup:
from sessemi import Sessemi
from bs4 import BeautifulSoup
client = Sessemi()
result = client.scrape(
url="https://www.idealista.com/en/venta-viviendas/madrid-madrid/",
country="ES",
stealth=True
)
if result.ok:
soup = BeautifulSoup(result.text, "html.parser")
for article in soup.select("article.item"):
price_el = article.select_one(".item-price")
link_el = article.select_one("a.item-link")
detail_els = article.select(".item-detail")
price = price_el.get_text(strip=True) if price_el else "N/A"
link = link_el["href"] if link_el else ""
details = [d.get_text(strip=True) for d in detail_els]
print(f" {price} | {', '.join(details)} | https://www.idealista.com{link}")
Paginating through results
Idealista uses /pagina-N for pagination. A typical search returns 30 listings per page:
from sessemi import Sessemi
from bs4 import BeautifulSoup
import time
client = Sessemi()
all_listings = []
for page in range(1, 11):
url = "https://www.idealista.com/en/venta-viviendas/madrid-madrid/"
if page > 1:
url += f"pagina-{page}.htm"
result = client.scrape(url=url, country="ES", stealth=True)
if result.status_code != 200:
print(f"Page {page}: got {result.status_code}, stopping")
break
soup = BeautifulSoup(result.text, "html.parser")
articles = soup.select("article.item")
if not articles:
print(f"Page {page}: no listings found, stopping")
break
for article in articles:
price_el = article.select_one(".item-price")
link_el = article.select_one("a.item-link")
all_listings.append({
"price": price_el.get_text(strip=True) if price_el else "",
"url": link_el["href"] if link_el else "",
})
print(f"Page {page}: {len(articles)} listings (total: {len(all_listings)})")
time.sleep(2) # polite crawling
print(f"\nScraped {len(all_listings)} listings total")
Named sessions for faster scraping
The first request to Idealista solves the DataDome challenge (~10–30 seconds). Named sessions cache the solved cookies so subsequent requests skip the challenge entirely:
from sessemi import Sessemi
client = Sessemi()
# First request: solves DataDome challenge, stores cookies
result = client.scrape(
url="https://www.idealista.com/en/venta-viviendas/madrid-madrid/",
country="ES",
stealth=True,
session="idealista-madrid"
)
print(f"Page 1: {result.status_code} ({result.body_size:,} bytes, {result.duration_ms}ms)")
# Subsequent requests: reuses IP + cookies (~2-5s each)
for page in range(2, 11):
result = client.scrape(
url=f"https://www.idealista.com/en/venta-viviendas/madrid-madrid/pagina-{page}.htm",
session="idealista-madrid"
)
print(f"Page {page}: {result.status_code} ({result.body_size:,} bytes, {result.duration_ms}ms)")
Named sessions keep the same proxy IP and cookies across requests. The first request pays the challenge-solve cost; every request after that is a fast cookie replay at 2–5 seconds.
Multi-market scraping
Idealista covers three countries with different domains and URL structures. Use a different session and country code for each market:
from sessemi import Sessemi
client = Sessemi()
MARKETS = {
"madrid": {
"url": "https://www.idealista.com/en/venta-viviendas/madrid-madrid/",
"country": "ES",
},
"lisbon": {
"url": "https://www.idealista.pt/en/venda/casas/lisboa/",
"country": "PT",
},
"milan": {
"url": "https://www.idealista.it/en/vendita-case/milano/",
"country": "IT",
},
}
for name, market in MARKETS.items():
result = client.scrape(
url=market["url"],
country=market["country"],
stealth=True,
session=f"idealista-{name}",
)
print(f"{name:10s} {result.status_code} — {result.body_size:,} bytes — {result.duration_ms}ms")
Each market gets its own named session with a residential IP from the correct country. The first request per market solves the challenge; subsequent pages reuse the clearance.
Cost breakdown
Sessemi uses a flat credit system — 5 credits per scrape regardless of whether a challenge was solved.
| Plan | Credits/mo | Pages/mo | Cost/page | Monthly |
|---|---|---|---|---|
| Free | 1,000 | 200 | €0 | €0 |
| Basic | 20,000 | 4,000 | €0.005 | €20 |
| Pro | 100,000 | 20,000 | €0.005 | €100 |
For context: scraping 300 listings across Madrid (10 pages) costs 50 credits. On the free tier, that's 20 full search scrapes per month. With named sessions, the second page onwards is faster and cheaper on your time — same 5 credits per page, but 2–5 seconds instead of 10–30.
Tips for reliable Idealista scraping
Always set the country code — country="ES" for Spain, "PT" for Portugal, "IT" for Italy. Local residential IPs get significantly higher trust scores from DataDome, meaning fewer challenges and higher success rates.
Use stealth=True — This tells Sessemi to automatically select the best proxy pool, enable challenge solving, and add retries. For higher success rates on first attempt, add country="ES" to route through a local residential IP.
Use named sessions for multi-page crawls. The first request solves the challenge (~10–30s); subsequent requests reuse the clearance cookie (~2–5s each). One session per market.
Respect rate limits — Idealista monitors request frequency per IP and per region. 1 request every 2–3 seconds is safe. Faster than that risks IP burns and temporary blocks on that search region.
Use the English URL prefix — /en/ gives you English-language pages with consistent HTML structure across all three markets. The localized versions (/es/, /pt/, /it/) work too but class names can differ.
Watch for pagination limits — Idealista caps search results at around 60 pages (~1,800 listings). For comprehensive coverage, break your scrape into smaller geographic areas or price ranges.
Get started: Sign up at app.sessemi.com — 1,000 free credits, no credit card. Install: pip install sessemi. Set your key: export SESSEMI_KEY=your_key. Start scraping.
Full working example: idealista_scrape.py — ready-to-run multi-market scraper with named sessions.