Voltar ao blogue
Guias
Raluca Penciuc3 de março de 202310 minutos de leitura

Como fazer web scraping no Idealista: Um guia completo (Atualização de 2023)

Como fazer web scraping no Idealista: Um guia completo (Atualização de 2023)

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:

Idealista property listings page with browser devtools highlighting HTML for title, price, and description fields

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:

Idealista anti-bot verification page showing a puzzle image with daisies and a slider prompt

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.

Idealista blocked access page stating suspicious usage was detected and access has been blocked

… 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.

Dashboard quickstart guide showing three steps: API access key, API Playground, and integration into your application

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.

Sobre o autor
Raluca Penciuc, Desenvolvedor Full-Stack @ WebScrapingAPI
Raluca PenciucDesenvolvedor Full-Stack

Raluca Penciuc é programadora Full Stack na WebScrapingAPI, onde desenvolve scrapers, aperfeiçoa estratégias de evasão e procura formas fiáveis de reduzir a deteção nos sites-alvo.

Comece a construir

Pronto para expandir a sua recolha de dados?

Junte-se a mais de 2.000 empresas que utilizam a WebScrapingAPI para extrair dados da Web à escala empresarial, sem quaisquer custos de infraestrutura.