Configuração do ambiente
Antes de começar a extrair dados, precisa de instalar o Node.js no seu computador. Pode descarregar a versão mais recente a partir do site oficial e seguir as instruções de acordo com o seu sistema operativo.
Em seguida, crie um novo diretório para o seu projeto e aceda-o no seu terminal ou linha de comandos. Execute o seguinte comando para inicializar 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.
Para instalar o TypeScript, execute o seguinte comando:
npm install typescript -save-dev
O TypeScript é um superconjunto do JavaScript que adiciona tipagem estática opcional e outras funcionalidades. É útil para projetos de maior dimensão e pode facilitar a deteção precoce de erros. 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.
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.
E por último, mas não menos importante, execute o seguinte comando para adicionar o Puppeteer às dependências do seu 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 tarefas de web scraping e automação. É altamente recomendado quando se pretende garantir a integridade dos seus dados, uma vez que muitos sites hoje em dia contêm conteúdo gerado dinamicamente.
Seleção de 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 estúdios disponíveis para arrendamento em Plano, TX: https://www.realtor.com/apartments/Plano_TX/beds-studio.
Vamos extrair os seguintes dados de cada anúncio na página:
- o URL;
- os preços;
- o número de casas de banho;
- as áreas (medidas em pés quadrados);
- os endereços físicos
Pode ver todas estas informações destacadas na captura de ecrã abaixo:

Extração de dados
Para extrair todos estes dados, precisamos primeiro de os localizar. Clique com o botão direito do rato nas secções destacadas e, em seguida, selecione «Inspecionar» para abrir as Ferramentas de Programador e visualizar o documento HTML. Ao passar o cursor do rato por cima, pode ver facilmente qual a parte que corresponde a cada secção:
Para este tutorial, vou utilizar seletores CSS, pois são a opção mais simples. Se não estiver familiarizado com este método, sinta-se à vontade para consultar primeiro este guia autoexplicativo.
Para começar a escrever o nosso script, vamos verificar se a instalação do Puppeteer correu bem:
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")
Aqui, abrimos uma janela do navegador, criamos uma nova página, navegamos até ao nosso URL de destino e, em seguida, fechamos o navegador. Por uma questão de simplicidade e depuração visual, abro o navegador maximizado no modo não headless.
Como cada anúncio tem a mesma estrutura e os mesmos dados, no nosso algoritmo iremos extrair todas as informações para toda a lista de imóveis. No final do script, iremos percorrer todos os resultados e centralizá-los numa única lista.
Talvez tenha reparado que o URL do anúncio não estava visível na primeira captura de ecrã, mas foi mencionado e destacado na segunda. Isso acontece porque é redirecionado para o URL do imóvel quando clica nele.
// 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 o URL escolhendo os elementos âncora que têm o atributo “data-testid” com o valor “card-link”. Em seguida, convertemos o resultado numa matriz JavaScript e mapeamos cada elemento para o valor do atributo “href”.
No entanto, a lista resultante conterá cada URL duas vezes. Isso acontece porque cada anúncio tem o mesmo elemento âncora para duas secções: as imagens do imóvel e os detalhes do arrendamento. Podemos resolver isto facilmente utilizando a estrutura de dados Set:
const unique_listings_location = [...new Set(listings_location)]
console.log(unique_listings_location)
Para o preço do imóvel, iremos extrair os elementos “div” que possuem o atributo “data-testid” com o valor “card-price”. Este também precisa de ser convertido numa matriz e, em seguida, mapeado para o seu conteúdo 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 obter o número de casas de banho e a área do imóvel, utilizaremos o operador para elementos filhos diretos. Isto significa que o elemento pai é identificado de forma única, enquanto o elemento filho tem um ID ou nome de classe mais genérico. Para além disso, a lógica é a mesma de 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)
E, finalmente, para os endereços dos anúncios, selecionamos os elementos “div” que possuem o atributo “data-testid” definido com o 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)
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 < 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)
O resultado final deverá ficar mais ou menos assim:
[
{
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'
}
]Evite a deteção de bots
Embora o scraping do Realtor possa parecer fácil à primeira vista, o processo pode tornar-se mais complexo e desafiante à medida que o seu projeto cresce. O site imobiliário implementa várias técnicas para detetar e impedir o tráfego automatizado, pelo que o seu scraper, à medida que cresce, começa a ser bloqueado.
O Realtor utiliza o modelo «Press & Hold» de CAPTCHA, oferecido pela PerimeterX, que é conhecido por ser quase impossível de resolver a partir do seu código. Além disso, o site também recolhe vários dados do navegador para gerar e associá-lo a uma impressão digital única.
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
- 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 Realtor.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': 'datacenter',
'timeout': 60000,
'extract_rules': JSON.stringify({
locations: {
selector: 'a[data-testid="card-link"]',
output: '@href',
all: '1'
},
prices: {
selector: 'div[data-testid="card-price"]',
output: 'text',
all: '1'
},
baths: {
selector: 'li[data-testid="property-meta-baths"] > span[data-testid="meta-value"]',
output: 'text',
all: '1'
},
sqfts: {
selector: 'li[data-testid="property-meta-sqft"] > span[data-testid="screen-reader-value"]',
output: 'text',
all: '1'
},
addresses: {
selector: 'div[data-testid="card-address"]',
output: 'text',
all: '1'
}
})
}
const URL = "https://www.realtor.com/apartments/Plano_TX/beds-studio"
const response = await client.get(URL, api_params)
if (response.success) {
const unique_listings_location = [...new Set(response.response.data.locations)]
// Group the lists
const listings = []
for (let i = 0; i < unique_listings_location.length; i++) {
listings.push({
url: unique_listings_location[i],
price: response.response.data.prices[i],
baths: response.response.data.baths[i],
sqft: response.response.data.sqfts[i],
address: response.response.data.addresses[i]
})
}
console.log(listings)
} else {
console.log(response.error.response.data)
}
}
exampleUsage();Conclusão
Neste tutorial, fornecemos um guia passo a passo sobre como fazer scraping do realtor.com usando Node.js e Puppeteer. Também discutimos formas de melhorar a fiabilidade e a eficiência do scraper, e por que razão a utilização de um serviço profissional de scraping pode ser uma opção melhor para alguns casos de utilização.
O realtor.com é uma fonte popular e valiosa de dados imobiliários e, com as competências e os conhecimentos que adquiriu neste tutorial, deverá agora ser capaz de utilizar o web scraping para extrair estes dados e aplicá-los nos seus próprios projetos.
Quer seja um profissional do setor imobiliário à procura de uma vantagem competitiva, um investidor em busca de novas oportunidades ou um comprador à procura do imóvel perfeito, o web scraping pode fornecer-lhe informações e dados valiosos do realtor.com. Esperamos que este tutorial tenha sido útil e que esteja agora pronto para elevar o seu nível no setor imobiliário com a ajuda do web scraping do realtor.com.




