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

房地产网站爬取:如何像专业人士一样从Realtor.com提取数据

房地产网站爬取:如何像专业人士一样从Realtor.com提取数据

对于许多行业的企业和个人而言,收集准确且最新的数据至关重要,房地产行业也不例外。Realtor.com 是一个广受欢迎的网站,用于查找待售或待租的公寓和住宅,因为它包含大量对房地产专业人士、投资者和购房者都极具价值的信息。

在本教程中,我将向您展示如何抓取 Realtor.com 的数据,以便您获得启动项目所需的信息。我将逐步讲解项目设置、访问 Realtor.com 以及提取目标数据的全过程。

此外,我还将探讨如何提升爬虫工具的可靠性和效率,并说明在某些使用场景下,为何选择专业的爬虫服务可能是更优的选择。

无论您是寻求竞争优势的房地产从业者、寻找新机遇的投资者,还是寻找理想房产的购房者,完成本教程后,您都将对如何抓取 realtor.com 数据有深入的了解。

环境配置

在开始抓取之前,您需要在计算机上安装 Node.js。您可以从官方网站下载最新版本,并根据您的操作系统按照说明进行安装。

随后,为您的项目创建一个新目录,并在终端或命令提示符中切换至该目录。运行以下命令初始化一个新的 Node.js 项目:

npm init -y

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

要安装 TypeScript,请运行以下命令:

npm install typescript -save-dev

TypeScript 是 JavaScript 的超集,增加了可选的静态类型检查及其他功能。它适用于大型项目,并能帮助您更早地发现错误。TypeScript 使用名为 tsconfig.json 的配置文件来存储编译器选项和其他设置。要在项目中创建此文件,请运行以下命令:

npx tsc -init

请确保将“outDir”的值设置dist”。这样,我们将把 TypeScript 文件与编译后的文件分开存放。

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

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

"scripts": {

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

}

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

最后但同样重要的是,运行以下命令将 Puppeteer 添加到项目依赖项中:

npm install puppeteer

Puppeteer 是一个 Node.js 库,它提供了一个用于控制无头 Chrome 浏览器的高级 API,可用于网页抓取和自动化任务。由于当今许多网站都包含动态生成的内容,因此当你需要确保数据完整性时,强烈建议使用 Puppeteer。

数据筛选

现在环境已配置完毕,我们可以开始探讨数据提取了。本文中,我选择抓取德克萨斯州普莱诺市(Plano, TX)可供出租的单间公寓列表:https://www.realtor.com/apartments/Plano_TX/beds-studio。

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

  • URL;
  • 价格;
  • 卫生间数量;
  • 面积(以平方英尺为单位);
  • 实际地址

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

数据提取

要提取所有这些数据,我们需要先定位它们。右键点击高亮显示的区域,然后选择“检查”以打开开发者工具并查看 HTML 文档。将鼠标光标悬停在其上,即可轻松查看每个区域对应的具体部分:

在本教程中,我将使用 CSS 选择器,因为这是最直接的方法。如果您对这种方法不熟悉,不妨先阅读这篇通俗易懂的指南

在开始编写脚本之前,让我们先验证一下 Puppeteer 是否安装成功:

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

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

由于每条房源信息都具有相同的结构和数据,在我们的算法中,我们将提取整个房源列表中的所有信息。在脚本结尾,我们将遍历所有结果,并将它们集中到一个列表中。

您可能已经注意到,在第一张截图中房源 URL 不可见,但在第二张截图中已标出并高亮显示。这是因为点击房源时,系统会将您重定向至该房源的 URL。

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

我们通过筛选具有“data-testid”属性且值为“card-link”的锚点元素来定位 URL。随后将结果转换为 JavaScript 数组,并将每个元素映射到“href”属性的值。

不过,生成的列表中每个 URL 都会出现两次。这是因为每个房源在两个部分(房产图片和租赁详情)中都使用了相同的锚点元素。我们可以利用 Set 数据结构轻松解决这个问题:

const unique_listings_location = [...new Set(listings_location)]

console.log(unique_listings_location)

对于房源价格,我们将提取具有“data-testid”属性且值为“card-price”的“div”元素。同样需要将其转换为数组,然后映射到其文本内容。

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

要获取浴室数量和房产面积,我们将使用操作符来处理直接子元素。这意味着父元素具有唯一标识,而子元素则使用更通用的 ID 或类名。除此之外,逻辑与之前相同:

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

最后,对于房源的地址,我们筛选具有“data-testid”属性且其值为“card-address”的“div”元素。

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

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

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

最终结果应类似如下:

[

    {

        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'

    }

]

避免被识别为机器人

虽然初看之下抓取Realtor似乎很简单,但随着项目规模的扩大,这个过程会变得越来越复杂和具有挑战性。该房地产网站采用了多种技术来检测和阻止自动化流量,因此当你扩大抓取规模时,你的抓取程序就会开始被封锁。

Realtor 采用 PerimeterX 提供的“按住不放”式 CAPTCHA,众所周知,这种验证码几乎无法通过代码破解。此外,该网站还会收集多种浏览器数据,以此生成并关联您的唯一指纹。

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

  • Navigator对象的属性(deviceMemory、hardwareConcurrency、languages、platform、userAgent、webdriver等)
  • 时序与性能检测
  • WebGL
  • WebRTCIP嗅探
  • 以及更多内容

要克服这些挑战并继续进行大规模抓取,一种方法是使用抓取 API。此类服务提供了一种简单可靠的方式,可从 Realtor.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': '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();

结语

在本教程中,我们详细介绍了如何使用 Node.js 和 Puppeteer 抓取 realtor.com 的数据。此外,我们还探讨了如何提升抓取工具的可靠性和效率,并分析了在某些使用场景下,为何采用专业的抓取服务可能是更优的选择。

Realtor.com 是广受欢迎且极具价值的房地产数据来源。凭借本教程中掌握的技能与知识,您现在应该能够利用网络爬虫提取这些数据,并将其应用于自己的项目中。

无论您是寻求竞争优势的房地产从业者、寻找新机遇的投资者,还是正在寻找理想房产的购房者,网络爬虫都能为您从 realtor.com 获取宝贵的洞察和数据。我们希望本教程对您有所帮助,并祝您借助 realtor.com 的网络爬虫技术,在房地产领域更上一层楼。

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

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

开始构建

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

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