apidea

API de Idealista con Python: buscar anuncios y exportar a CSV

Consulta viviendas de alquiler en Madrid, filtra por precio y guarda los resultados en un CSV. Este tutorial incluye un script completo con Python y HTTPX que puedes adaptar a tu ubicación.

apidea es una API no oficial de terceros. No es un producto de Idealista y no tiene afiliación, respaldo ni patrocinio de Idealista S.A.U. Los anuncios pertenecen a Idealista y a sus anunciantes.

1. Preparar Python y la clave

Necesitas Python 3.12 o posterior y una suscripción a apidea en RapidAPI. Elige el plan que encaje con tu uso y copia tu clave desde RapidAPI. Las cuotas y la facturación se gestionan allí; cada petición del script consume uso de tu plan.

Este ejemplo utiliza la clave de RapidAPI. Las credenciales de la API oficial de Idealista no sirven para estos endpoints. Si buscas el servicio oficial, su formulario de acceso a la Search API es independiente de apidea.

Crea un entorno e instala el cliente HTTP. En macOS o Linux:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install httpx
export RAPIDAPI_KEY='TU_CLAVE_DE_RAPIDAPI'

En Windows, con PowerShell:

py -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install httpx
$env:RAPIDAPI_KEY = 'TU_CLAVE_DE_RAPIDAPI'

La clave se lee de una variable de entorno. No la incluyas en el archivo Python ni la subas a un repositorio. El script hace las peticiones desde tu ordenador o servidor.

2. Buscar anuncios y exportarlos a CSV

Guarda el siguiente código en idealista_export.py. Busca viviendas de alquiler de hasta 1.500 € en Madrid ciudad, con las más recientes primero. Lee hasta tres páginas de 40 resultados, elimina duplicados por propertyCode y escribe idealista.csv.

Es una muestra de hasta 120 anuncios, no una descarga completa de Madrid. Como máximo hace tres peticiones de búsqueda, sin reintentos automáticos ni consultas de detalle.

import csv
import os
import time

import httpx

HOST = "idealista-real-estate.p.rapidapi.com"
MAX_PAGES = 3
OUTPUT = "idealista.csv"
PARAMS = {
    "country": "es",
    "locale": "es",
    "operation": "rent",
    "propertyType": "homes",
    "locationIds": "[0-EU-ES-28-07-001-079]",
    "priceTo": 1500,
    "order": "publicationDate",
}
FIELDS = [
    "propertyCode", "price", "size", "rooms",
    "municipality", "address", "url",
]


def fetch_listings(client, params, max_pages=MAX_PAGES):
    if not 1 <= max_pages <= 50:
        raise ValueError("Use 1 to 50 pages; split larger searches by location.")
    listings = []
    seen = set()
    total = 0
    for page in range(1, max_pages + 1):
        response = client.get(
            "/v1/search",
            params={**params, "numPage": page, "maxItems": 40},
        )
        response.raise_for_status()
        data = response.json()
        if page == 1:
            total = data["total"]
        for item in data["elementList"]:
            code = item["propertyCode"]
            if code not in seen:
                seen.add(code)
                listings.append(item)
        if not data["elementList"] or page >= data["totalPages"]:
            break
        if page < max_pages:
            time.sleep(1)
    return listings, total


def csv_cell(value):
    # Keep text from being interpreted as a spreadsheet formula.
    if isinstance(value, str) and value.startswith(("=", "+", "-", "@", "\t", "\r", "\n")):
        return "'" + value
    return value


def write_csv(listings):
    with open(OUTPUT, "w", newline="", encoding="utf-8-sig") as file:
        writer = csv.DictWriter(file, fieldnames=FIELDS)
        writer.writeheader()
        for item in listings:
            writer.writerow({field: csv_cell(item.get(field)) for field in FIELDS})


def main():
    key = os.environ.get("RAPIDAPI_KEY")
    if not key:
        raise SystemExit("Set the RAPIDAPI_KEY environment variable first.")
    headers = {
        "x-rapidapi-key": key,
        "x-rapidapi-host": HOST,
        "accept": "application/json",
    }
    try:
        with httpx.Client(
            base_url=f"https://{HOST}", headers=headers, timeout=30.0,
        ) as client:
            listings, total = fetch_listings(client, PARAMS)
    except httpx.HTTPStatusError as exc:
        raise SystemExit(
            f"HTTP {exc.response.status_code}: check the tutorial's error table."
        ) from exc
    except httpx.RequestError as exc:
        raise SystemExit("Network error or timeout; try again later.") from exc
    write_csv(listings)
    print(f"Saved {len(listings)} of {total} matches to {OUTPUT}.")
    print(f"Page limit: {MAX_PAGES}. This may be a partial export.")


if __name__ == "__main__":
    main()

Ejecútalo desde la misma terminal en la que configuraste la clave:

python idealista_export.py

El CSV se guarda en la carpeta actual y puedes abrirlo con Excel, LibreOffice o importarlo en Google Sheets. Utiliza UTF-8 con BOM para conservar las tildes. Si ya existe idealista.csv, una ejecución correcta lo sobrescribe; cambia OUTPUT para conservar exportaciones anteriores.

Si la búsqueda está vacía, se genera un CSV con sus cabeceras. Si falla una petición, el programa termina antes de escribir el archivo. Los campos ausentes quedan vacíos.

3. Entender la respuesta

GET /v1/search devuelve un objeto JSON. Este es un ejemplo ficticio y abreviado para mostrar su estructura:

{
  "total": 1,
  "totalPages": 1,
  "actualPage": 1,
  "itemsPerPage": 40,
  "elementList": [
    {
      "propertyCode": "123456789",
      "propertyType": "flat",
      "operation": "rent",
      "price": 1200,
      "size": 65,
      "rooms": 2,
      "municipality": "Madrid"
    }
  ]
}

elementList contiene los anuncios. total es el número de coincidencias de la búsqueda, totalPages sus páginas y actualPage la página recibida. El identificador propertyCode permite eliminar duplicados o pedir el detalle de un anuncio por separado. El precio, la superficie u otros campos pueden ser nulos o no estar disponibles.

4. Cambiar ubicación y filtros

Cambia el diccionario PARAMS del script. Los nombres distinguen mayúsculas y minúsculas; por ejemplo, la búsqueda acepta locationIds, en plural.

ParámetroCómo usarlo
operationrent para alquiler; sale para venta. Revisa también el límite de precio al cambiar de operación.
locationIds[0-EU-ES-28-07-001-079] es Madrid ciudad. [0-EU-ES-28] abarca toda la provincia. Conserva los corchetes.
priceToPrecio máximo. Elimina esta entrada para buscar sin ese límite.
bedroomsCadena con los recuentos deseados: "2,3" para dos o tres dormitorios.
countryes, pt o it. Debe corresponder al país de la ubicación.
localeIdioma de los textos; por ejemplo, es o en. No cambia el país de búsqueda.

Para otra ciudad, consulta GET /v1/locations/autocomplete con query y country, y elige un resultado que tenga locationId: algunos puntos de interés no lo tienen. Copia el identificador entre corchetes en locationIds. La receta de ubicaciones muestra la petición. No basta con cambiar country y conservar el identificador de Madrid.

Los filtros disponibles dependen del contexto. Consulta GET /v1/search/filters con la misma operación, tipo de inmueble y ubicación antes de ampliar el ejemplo con más filtros.

5. Paginación, límites y errores

numPage empieza en 1 y maxItems admite de 1 a 40. El script se detiene al llegar a MAX_PAGES, a la última página o a una página vacía. Espera un segundo entre páginas; adapta el ritmo a los límites de tu plan.

Una consulta solo permite recuperar aproximadamente los primeros 2.000 anuncios distintos. Aumentar MAX_PAGES no garantiza una exportación completa: el script admite hasta 50 páginas como tope, pero las búsquedas grandes deben dividirse por distritos o barrios. Consulta la guía de extracción por ubicaciones. Los anuncios pueden cambiar mientras paginas; deduplicar no convierte la búsqueda en una instantánea.

Las búsquedas tienen una caché de cinco minutos. Si necesitas forzar una actualización, consulta las opciones de caché.

ErrorQué revisar
400 / 422Nombre, tipo o valor de los parámetros. Un nombre desconocido provoca 400 unknown_query_parameter; corrige el parámetro antes de repetir.
401 / 403Clave, suscripción y host de RapidAPI. Comprueba que la suscripción corresponde a esta API.
404Ruta o ubicación. Una ubicación obsoleta devuelve location_not_found; resuélvela de nuevo.
429Cuota o ritmo de peticiones del gateway. Revisa tu plan y respeta Retry-After si viene en la respuesta.
502 / 503Fallo del servicio upstream. Espera y reintenta con un límite; si persiste, contacta con soporte.

El ejemplo se detiene ante estos errores. Revisa la referencia de errores antes de añadir reintentos.

6. Continuar con tu integración

Para consultar detalles, promociones de obra nueva o zonas más amplias, continúa con la guía de integración. La especificación OpenAPI documenta los parámetros y respuestas; llms.txt reúne el contrato para asistentes de programación.

Ver planes y obtener la clave en RapidAPI →

Si tienes dudas sobre esta integración, escribe a contacto@apidea.es.