Requisitos previos
Si aún no tienes configurado tu entorno Node.js, solo tienes que dirigirte a su sitio web oficial para descargar la última versión para tu sistema operativo. A continuación, crea un nuevo directorio y ejecuta el siguiente comando para inicializar tu proyecto:
npm init -y
Usaremos TypeScript para escribir el código. Este superconjunto de JavaScript añade tipado estático opcional y otras características. Es útil para proyectos más grandes y puede facilitar la detección temprana de errores. Debes añadirlo a las dependencias de desarrollo del proyecto e inicializar su archivo de configuración:
npm install typescript -save-dev npx tsc -init
Solo asegúrate de que, en el archivo tsconfig.json recién generado, la propiedad «outDir» esté establecida en «dist», ya que queremos separar el código TypeScript del compilado.
Por último, el siguiente comando añadirá Puppeteer a las dependencias de nuestro proyecto:
npm install puppeteer
Puppeteer es una biblioteca de Node.js que proporciona una API de alto nivel para controlar un navegador Chrome sin interfaz gráfica, que se puede utilizar para tareas de web scraping y automatización
Ubicación de los datos
Para este tutorial, hemos elegido extraer los datos de las propiedades disponibles en las Islas de Madeira, Portugal: https://www.booking.com/searchresults.en-us.html?ss=Madeira+Islands&checkin=2023-01-13&checkout=2023-01-15. Es importante añadir las fechas de entrada y salida a la URL para que toda la información de las propiedades esté disponible.
Esta guía abarca la extracción de los siguientes datos de las propiedades:
- el nombre
- la URL
- la dirección física
- el precio
- la valoración y el número de reseñas
- la miniatura
Puedes verlos resaltados en la captura de pantalla siguiente:
Al abrir las Herramientas de desarrollador en cada uno de estos elementos, podrás ver los selectores CSS que utilizaremos para localizar los elementos HTML. Si eres bastante nuevo en el funcionamiento de los selectores CSS, no dudes en consultar esta guía para principiantes.
Análisis de los datos
Dado que todos los anuncios tienen la misma estructura y datos, podemos extraer toda la información de la lista completa de propiedades en nuestro algoritmo. Tras ejecutar el script, podemos recorrer todos los resultados y compilarlos en una única lista.
Tras un primer vistazo al documento HTML, es posible que hayas notado que la página web de Booking es bastante compleja y que los nombres de las clases se generan en su mayoría de forma aleatoria.
Por suerte para nosotros, el sitio web no se basa únicamente en los nombres de clase, y podemos utilizar el valor de un atributo específico como criterio de extracción. En la captura de pantalla anterior, hemos resaltado lo accesibles que son la miniatura, el nombre y la URL de una propiedad.
import puppeteer from 'puppeteer';
async function scrapeBookingData(booking_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(booking_url)
// Extract listings name
const listings_name = await page.evaluate(() => {
const names = document.querySelectorAll('div[data-testid="title"]')
const names_array = Array.from(names)
return names ? names_array.map(n => n.textContent) : []
})
console.log(listings_name)
// Extract listings location
const listings_location = await page.evaluate(() => {
const locations = document.querySelectorAll('a[data-testid="title-link"]')
const locations_array = Array.from(locations)
return locations ? locations_array.map(l => l.getAttribute('href')) : []
})
console.log(listings_location)
// Extract listings thumbnail
const listings_thumbnail = await page.evaluate(() => {
const thumbnails = document.querySelectorAll('[data-testid="image"]')
const thumbnails_array = Array.from(thumbnails)
return thumbnails ? thumbnails_array.map(t => t.getAttribute('src')) : []
})
console.log(listings_thumbnail)
await browser.close()
}
scrapeBookingData("https://www.booking.com/searchresults.en-us.html?ss=Madeira+Islands&checkin=2023-01-13&checkout=2023-01-15")
Hemos utilizado Puppeteer para abrir una instancia del navegador, crear una nueva página, navegar hasta nuestra URL de destino, extraer los datos mencionados y, a continuación, cerrar el navegador. Para facilitar la depuración visual, estoy utilizando el modo no headless del navegador.
Como se ha explicado anteriormente, se pudo acceder fácilmente a los datos gracias al atributo «data-testid», que asignaba un valor único al elemento HTML. Ejecuta el siguiente comando para ejecutar el script:
npx tsc && node dist/index.js
Tu terminal debería mostrar 3 resultados en forma de lista del mismo tamaño, que representan los nombres, las URL y las miniaturas de todas las propiedades de la página actual.
Para la siguiente sección del documento HTML, hemos resaltado la dirección, la valoración y el número de reseñas de una propiedad.
// Extract listings address
const listings_address = await page.evaluate(() => {
const addresses = document.querySelectorAll('[data-testid="address"]')
const addresses_array = Array.from(addresses)
return addresses ? addresses_array.map(a => a.textContent) : []
})
console.log(listings_address)
// Extract listings rating and review count
const listings_rating = await page.evaluate(() => {
const ratings = document.querySelectorAll('[data-testid="review-score"]')
const ratings_array = Array.from(ratings)
return ratings ? ratings_array.map(r => r.textContent) : []
})
console.log(listings_rating)
Al igual que antes, hemos utilizado el atributo «data-testid». Al volver a ejecutar el script, deberían aparecer dos listas más, igual que las anteriores.
Y, por último, en la última sección, hemos extraído el precio de la propiedad. El código no será diferente de lo que hicimos antes:
// Extract listings price
const listings_price = await page.evaluate(() => {
const prices = document.querySelectorAll('[data-testid="price-and-discounted-price"]')
const prices_array = Array.from(prices)
return prices ? prices_array.map(p => p.textContent) : []
})
console.log(listings_price)
Para facilitar el procesamiento posterior de los datos extraídos, combinaremos las listas resultantes en una sola.
// Group the lists
const listings = []
for (let i = 0; i < listings_name.length; i++) {
listings.push({
name: listings_name[i],
url: listings_location[i],
address: listings_address[i],
price: listings_price[i],
ratings: listings_rating[i],
thumbnails: listings_thumbnail[i]
})
}
console.log(listings)
El resultado final debería tener ahora este aspecto:
[
{
name: 'Pestana Churchill Bay',
url: 'https://www.booking.com/hotel/pt/pestana-churchill-bay.html?aid=304142&label=gen173nr-1FCAQoggJCFnNlYXJjaF9tYWRlaXJhIGlzbGFuZHNIMVgEaMABiAEBmAExuAEXyAEM2AEB6AEB-AEDiAIBqAIDuAK9luydBsACAdICJGViMWY2MmRjLWJhZmEtNGZhZC04MDAyLWQ4MmU3YjU5MTMwZtgCBeACAQ&ucfs=1&arphpl=1&checkin=2023-01-13&checkout=2023-01-15&group_adults=2&req_adults=2&no_rooms=1&group_children=0&req_children=0&hpos=1&hapos=1&sr_order=popularity&srpvid=42cc81de452009eb&srepoch=1673202494&all_sr_blocks=477957801_262227867_0_1_0&highlighted_blocks=477957801_262227867_0_1_0&matching_block_id=477957801_262227867_0_1_0&sr_pri_blocks=477957801_262227867_0_1_0__18480&tpi_r=2&from_sustainable_property_sr=1&from=searchresults#hotelTmpl',
address: 'Câmara de Lobos',
price: '911 lei',
ratings: '9.0Wonderful 727 reviews',
thumbnails: 'https://cf.bstatic.com/xdata/images/hotel/square200/202313893.webp?k=824dc3908c4bd3e80790ce011f763f10fd4064dcb5708607f020f2e7c92d130e&o=&s=1'
},
{
name: 'Hotel Madeira',
url: 'https://www.booking.com/hotel/pt/madeira-funchal.html?aid=304142&label=gen173nr-1FCAQoggJCFnNlYXJjaF9tYWRlaXJhIGlzbGFuZHNIMVgEaMABiAEBmAExuAEXyAEM2AEB6AEB-AEDiAIBqAIDuAK9luydBsACAdICJGViMWY2MmRjLWJhZmEtNGZhZC04MDAyLWQ4MmU3YjU5MTMwZtgCBeACAQ&ucfs=1&arphpl=1&checkin=2023-01-13&checkout=2023-01-15&group_adults=2&req_adults=2&no_rooms=1&group_children=0&req_children=0&hpos=2&hapos=2&sr_order=popularity&srpvid=42cc81de452009eb&srepoch=1673202494&all_sr_blocks=57095605_262941681_2_1_0&highlighted_blocks=57095605_262941681_2_1_0&matching_block_id=57095605_262941681_2_1_0&sr_pri_blocks=57095605_262941681_2_1_0__21200&tpi_r=2&from_sustainable_property_sr=1&from=searchresults#hotelTmpl',
address: 'Se, Funchal',
price: '1,045 lei',
ratings: '8.3Very Good 647 reviews',
thumbnails: 'https://cf.bstatic.com/xdata/images/hotel/square200/364430623.webp?k=8c1e510da2aad0fc9ff5731c3874e05b1c4cceec01a07ef7e9db944799771724&o=&s=1'
},
{
name: 'Les Suites at The Cliff Bay - PortoBay',
url: 'https://www.booking.com/hotel/pt/les-suites-at-the-cliff-bay.html?aid=304142&label=gen173nr-1FCAQoggJCFnNlYXJjaF9tYWRlaXJhIGlzbGFuZHNIMVgEaMABiAEBmAExuAEXyAEM2AEB6AEB-AEDiAIBqAIDuAK9luydBsACAdICJGViMWY2MmRjLWJhZmEtNGZhZC04MDAyLWQ4MmU3YjU5MTMwZtgCBeACAQ&ucfs=1&arphpl=1&checkin=2023-01-13&checkout=2023-01-15&group_adults=2&req_adults=2&no_rooms=1&group_children=0&req_children=0&hpos=3&hapos=3&sr_order=popularity&srpvid=42cc81de452009eb&srepoch=1673202494&all_sr_blocks=395012401_247460894_2_1_0&highlighted_blocks=395012401_247460894_2_1_0&matching_block_id=395012401_247460894_2_1_0&sr_pri_blocks=395012401_247460894_2_1_0__100000&tpi_r=2&from_sustainable_property_sr=1&from=searchresults#hotelTmpl',
address: 'Sao Martinho, Funchal',
price: '4,928 lei',
ratings: '9.5Exceptional 119 reviews',
thumbnails: 'https://cf.bstatic.com/xdata/images/hotel/square200/270120962.webp?k=68ded1031f5082597c48eb25c833ea7fcedc2ec2bc5d555adfcac98b232f9745&o=&s=1'
}
]Alternativas
Aunque el tutorial hasta este punto ha parecido sencillo, debemos mencionar las dificultades que suelen surgir en el web scraping, especialmente en el caso de que quieras ampliar tu proyecto.
Hoy en día, los sitios web implementan diversas técnicas de detección de bots y recopilan datos del navegador para poder prevenir o bloquear el tráfico automatizado. Booking.com no es una excepción a esta regla. Mediante la protección de PerimeterX, el sitio web realiza comprobaciones de tu IP y recopila múltiples datos:
- propiedades del objeto Navigator (deviceMemory, languages, platform, userAgent, webdriver, etc.)
- enumeración de fuentes y complementos
- comprobaciones de las dimensiones de la pantalla
- y mucho más.
Una solución a estos retos es utilizar una API de scraping, que ofrece una forma sencilla y fiable de acceder a los datos de sitios web como Booking.com sin necesidad de crear y mantener tu propio scraper.
WebScrapingAPI es un producto de este tipo, que utiliza la rotación de proxies para eludir los CAPTCHAs y aleatoriza los datos del navegador para imitar a un usuario real. Para empezar, solo tienes que registrarte y obtener tu clave API desde el panel de control. Esta clave se utiliza para autenticar tus solicitudes.
Para probar rápidamente la API con el proyecto Node.js ya existente, podemos utilizar su SDK correspondiente. Solo tienes que ejecutar el siguiente comando:
npm install webscrapingapi
Ahora, todo lo que tienes que hacer es ajustar los selectores CSS anteriores a la API. La función de reglas de extracción te permite analizar datos con modificaciones mínimas, lo que la convierte en una herramienta poderosa en tu kit de herramientas de web scraping.
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({
names: {
selector: 'div[data-testid="title"]',
output: 'text',
all: '1'
},
locations: {
selector: 'a[data-testid="title-link"]',
output: '@href',
all: '1'
},
addresses: {
selector: '[data-testid="address"]',
output: 'text',
all: '1'
},
prices: {
selector: '[data-testid="price-and-discounted-price"]',
output: 'text',
all: '1'
},
ratings: {
selector: '[data-testid="review-score"]',
output: 'text',
all: '1'
},
thumbnails: {
selector: '[data-testid="image"]',
output: '@src',
all: '1'
}
})
}
const URL = "https://www.booking.com/searchresults.en-us.html?ss=Madeira+Islands&checkin=2023-01-13&checkout=2023-01-15"
const response = await client.get(URL, api_params)
if (response.success) {
// Group the lists
const listings = []
for (let i = 0; i < response.response.data.names.length; i++) {
listings.push({
name: response.response.data.names[i],
url: response.response.data.locations[i],
address: response.response.data.addresses[i],
price: response.response.data.prices[i],
ratings: response.response.data.ratings[i],
thumbnails: response.response.data.thumbnails[i]
})
}
console.log(listings)
} else {
console.log(response.error.response.data)
}
}
exampleUsage();Conclusión
En este tutorial, hemos cubierto los conceptos básicos de cómo extraer datos de Booking.com utilizando Node.js y Puppeteer. Te hemos mostrado cómo configurar tu entorno y extraer los detalles de los anuncios de Madeira, Portugal. Sin embargo, estas técnicas y conceptos también se pueden aplicar a otros sitios web y puntos de datos.
El web scraping puede ser una herramienta increíblemente útil tanto para empresas como para científicos de datos. Al recopilar datos de Booking.com, puedes obtener información valiosa sobre el sector hotelero, evaluar a la competencia y mucho más. Sin embargo, es importante tener en cuenta que el web scraping puede infringir las condiciones de uso de algunos sitios web, por lo que siempre es recomendable consultar las políticas específicas antes de continuar.
Aunque es posible crear su propio rastreador web, recurrir a un servicio profesional suele ser una opción más segura y eficiente, especialmente para proyectos de mayor envergadura. Un rastreador profesional contará con la experiencia y los recursos necesarios para hacer frente a cualquier reto que pueda surgir y ofrecer resultados de alta calidad.
Esperamos que hayas disfrutado de este tutorial y que ahora te sientas preparado para recopilar datos valiosos de Booking.com utilizando un entorno Node.js. ¡Gracias por leer!




