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:
Para 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 autoexplicativa.
Para empezar a escribir nuestro script, comprobemos que la instalación de Puppeteer se ha 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, navegamos a nuestra URL de destino y luego cerramos el navegador. En aras de la simplicidad y la depuración visual, abro el navegador maximizado 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 centralizaremos en una única lista.
Quizá hayas notado que la URL del anuncio no era visible en la primera captura de pantalla, pero se mencionaba y resaltaba en la segunda. Esto se debe a que, al hacer clic en él, 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)
Para el 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 luego 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 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 para cada dato que hemos extraído. Como mencioné antes, deberíamos centralizarlas en una sola. De esta forma, la información que hemos recopilado será mucho más fácil de procesar posteriormente.
// 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 tener un aspecto similar a este:
[
{
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'
}
]