Volver al blog
Guías
Raluca Penciuc7 de abril de 202310 min de lectura

Web Scraping para Inmobiliarias: Cómo extraer datos de Realtor.com como un profesional

Web Scraping para Inmobiliarias: Cómo extraer datos de Realtor.com como un profesional

Configuración del entorno

Antes de empezar a extraer datos, debes instalar Node.js en tu ordenador. Puedes descargar la última versión desde la página web oficial y seguir las instrucciones correspondientes a tu sistema operativo.

A continuación, crea un nuevo directorio para tu proyecto y accede a él desde el terminal o la línea de comandos. Ejecuta el siguiente comando para inicializar un nuevo proyecto de Node.js:

npm init -y

Esto creará un archivo package.json en el directorio de su proyecto, que almacenará información sobre su proyecto y sus dependencias.

Para instalar TypeScript, ejecuta el siguiente comando:

npm install typescript -save-dev

TypeScript es un superconjunto de JavaScript que añade tipado estático opcional y otras características. Resulta útil para proyectos de mayor envergadura y puede facilitar la detección temprana de errores. TypeScript utiliza un archivo de configuración llamado tsconfig.json para almacenar las opciones del compilador y otros ajustes. Para crear este archivo en tu proyecto, ejecuta el siguiente comando:

npx tsc -init

Asegúrate de que el valor de«outDir»esté establecido en«dist». De esta forma, separaremos los archivos de TypeScript de los compilados.

Ahora, crea un directorio«src»en tu proyecto y un nuevo archivo «index.ts». Aquí es donde guardaremos el código de scraping. Para ejecutar código TypeScript hay que compilarlo primero, así que, para asegurarnos de no olvidarnos de este paso adicional, podemos utilizar un comando definido por nosotros mismos.

Ve al archivo «package.json» y modifica la sección «scripts» de la siguiente manera:

"scripts": {

    "test": "npx tsc && node dist/index.js"

}

De esta forma, cuando vayas a ejecutar el script, sólo tienes que escribir "npm run test" en tu terminal.

Y, por último, pero no por ello menos importante, ejecuta el siguiente comando para añadir Puppeteer a las dependencias de tu proyecto:

npm install puppeteer

Puppeteer es una biblioteca de Node.js que ofrece una API de alto nivel para controlar un navegador Chrome sin interfaz gráfica, que puede utilizarse para tareas de extracción de datos web y automatización. Es muy recomendable cuando se desea garantizar la integridad de los datos, ya que muchos sitios web actuales contienen contenido generado dinámicamente.

Selección de datos

Ahora que ya tienes el entorno configurado, podemos empezar a ver cómo extraer los datos. Para este artículo, he decidido recopilar la lista de apartamentos tipo estudio disponibles en alquiler en Plano, Texas: https://www.realtor.com/apartments/Plano_TX/beds-studio.

Vamos a extraer los siguientes datos de cada listado de la página:

  • la URL;
  • los precios;
  • el número de baños;
  • las superficies (medidas en pies cuadrados);
  • las direcciones físicas

Puede ver toda esta información resaltada en la siguiente captura de pantalla:

Resultados de la búsqueda de alquileres de apartamentos con fichas de anuncios resaltadas en rojo, en las que se muestran fotos, precios y datos de la dirección

Extracción de datos

Para extraer todos estos datos, primero tendremos que localizarlos. Haz clic con el botón derecho del ratón en las secciones resaltadas y, a continuación, selecciona «Inspeccionar» para abrir las Herramientas de desarrollador y ver el documento HTML. Al pasar el cursor del ratón por encima, podrás ver fácilmente qué parte corresponde a cada sección:

Ficha de un anuncio de piso junto al inspector del navegador, en la que se resaltan los selectores HTML de los campos de precio, baños, metros cuadrados y dirección

En este tutorial utilizaré selectores CSS, ya que son la opción más sencilla. Si eres nuevo en este método, no dudes en consultar primero esta guía, que se explica por sí sola.

Para empezar a escribir nuestro script, comprobemos que la instalación de Puppeteer se haya realizado correctamente:

import puppeteer from 'puppeteer';

async function scrapeRealtorData(realtor_url: string): Promise<void> {

    // Launch Puppeteer

    const browser = await puppeteer.launch({

        headless: false,

        args: ['--start-maximized'],

    	  defaultViewport: null

    })

    const page = await browser.newPage()

    // Navigate to the channel URL

    await page.goto(realtor_url)

    // Close the browser

    await browser.close()

}

scrapeRealtorData("https://www.realtor.com/apartments/Plano_TX/beds-studio")

Aquí abrimos una ventana del navegador, creamos una nueva página, accedemos a la URL de destino y, a continuación, cerramos el navegador. Para mayor simplicidad y facilitar la depuración visual, abro el navegador a pantalla completa en modo no headless.

Dado que cada anuncio tiene la misma estructura y los mismos datos, en nuestro algoritmo extraeremos toda la información de la lista completa de propiedades. Al final del script, recorreremos todos los resultados y los reuniremos en una única lista.

Quizá hayas notado que la URL del anuncio no aparecía en la primera captura de pantalla, pero sí se mencionaba y resaltaba en la segunda. Esto se debe a que, al hacer clic en ella, se te redirige a la URL de la propiedad.

// Extract listings location

const listings_location = await page.evaluate(() => {

    const locations = document.querySelectorAll('a[data-testid="card-link"]')

    const locations_array = Array.from(locations)

    return locations ? locations_array.map(a => a.getAttribute('href')) : []

})

console.log(listings_location)

Localizamos la URL seleccionando los elementos de anclaje que tienen el atributo«data-testid»con el valor«card-link». A continuación, convertimos el resultado en una matriz de JavaScript y asignamos cada elemento al valor del atributo «href».

Sin embargo, la lista resultante contendrá cada URL dos veces. Esto se debe a que cada anuncio tiene el mismo elemento de anclaje para dos secciones: las imágenes de la propiedad y los detalles del alquiler. Podemos solucionar esto fácilmente utilizando la estructura de datos «Set»:

const unique_listings_location = [...new Set(listings_location)]

console.log(unique_listings_location)

En cuanto al precio de la propiedad, extraeremos los elementos«div»que tengan el atributo«data-testid»con el valor«card-price». También hay que convertirlo en una matriz y, a continuación, asignarlo a su contenido de texto.

// Extract listings price

const listings_price = await page.evaluate(() => {

    const prices = document.querySelectorAll('div[data-testid="card-price"]')

    const prices_array = Array.from(prices)

    return prices ? prices_array.map(p => p.textContent) : []

})

console.log(listings_price)

Para obtener el número de baños y la superficie de la propiedad, utilizaremos el operador para elementos hijos directos. Esto significa que el elemento padre se identifica de forma única, mientras que el elemento hijo tiene un identificador o un nombre de clase más genérico. Aparte de eso, la lógica es la misma que antes:

// Extract listings baths

const listings_baths = await page.evaluate(() => {

    const baths = document.querySelectorAll('li[data-testid="property-meta-baths"] > span[data-testid="meta-value"]')

    const baths_array = Array.from(baths)

    return baths ? baths_array.map(b => b.textContent) : []

})

console.log(listings_baths)

// Extract listings sqft

const listings_sqft = await page.evaluate(() => {

    const sqfts = document.querySelectorAll('li[data-testid="property-meta-sqft"] > span[data-testid="screen-reader-value"]')

    const sqfts_array = Array.from(sqfts)

    return sqfts ? sqfts_array.map(s => s.textContent) : []

})

console.log(listings_sqft)

Y, por último, para las direcciones de los anuncios, seleccionamos los elementos«div»que tienen el atributo«data-testid»establecido con el valor«card-address».

// Extract listings address

const listings_address = await page.evaluate(() => {

    const addresses = document.querySelectorAll('div[data-testid="card-address"]')

    const addresses_array = Array.from(addresses)

    return addresses ? addresses_array.map(a => a.textContent) : []

})

console.log(listings_address)

Ahora deberías tener 5 listas, una por cada dato que hemos scrapeado. Como he mencionado antes, deberíamos centralizarlas en una sola. De esta manera, la información que hemos recopilado será mucho más fácil de procesar.

// Group the lists

const listings = []

for (let i = 0; i < unique_listings_location.length; i++) {

    listings.push({

        url: unique_listings_location[i],

        price: listings_price[i],

        baths: listings_baths[i],

        sqft: listings_sqft[i],

        address: listings_address[i]

    })

}

console.log(listings)

El resultado final debería quedar más o menos así:

[

    {

        url: '/realestateandhomes-detail/1009-14th-St-Apt-410_Plano_TX_75074_M92713-98757',  

        price: '$1,349',

        baths: '1',

	  sqft: '602 square feet',

	  address: '1009 14th St Apt 410Plano, TX 75074'

    },

    {

	  url: '/realestateandhomes-detail/1009-14th-St-Apt-1_Plano_TX_75074_M95483-11211',    

	  price: '$1,616',

	  baths: '1',

	  sqft: '604 square feet',

	  address: '1009 14th St Apt 1Plano, TX 75074'

    },

    {

	  url: '/realestateandhomes-detail/1009-14th-St_Plano_TX_75074_M87662-45547',

	  price: '$1,605 - $2,565',

	  baths: '1 - 2',

	  sqft: '602 - 1,297 square feet',

	  address: '1009 14th StPlano, TX 75074'

    },

    {

	  url: '/realestateandhomes-detail/5765-Bozeman-Dr_Plano_TX_75024_M70427-45476',  	 

	  price: '$1,262 - $2,345',

	  baths: '1 - 2',

	  sqft: '352 - 1,588 square feet',

	  address: '5765 Bozeman DrPlano, TX 75024'

    },

    {

	  url: '/realestateandhomes-detail/1410-K-Ave-Ste-1105A_Plano_TX_75074_M97140-46163',  

	  price: '$1,250 - $1,995',

	  baths: '1 - 2',

	  sqft: '497 - 1,324 square feet',

	  address: '1410 K Ave Ste 1105APlano, TX 75074'

    }

]

Evita que te detecten como bot

Aunque extraer datos de Realtor puede parecer fácil al principio, el proceso puede volverse más complejo y complicado a medida que amplías tu proyecto. La web inmobiliaria utiliza diversas técnicas para detectar y prevenir el tráfico automatizado, por lo que tu programa de extracción, al ampliarse, empieza a ser bloqueado.

Realtor utiliza el modelo «Press & Hold» de CAPTCHA, ofrecido por PerimeterX, que es conocido por ser prácticamente imposible de resolver desde el código. Además, el sitio web recopila diversos datos del navegador para generar una huella digital única y asociarla al usuario.

Entre los datos recogidos del navegador encontramos:

  • propiedades del objeto Navigator (deviceMemory, hardwareConcurrency, languages, platform, userAgent, webdriver, etc.)
  • controles de tiempo y rendimiento
  • WebGL
  • Escaneo de IP de WebRTC
  • y muchos más

Una forma de superar estos retos y seguir realizando el scraping a gran escala es utilizar una API de scraping. Este tipo de servicios ofrecen una forma sencilla y fiable de acceder a datos de sitios web como Realtor.com, sin necesidad de crear y mantener tu propio programa de scraping.

WebScrapingAPI es un ejemplo de este tipo de producto. Su mecanismo de rotación de proxy evita por completo los CAPTCHA, y su base de conocimientos ampliada permite aleatorizar los datos del navegador para que se parezca a un usuario real.

La configuración es rápida y sencilla. Lo único que tienes que hacer es registrar una cuenta para recibir tu clave API. Puedes acceder a ella desde tu panel de control y se utiliza para autenticar las solicitudes que envías.

Página de inicio del panel de control de WebScrapingAPI, que muestra una guía de inicio rápido en tres pasos sobre la clave API, el entorno de pruebas de la API y la documentación

Como ya has configurado tu entorno Node.js, podemos hacer uso del SDK correspondiente. Ejecuta el siguiente comando para añadirlo a las dependencias de tu proyecto:

npm install webscrapingapi

Ahora sólo queda ajustar los selectores CSS anteriores a la API. La potente función de reglas de extracción permite analizar datos sin modificaciones significativas.

import webScrapingApiClient from 'webscrapingapi';

const client = new webScrapingApiClient("YOUR_API_KEY");

async function exampleUsage() {

    const api_params = {

        'render_js': 1,

    	  'proxy_type': 'datacenter',

    	  'timeout': 60000,

    	  'extract_rules': JSON.stringify({

            locations: {

                selector: 'a[data-testid="card-link"]',

                output: '@href',

                all: '1'

        	},

        	prices: {

                selector: 'div[data-testid="card-price"]',

                output: 'text',

                all: '1'

        	},

        	baths: {

                selector: 'li[data-testid="property-meta-baths"] > span[data-testid="meta-value"]',

                output: 'text',

                all: '1'

        	},

        	sqfts: {

                selector: 'li[data-testid="property-meta-sqft"] > span[data-testid="screen-reader-value"]',

                output: 'text',

                all: '1'

        	},

        	addresses: {

                selector: 'div[data-testid="card-address"]',

                output: 'text',

                all: '1'

        	}

        })

    }

    const URL = "https://www.realtor.com/apartments/Plano_TX/beds-studio"

    const response = await client.get(URL, api_params)

    if (response.success) {

        const unique_listings_location = [...new Set(response.response.data.locations)]

    	  // Group the lists

    	  const listings = []

    	  for (let i = 0; i < unique_listings_location.length; i++) {

            listings.push({

                url: unique_listings_location[i],

                price: response.response.data.prices[i],

                baths: response.response.data.baths[i],

                sqft: response.response.data.sqfts[i],

                address: response.response.data.addresses[i]

            })

    	  }

    	  console.log(listings)

    } else {

        console.log(response.error.response.data)

    }

}

exampleUsage();

Conclusión

En este tutorial, hemos proporcionado una guía paso a paso sobre cómo extraer datos de realtor.com utilizando Node.js y Puppeteer. También hemos analizado formas de mejorar la fiabilidad y la eficiencia del extractor, y por qué recurrir a un servicio profesional de extracción de datos puede ser una mejor opción en algunos casos.

Realtor.com es una fuente muy popular y valiosa de datos inmobiliarios, y gracias a las habilidades y conocimientos que has adquirido en este tutorial, ahora deberías ser capaz de utilizar el web scraping para extraer esos datos y aplicarlos a tus propios proyectos.

Tanto si eres un profesional del sector inmobiliario que busca una ventaja competitiva, un inversor en busca de nuevas oportunidades o un comprador que busca la vivienda perfecta, el web scraping puede proporcionarte información y datos valiosos de realtor.com. Esperamos que este tutorial te haya resultado útil y que ahora estés listo para mejorar tu estrategia inmobiliaria con la ayuda del web scraping de realtor.com.

Acerca del autor
Raluca Penciuc, desarrolladora full-stack en WebScrapingAPI
Raluca PenciucDesarrollador full-stack

Raluca Penciuc es desarrolladora full stack en WebScrapingAPI, donde se dedica a crear rastreadores, mejorar las técnicas de evasión y buscar formas fiables de reducir la detección en los sitios web de destino.

Empieza a crear

¿Estás listo para ampliar tu recopilación de datos?

Únete a más de 2000 empresas que utilizan WebScrapingAPI para extraer datos de la web a escala empresarial sin ningún gasto de infraestructura.