返回博客
指南
拉卢卡·彭丘克2023年3月3日阅读时间:10分钟

如何网络抓取理想主义者:综合指南(2023 年更新)

如何网络抓取理想主义者:综合指南(2023 年更新)

先决条件

在开始之前,让我们先确认一下是否备齐了必要的工具。

首先,从官方网站下载并安装 Node.js,确保使用长期支持 (LTS) 版本。这也将自动安装 Node Package Manager(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/。

我们将从页面上的每个列表中提取以下数据:

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

您可以在下面的截图中看到所有这些信息:

Idealista房产列表页面,浏览器开发者工具高亮显示标题、价格和描述字段的HTML代码

通过为这些元素逐一打开开发者工具,您将能够看到我们将用于定位 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)

对于价格部分,我们需要定位同时具有“item-price”和“h2-simulated”这两个类名的“span”元素。准确识别这些元素至关重要,这样才能确保最终结果不受影响。这些元素还需要转换为数组,然后映射到其文本内容。

// 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的反机器人验证页面,显示一张带有雏菊的拼图图像以及一个滑块提示

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

你可以使用以下代码轻松暂停 Puppeteer 脚本,直到你解决该挑战:

await page.waitForFunction(() => {

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

    return pageContent !== null

}, {timeout: 10000})

这会指示我们的脚本等待 10 秒,直到指定的 CSS 选择器出现在 DOM 中。这段时间应该足够你解决验证码,并让导航过程完成。

Idealista 显示访问被阻止的页面,提示检测到可疑使用行为,因此已阻止访问

……除非Idealista网站还是会把你拉黑。

此时,整个过程变得更加复杂和具有挑战性,而你甚至还没有扩大项目规模。

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

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

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

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

WebScrapingAPI 就是这样一款产品。它的代理旋转机制完全避免了验证码,其扩展知识库可以随机化浏览器数据,使其看起来像真实用户。

设置简单快捷。你只需注册一个账户,就会收到 API 密钥。您可以在仪表板上访问该密钥,它用于验证您发送的请求。

仪表盘快速入门指南,包含三个步骤:API 访问密钥、API 测试平台以及集成到您的应用程序中

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

npm install webscrapingapi

现在只需根据 API 调整之前的 CSS 选择器即可。提取规则的强大功能使我们可以在不做重大修改的情况下解析数据。

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进行网页抓取可以为企业和个人提供有价值的信息。通过运用本文介绍的技术,您可以从该网站提取房产网址、价格和描述等数据。

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

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

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

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

开始构建

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

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