Pré-requisitos
Se ainda não tiver o seu ambiente Node.js configurado, basta ir ao site oficial para descarregar a versão mais recente para o seu sistema operativo. Em seguida, crie um novo diretório e execute o seguinte comando para inicializar o seu projeto:
npm init -y
Iremos utilizar TypeScript para escrever o código. Este superset do JavaScript adiciona tipagem estática opcional e outras funcionalidades. É útil para projetos de maior dimensão e pode facilitar a deteção precoce de erros. É necessário adicioná-lo às dependências de desenvolvimento do projeto e inicializar o seu ficheiro de configuração:
npm install typescript -save-dev npx tsc -init
Certifique-se apenas de que, no ficheiro tsconfig.json recém-gerado, a propriedade «outDir» está definida como «dist», uma vez que pretendemos separar o código TypeScript do código compilado.
Por último, o comando seguinte irá adicionar o Puppeteer às dependências do nosso projeto:
npm install puppeteer
O Puppeteer é uma biblioteca Node.js que fornece uma API de alto nível para controlar um navegador Chrome sem interface gráfica, que pode ser usado para web scraping e tarefas de automação
Localização dos dados
Para este tutorial, optámos por extrair as propriedades disponíveis nas Ilhas da Madeira, Portugal: https://www.booking.com/searchresults.en-us.html?ss=Madeira+Islands&checkin=2023-01-13&checkout=2023-01-15. É importante adicionar as datas de check-in e check-out ao URL para que todas as informações sobre as propriedades fiquem disponíveis.
Este guia aborda a extração dos seguintes dados das propriedades:
- o nome
- o URL
- o endereço físico
- o preço
- a classificação e o número de avaliações
- a miniatura
Pode vê-los destacados na captura de ecrã abaixo:
Ao abrir as Ferramentas do Desenvolvedor em cada um destes elementos, poderá observar os seletores CSS que iremos utilizar para localizar os elementos HTML. Se ainda não estiver familiarizado com o funcionamento dos seletores CSS, consulte este guia para principiantes.
Análise dos dados
Uma vez que todos os anúncios têm a mesma estrutura e dados, podemos extrair toda a informação da lista completa de propriedades no nosso algoritmo. Após executar o script, podemos percorrer todos os resultados e compilá-los numa única lista.
Após uma primeira olhadela no documento HTML, deve ter reparado que o site da Booking é bastante complexo e que os nomes das classes são, na sua maioria, gerados aleatoriamente.
Felizmente para nós, o site não depende exclusivamente de nomes de classes, e podemos usar o valor de um atributo específico como critério de extração. Na captura de ecrã acima, destacámos como a miniatura, o nome e o URL de um imóvel são acessíveis.
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")
Utilizámos o Puppeteer para abrir uma instância do navegador, criar uma nova página, navegar até ao nosso URL de destino, extrair os dados mencionados e, em seguida, fechar o navegador. Para fins de depuração visual, estou a utilizar o modo não headless do navegador.
Conforme explicado acima, os dados eram facilmente acessíveis graças ao atributo “data-testid”, que atribuiu um valor único ao elemento HTML. Execute o seguinte comando para executar o script:
npx tsc && node dist/index.js
O seu terminal deverá apresentar 3 resultados de lista do mesmo tamanho, representando os nomes, as URLs e as miniaturas de todas as propriedades na página atual.
Para a secção seguinte do documento HTML, destacámos o endereço, a classificação e o número de avaliações de uma propriedade.
// 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)
Tal como antes, utilizámos o atributo “data-testid”. Ao executar o script novamente, deverão aparecer mais 2 listas, tal como as anteriores.
E, finalmente, na última secção, extraímos o preço do imóvel. O código não será diferente do que fizemos anteriormente:
// 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 o processamento posterior dos dados extraídos, vamos combinar as listas resultantes numa única lista.
// 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)
O resultado final deverá agora ter este aspeto:
[
{
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
Embora o tutorial até este ponto tenha parecido simples, devemos mencionar as ressalvas normalmente encontradas na extração de dados da web, especialmente no caso de querer expandir o seu projeto.
Atualmente, os sites implementam várias técnicas de deteção de bots e recolhem dados do navegador para que possam impedir ou bloquear o tráfego automatizado. O Booking.com não é exceção a esta regra. Utilizando a proteção PerimeterX, o site realiza verificações no seu IP e recolhe várias informações:
- propriedades do objeto Navigator (deviceMemory, languages, platform, userAgent, webdriver, etc.)
- enumeração de tipos de letra e plugins
- verificações das dimensões do ecrã
- e muito mais.
Uma solução para estes desafios é utilizar uma API de scraping, que oferece uma forma simples e fiável de aceder a dados de sites como o Booking.com sem a necessidade de criar e manter o seu próprio scraper.
A WebScrapingAPI é um produto desse tipo, que utiliza rotação de proxies para contornar CAPTCHAs e randomiza os dados do navegador para imitar um utilizador real. Para começar, basta registar-se para criar uma conta e obter a sua chave API no painel de controlo. Esta chave é utilizada para autenticar os seus pedidos.
Para testar rapidamente a API com o projeto Node.js já existente, podemos utilizar o SDK correspondente. Basta executar o seguinte comando:
npm install webscrapingapi
Agora, basta ajustar os seletores CSS anteriores à API. A funcionalidade de regras de extração permite analisar dados com modificações mínimas, tornando-a uma ferramenta poderosa no seu conjunto de ferramentas 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();Conclusão
Neste tutorial, abordámos os conceitos básicos de como fazer web scraping no Booking.com utilizando Node.js e Puppeteer. Mostrámos-lhe como configurar o seu ambiente e extrair detalhes de anúncios para a Madeira, Portugal. No entanto, estas técnicas e conceitos podem ser aplicados a outros sites e pontos de dados também.
O web scraping pode ser uma ferramenta incrivelmente útil tanto para empresas como para cientistas de dados. Ao recolher dados do Booking.com, pode obter informações valiosas sobre o setor hoteleiro, avaliar a concorrência e muito mais. No entanto, é importante ter em mente que o web scraping pode violar os termos de utilização de alguns sites, e é sempre uma boa ideia verificar as políticas específicas antes de prosseguir.
Embora seja possível criar o seu próprio web scraper, recorrer a um serviço profissional pode muitas vezes ser uma opção mais segura e eficiente, especialmente para projetos de maior dimensão. Um scraper profissional terá os conhecimentos e os recursos necessários para lidar com quaisquer desafios que possam surgir e fornecer resultados de alta qualidade.
Esperamos que tenha gostado deste tutorial e que agora se sinta preparado para recolher dados valiosos da Booking.com utilizando um ambiente Node.js. Obrigado pela leitura!




