Pré-requisitos
Antes de começarmos, vamos garantir que temos as ferramentas necessárias.
Primeiro, descarregue e instale o Node.js a partir do site oficial, certificando-se de que utiliza a versão de Suporte a Longo Prazo (LTS). Isto também instalará automaticamente o Node Package Manager (NPM), que utilizaremos para instalar outras dependências.
Para este tutorial, iremos utilizar o Visual Studio Code como nosso Ambiente de Desenvolvimento Integrado (IDE), mas pode utilizar qualquer outro IDE à sua escolha. Crie uma nova pasta para o seu projeto, abra o terminal e execute o seguinte comando para configurar um novo projeto Node.js:
npm init -y
Isto irá criar um ficheiro package.json no diretório do seu projeto, que armazenará informações sobre o seu projeto e as suas dependências.
Em seguida, precisamos de instalar o TypeScript e as definições de tipos para o Node.js. O TypeScript oferece tipagem estática opcional, o que ajuda a evitar erros no código. Para o fazer, execute no terminal:
npm install typescript @types/node --save-dev
Pode verificar a instalação executando:
npx tsc --version
O TypeScript utiliza um ficheiro de configuração chamado tsconfig.json para armazenar opções do compilador e outras definições. Para criar este ficheiro no seu projeto, execute o seguinte comando:
npx tsc -init
Certifique-se de que o valor de “outDir” está definido como “dist”. Desta forma, separaremos os ficheiros TypeScript dos ficheiros compilados. Pode encontrar mais informações sobre este ficheiro e as suas propriedades na documentação oficial do TypeScript.
Agora, crie um diretório “src” no seu projeto e um novo ficheiro “index.ts”. É aqui que iremos manter o código de scraping. Para executar código TypeScript, é necessário compilá-lo primeiro; por isso, para garantir que não nos esquecemos deste passo adicional, podemos usar um comando definido por nós.
Vá até ao ficheiro “package.json” e edite a secção “scripts” da seguinte forma:
"scripts": {
"test": "npx tsc && node dist/index.js"
}
Desta forma, quando executar o script, basta digitar “npm run test” no seu terminal.
Por fim, para extrair os dados do site, vamos usar o Puppeteer, uma biblioteca de navegador headless para Node.js que permite controlar um navegador web e interagir com sites de forma programática. Para instalá-lo, execute este comando no terminal:
npm install puppeteer
É altamente recomendado quando se pretende garantir a integridade dos dados, uma vez que muitos sites hoje em dia contêm conteúdo gerado dinamicamente. Se estiver curioso, pode consultar a documentação do Puppeteer antes de continuar para ver tudo o que ele é capaz de fazer.
Localizar os dados
Agora que já tem o seu ambiente configurado, podemos começar a ver como extrair os dados. Para este artigo, optei por extrair a lista de casas e apartamentos disponíveis numa região de Toledo, Espanha: https://www.idealista.com/pt/alquiler-viviendas/toledo/buenavista-valparaiso-la-legua/.
Vamos extrair os seguintes dados de cada anúncio na página:
- o URL;
- o título;
- o preço;
- os detalhes (número de quartos, área, etc.);
- a descrição
Pode ver todas estas informações destacadas na captura de ecrã abaixo:
Ao abrir as Ferramentas do Desenvolvedor em cada um destes elementos, poderá observar os seletores CSS que utilizaremos para localizar os elementos HTML. Se ainda não estiver familiarizado com o funcionamento dos seletores CSS, consulte este guia para principiantes.
Extração de dados
Para começar a escrever o nosso script, vamos verificar se a instalação do Puppeteer correu bem:
import puppeteer from 'puppeteer';
async function scrapeIdealistaData(idealista_url: string): Promise<void> {
// Launch Puppeteer
const browser = await puppeteer.launch({
headless: false,
args: ['--start-maximized'],
defaultViewport: null
})
// Create a new page
const page = await browser.newPage()
// Navigate to the target URL
await page.goto(idealista_url)
// Close the browser
await browser.close()
}
scrapeIdealistaData("https://www.idealista.com/pt/alquiler-viviendas/toledo/buenavista-valparaiso-la-legua/")
Aqui, abrimos uma janela do navegador, criamos uma nova página, navegamos até ao nosso URL de destino e fechamos o navegador. Por uma questão de simplicidade e depuração visual, abro a janela do navegador maximizada no modo não headless.
Uma vez que todos os anúncios têm a mesma estrutura e dados, podemos extrair toda a informação da lista completa de imóveis no nosso algoritmo. Após executar o script, podemos percorrer todos os resultados e compilá-los numa única lista.
Para obter o URL de todas as propriedades, localizamos os elementos âncora com a classe “item-link”. Em seguida, convertemos o resultado numa matriz JavaScript e mapeamos cada elemento para o valor do atributo “href”.
// Extract listings location
const listings_location = await page.evaluate(() => {
const locations = document.querySelectorAll('a.item-link')
const locations_array = Array.from(locations)
return locations ? locations_array.map(a => a.getAttribute('href')) : []
})
console.log(listings_location.length, listings_location)
Em seguida, para os títulos, podemos utilizar o mesmo elemento âncora, exceto que, desta vez, iremos extrair o seu atributo “title”.
// Extract listings titles
const listings_title = await page.evaluate(() => {
const titles = document.querySelectorAll('a.item-link')
const titles_array = Array.from(titles)
return titles ? titles_array.map(t => t.getAttribute('title')) : []
})
console.log(listings_title.length, listings_title)
Para os preços, localizamos os elementos “span” com dois nomes de classe: “item-price” e “h2-simulated”. É importante identificar os elementos da forma mais única possível, para não alterar o resultado final. Também é necessário convertê-los numa matriz e, em seguida, mapear para o seu conteúdo de texto.
// Extract listings prices
const listings_price = await page.evaluate(() => {
const prices = document.querySelectorAll('span.item-price.h2-simulated')
const prices_array = Array.from(prices)
return prices ? prices_array.map(p => p.textContent) : []
})
console.log(listings_price.length, listings_price)
Aplicamos o mesmo princípio aos detalhes da propriedade, analisando os elementos “div” com o nome de classe “item-detail-char”.
// Extract listings details
const listings_detail = await page.evaluate(() => {
const details = document.querySelectorAll('div.item-detail-char')
const details_array = Array.from(details)
return details ? details_array.map(d => d.textContent) : []
})
console.log(listings_detail.length, listings_detail)
E, finalmente, a descrição dos imóveis. Aqui, aplicamos uma expressão regular adicional para remover todos os caracteres de nova linha desnecessários.
// Extract listings descriptions
const listings_description = await page.evaluate(() => {
const descriptions = document.querySelectorAll('div.item-description.description')
const descriptions_array = Array.from(descriptions)
return descriptions ? descriptions_array.map(d => d.textContent.replace(/(\r\n|\n|\r)/gm, "")) : []
})
console.log(listings_description.length, listings_description)
Agora deve ter 5 listas, uma para cada dado que extraímos. Como mencionei anteriormente, devemos centralizá-las numa única lista. Desta forma, a informação que recolhemos será muito mais fácil de processar posteriormente.
// Group the lists
const listings = []
for (let i = 0; i < listings_location.length; i++) {
listings.push({
url: listings_location[i],
title: listings_title[i],
price: listings_price[i],
details: listings_detail[i],
description: listings_description[i]
})
}
console.log(listings.length, listings)
O resultado final deve ficar assim:
[
{
url: '/pt/inmueble/99004556/',
title: 'Apartamento em ronda de Buenavista, Buenavista-Valparaíso-La Legua, Toledo',
price: '750€/mês',
details: '\n3 quart.\n115 m² área bruta\n2º andar exterior com elevador\nOntem \n',
description: 'Apartamento para alugar na Ronda Buenavista, em Toledo.Três quartos e duas casas de banho, sala, cozinha, terraço, garagem e arrecadação....'
},
{
url: '/pt/inmueble/100106615/',
title: 'Moradia em banda em Buenavista-Valparaíso-La Legua, Toledo',
price: '1.000€/mês',
details: '\n4 quart.\n195 m² área bruta\nOntem \n',
description: 'Magnífica casa geminada para alugar com 3 andares, 4 quartos aconchegantes, 3 banheiros, sala ampla e luminosa, cozinha totalmente equipa...'
},
{
url: '/pt/inmueble/100099977/',
title: 'Moradia em banda em calle Francisco Ortiz, Buenavista-Valparaíso-La Legua, Toledo',
price: '800€/mês',
details: '\n3 quart.\n118 m² área bruta\n10 jan \n',
description: 'O REMAX GRUPO FV aluga uma casa mobiliada na Calle Francisco Ortiz, em Toledo.Moradia geminada com 148 metros construídos, distribuídos...'
},
{
url: '/pt/inmueble/100094142/',
title: 'Apartamento em Buenavista-Valparaíso-La Legua, Toledo',
price: '850€/mês',
details: '\n4 quart.\n110 m² área bruta\n1º andar exterior com elevador\n10 jan \n',
description: 'Apartamento muito espaçoso para alugar sem móveis, cozinha totalmente equipada.Composto por 4 quartos, 1 casa de banho, terraço.Calefaç...'
}
]Contornar a deteção de bots
Se executar o seu script pelo menos duas vezes ao longo deste tutorial, já deve ter reparado nesta página irritante:
O Idealista utiliza o DataDome como proteção antibot, que incorpora um desafio CAPTCHA GeeTest. Deve mover a peça do puzzle até a imagem ficar completa e, em seguida, será redirecionado de volta para a sua página de destino.
Pode facilmente pausar o seu script Puppeteer até resolver o desafio utilizando este código:
await page.waitForFunction(() => {
const pageContent = document.getElementById('main-content')
return pageContent !== null
}, {timeout: 10000})
Isto diz ao nosso script para esperar 10 segundos até que um seletor CSS específico apareça no DOM. Deve ser suficiente para resolver o CAPTCHA e, em seguida, deixar a navegação ser concluída.
… A menos que a página da Idealista o bloqueie de qualquer forma.
Nesta altura, o processo tornou-se mais complexo e desafiante, e ainda nem sequer expandiu o seu projeto.
Como mencionei anteriormente, a Idealista está protegida pelo DataDome. Eles recolhem vários dados do navegador para gerar e associar-lhe uma impressão digital única. Se suspeitarem de algo, receberá o desafio CAPTCHA acima, que é bastante difícil de resolver automaticamente.
Entre os dados do navegador recolhidos, encontramos:
- propriedades do objeto Navigator (deviceMemory, hardwareConcurrency, languages, platform, userAgent, webdriver, etc.)
- verificações de tempo e desempenho
- WebGL
- WebRTC deteção de IP
- registo dos movimentos do rato
- incoerências entre o User-Agent e o seu sistema operativo
- e muito mais.
Uma forma de superar estes desafios e continuar a fazer scraping em grande escala é utilizar uma API de scraping. Este tipo de serviços oferece uma forma simples e fiável de aceder a dados de sites como o Idealista.com, sem a necessidade de criar e manter o seu próprio scraper.
A WebScrapingAPI é um exemplo desse tipo de produto. O seu mecanismo de rotação de proxies evita completamente os CAPTCHAs, e a sua base de conhecimento alargada permite randomizar os dados do navegador para que pareça um utilizador real.
A configuração é rápida e fácil. Basta registar uma conta para receber a sua chave API. Esta pode ser acedida a partir do seu painel de controlo e é utilizada para autenticar os pedidos que enviar.
Como já configurou o seu ambiente Node.js, podemos utilizar o SDK correspondente. Execute o seguinte comando para o adicionar às dependências do seu projeto:
npm install webscrapingapi
Agora, basta ajustar os seletores CSS anteriores à API. A poderosa funcionalidade das regras de extração permite analisar dados sem modificações significativas.
import webScrapingApiClient from 'webscrapingapi';
const client = new webScrapingApiClient("YOUR_API_KEY");
async function exampleUsage() {
const api_params = {
'render_js': 1,
'proxy_type': 'residential',
'timeout': 60000,
'extract_rules': JSON.stringify({
locations: {
selector: 'a.item-link',
output: '@href',
all: '1'
},
titles: {
selector: 'a.item-link',
output: '@title',
all: '1'
},
prices: {
selector: 'span.item-price.h2-simulated',
output: 'text',
all: '1'
},
details: {
selector: 'div.item-detail-char',
output: 'text',
all: '1'
},
descriptions: {
selector: 'div.item-description.description',
output: 'text',
all: '1'
}
})
}
const URL = "https://www.idealista.com/pt/alquiler-viviendas/toledo/buenavista-valparaiso-la-legua/"
const response = await client.get(URL, api_params)
if (response.success) {
// Group the lists
const listings = []
for (let i = 0; i < response.response.data.locations.length; i++) {
listings.push({
url: response.response.data.locations[i],
title: response.response.data.titles[i],
price: response.response.data.prices[i],
details: response.response.data.details[i],
description: response.response.data.descriptions[i].replace(/(\r\n|\n|\r)/gm, "")
})
}
console.log(listings.length, listings)
} else {
console.log(response.error.response.data)
}
}
exampleUsage();Conclusão
Neste artigo, mostrámos-lhe como fazer scraping do Idealista, um popular site imobiliário espanhol, utilizando TypeScript e Puppeteer. Passámos pelo processo de configuração dos pré-requisitos e de scraping dos dados, e discutimos algumas formas de melhorar o código.
O web scraping do Idealista pode fornecer informações valiosas para empresas e particulares. Ao utilizar as técnicas descritas neste artigo, pode extrair dados como URLs de imóveis, preços e descrições do site.
Além disso, se quiser evitar as medidas anti-bot e a complexidade do processo de scraping, utilizar um scraper profissional pode ser mais eficiente e fiável do que criar o seu próprio.
Seguindo os passos e as técnicas descritas neste guia, pode explorar o potencial do web scraping do Idealista e utilizá-lo para dar resposta às necessidades do seu negócio. Quer seja para pesquisa de mercado, geração de leads ou criação de novas oportunidades de negócio, o web scraping do Idealista pode ajudá-lo a manter-se à frente da concorrência.




