返回博客
指南
Raluca PenciucLast updated on Mar 31, 20261 min read

如何抓取Idealista网站:全面指南(2023年更新版)

如何抓取Idealista网站:全面指南(2023年更新版)

Idealista 是南欧领先的房地产网站之一,提供大量关于待售和待租房产的信息。该网站覆盖西班牙、葡萄牙和意大利,收录了数百万套住宅、房间和公寓。

对于希望深入了解西班牙房地产市场的企业和个人而言,该网站堪称一利器。通过网络爬取 Idealista,您可以提取这些宝贵信息,并将其应用于市场调研、潜在客户开发以及创造新的商业机会等多种场景。

本文将提供一份使用 TypeScript 抓取该网站的分步指南。我们将涵盖先决条件、房产数据的实际抓取及优化流程的方法,并阐述为何使用专业抓取工具优于自行开发。

读完本文后,您将掌握从 Idealista 提取数据并将其有效应用于业务的知识和工具。

先决条件

开始之前,请确保已准备好必要的工具。

首先,请从官方网站下载并安装 Node.js,务必选用长期支持(LTS)版本。此操作将自动安装 Node 包管理器(NPM),我们后续将通过它安装其他依赖项。

在本教程中,我们将使用 Visual Studio Code 作为集成开发环境(IDE),但您也可以选择使用任何其他 IDE。为您的项目创建一个新文件夹,打开终端,并运行以下命令来设置一个新的 Node.js 项目:

npm init -y

这将在项目目录中生成一个 package.json 文件,用于存储项目及其依赖项的相关信息。

接下来,我们需要安装 TypeScript 以及 Node.js 的类型定义。TypeScript 提供了可选的静态类型检查功能,有助于防止代码中的错误。为此,请在终端中运行:

npm install typescript @types/node --save-dev

您可以通过运行以下命令验证安装是否成功:

npx tsc --version

TypeScript 使用名为 tsconfig.json 的配置文件来存储编译器选项及其他设置。要在项目中创建此文件,请运行以下命令:

npx tsc -init

请确保将“outDir”的值设置dist”。这样,我们将把 TypeScript 文件与编译后的文件分开。您可以在 TypeScript 官方文档中找到有关此文件及其属性的更多信息

现在,在项目中创建一个“src”目录,并新建一个“index.ts”文件。这里将存放我们的爬取代码。要执行 TypeScript 代码,必须先进行编译,为了确保不会遗漏这一步,我们可以使用自定义命令。

打开package.json”文件,并将“scripts”部分修改如下:

"scripts": {

    "test": "npx tsc && node dist/index.js"

}

这样,当你需要运行脚本时,只需在终端中输入“npm run test”即可。

最后,为了从网站抓取数据,我们将使用 Puppeteer——这是一个用于 Node.js 的无头浏览器库,允许你通过编程方式控制网页浏览器并与网站交互。要安装它,请在终端中运行以下命令:

npm install puppeteer

强烈建议在确保数据完整性时使用该工具,因为如今许多网站都包含动态生成的内容。如果您感兴趣,可以在继续之前查阅 Puppeteer 文档,全面了解其功能。

定位数据

现在环境已配置完毕,我们可以开始探讨数据提取了。本文中,我选择抓取西班牙托莱多地区可售房屋和公寓的列表:https://www.idealista.com/pt/alquiler-viviendas/toledo/buenavista-valparaiso-la-legua/。

我们将从页面上的每条房源信息中提取以下数据:

  • URL;
  • 标题;
  • 价格;
  • 详细信息(房间数量、面积等);
  • 描述

您可以在下方的截图中看到所有这些信息已被标出:

通过在每个元素上打开开发者工具,您将能够看到我们将用于定位 HTML 元素的 CSS 选择器。如果您对 CSS 选择器的运作机制还不太熟悉,欢迎查阅这篇入门指南。

数据提取

在开始编写脚本之前,让我们先验证 Puppeteer 是否已正确安装:

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/")

在此,我们将打开一个浏览器窗口,创建新页面,导航至目标 URL,然后关闭浏览器。为了简化操作并便于可视化调试,我以非无头模式全屏打开浏览器窗口。

由于所有房源列表的结构和数据都相同,我们可以在算法中提取整个房源列表的所有信息。运行脚本后,我们可以遍历所有结果并将其合并为一个列表。

要获取所有房产的 URL,我们定位具有“item-link”类的锚点元素。然后将结果转换为 JavaScript 数组,并将每个元素映射到“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)

对于标题,我们可以利用相同的锚点元素,只不过这次要提取其“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)

对于价格,我们定位具有两个类名的“span”元素:“item-price”和“h2-simulated”。尽可能准确地识别元素至关重要,以免影响最终结果。该结果同样需要转换为数组,然后映射到其文本内容。

// 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)

对于房产详情,我们采用相同的原则,解析具有“item-detail-char”类名的“div”元素。

// 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)

最后是房产描述部分。这里我们使用额外的正则表达式,用于移除所有多余的换行符。

// 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)

现在您应该得到了5个列表,每个列表对应我们抓取的一组数据。如前所述,我们需要将它们合并为一个列表。这样,我们收集的信息将更容易进行后续处理。

// 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)

最终结果应如下所示:

[

  {

    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ç...'

  }

]

绕过机器人检测

如果你在本教程过程中至少运行过两次脚本,可能已经注意到这个烦人的页面:

Idealista 使用 DataDome 作为其反机器人防护措施,其中集成了 GeeTest CAPTCHA 验证。你需要移动拼图块直到图像完整,随后才会被重定向回目标页面。

你可以使用以下代码轻松暂停 Puppeteer 脚本,直到完成验证:

await page.waitForFunction(() => {

    const pageContent = document.getElementById('main-content')

    return pageContent !== null

}, {timeout: 10000})

这段代码会让脚本等待 10 秒,直到指定的 CSS 选择器出现在 DOM 中。这段时间通常足以让你完成 CAPTCHA 验证并完成页面跳转。

……除非 Idealista 页面无论如何都会阻止你。

此时,流程变得更加复杂且具有挑战性,而你甚至还没开始扩展项目。

正如我之前提到的,Idealista 由 DataDome 提供保护。他们会收集多种浏览器数据来生成并为你分配一个唯一的指纹。如果他们产生怀疑,你就会收到上述的 CAPTCHA 验证码,而这很难通过自动化手段解决。

在收集的浏览器数据中,我们发现:

  • Navigator对象的属性(deviceMemory、hardwareConcurrency、languages、platform、userAgent、webdriver等)
  • 时序与性能检测
  • WebGL
  • WebRTCIP嗅探
  • 记录鼠标移动
  • User-Agent 与您的操作系统之间的不一致
  • 等等。

要克服这些挑战并继续进行大规模抓取,一种方法是使用抓取 API。此类服务提供了一种简单可靠的方式,可从 Idealista.com 等网站获取数据,而无需自行构建和维护抓取工具。

WebScrapingAPI 便是此类产品的典型代表。其代理轮换机制能彻底规避 CAPTCHA 验证,而丰富的知识库则可随机化浏览器数据,使其行为与真实用户无异。

配置过程快速简便。您只需注册一个账户,即可获得 API 密钥。该密钥可在控制面板中获取,并用于验证您发送的请求。

既然您已经配置好了 Node.js 环境,我们就可以使用相应的 SDK。运行以下命令将其添加到项目依赖项中:

npm install webscrapingapi

现在只需将之前的 CSS 选择器适配到 API 即可。强大的数据提取规则功能使您无需进行重大修改即可解析数据。

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();

结语

本文向您展示了如何使用 TypeScript 和 Puppeteer 抓取西班牙知名房产网站 Idealista。我们详细介绍了环境配置和数据抓取的流程,并探讨了优化代码的几种方法。

抓取 Idealista 网站可为企业和个人提供有价值的信息。通过运用本文所述的技术,您可以从该网站提取房产 URL、价格和描述等数据。

此外,若您希望规避反机器人措施并简化抓取流程,使用专业的抓取工具往往比自行开发更高效可靠。

通过遵循本指南中概述的步骤和技巧,您可以充分释放 Idealista 网页抓取的潜力,并将其用于满足您的业务需求。无论是市场调研、潜在客户开发,还是创造新的商业机会,Idealista 网页抓取都能助您在竞争中保持领先地位。

关于作者
Raluca Penciuc, 全栈开发工程师 @ WebScrapingAPI
Raluca Penciuc全栈开发工程师

Raluca Penciuc 是 WebScrapingAPI 的全栈开发工程师,主要负责开发爬虫、优化规避机制,并探索可靠的方法以降低在目标网站上的被检测概率。

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网络数据。