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:

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:

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.

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.




