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

房地产网络抓取:如何像专业人士一样从 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,可用于网页抓取和自动化任务。鉴于当今许多网站都包含动态生成的内容,若您希望确保数据的完整性,强烈建议使用该库。

数据选择

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

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

  • 该网址;
  • 价格;
  • 洗澡的次数;
  • 面积(以平方英尺为单位);
  • 物理地址

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

公寓租赁搜索结果,房源卡片以红色突出显示,展示照片、价格和地址详情

数据提取

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

浏览器检查器旁边的公寓房源卡片,其中高亮显示了价格、卫生间数量、面积和地址字段的 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,然后关闭浏览器。为了简化操作并便于可视化调试,我以非无头模式全屏打开浏览器。

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

您可能已经注意到,在第一张截图中,房源链接并未显示,但在第二张截图中却被提及并标出了。这是因为点击该链接后,系统会自动跳转至该房源的页面。

// 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网站似乎很简单,但随着项目规模的扩大,这一过程可能会变得更加复杂和具有挑战性。该房地产网站采用了多种技术来检测和阻止自动化流量,因此当你扩大抓取规模时,你的抓取工具就会开始被封锁。

该房产中介网站采用由PerimeterX提供的“按住不放”式验证码(CAPTCHA),这种验证码以几乎无法通过代码破解而闻名。此外,该网站还会收集多种浏览器数据,以此生成并为您分配一个唯一的指纹标识。

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

  • Navigator 对象的属性(deviceMemory、hardwareConcurrency、languages、platform、userAgent、webdriver 等)。
  • 时间和性能检查
  • WebGL
  • WebRTCIP 嗅探
  • 以及更多

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

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

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

WebScrapingAPI 仪表盘首页展示了关于 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': '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在无需任何基础设施开销的情况下,以企业级规模提取网页数据。