简而言之:Redfin 公开了隐藏的 API 接口,这些接口会返回结构化的 JSON 格式房产列表数据,从而完全避免了易出错的 HTML 解析。本指南将带您逐步构建一个 Python 爬虫,用于提取租赁和销售数据、按位置搜索、通过 XML 站点地图监控新房源,并将整理后的结果导出为 CSV 或 JSON 格式。
引言:为何要从 Redfin 提取房产数据
Redfin 是美国最大的房地产平台之一,覆盖几乎所有大都市区的数百万套住宅房源。 如果您需要抓取 Redfin 数据用于市场分析、投资研究或构建房产数据库,该平台的内部架构实际上对您非常有利。与那些在服务器端渲染所有内容的网站不同,Redfin 的前端从隐藏的 API 端点获取数据,这些端点返回结构良好的 JSON。这意味着您可以通过编程方式提取房产数据,而无需纠结于每次网站更新布局时都会失效的 CSS 选择器。
在本教程中,您将从零开始构建一个基于 Python 的 Redfin 爬虫。 我们将涵盖三种不同的抓取目标(租赁房源、待售房产和搜索结果),向您展示如何通过 Redfin 的 XML 站点地图监控新上架的房产,并逐步演示如何将数据导出为 CSV 和 JSON 格式。在此过程中,我们将探讨速率限制、反机器人防护措施,以及在运行任何大规模房地产网页抓取项目之前您应注意的法律问题。
可抓取的 Redfin 数据字段
在编写代码之前,了解 Redfin 实际公开的数据内容非常有帮助。该平台将房产信息划分为多个类别,了解可用的数据字段将有助于您规划应针对哪些接口,以及输出数据结构应呈现何种形式。
以下是主要抓取目标及预期字段的参考列表:
|
抓取目标 |
关键数据字段 |
|---|---|
|
待售房源 |
挂牌价、成交记录、单价(每平方英尺)、卧室数、浴室数、建筑面积、地块面积、建成年份、业主协会费、房产类型、MLS编号、挂牌经纪人 |
|
租赁房源 |
月租金、押金、租期、卧室数、浴室数、建筑面积、宠物政策、配套设施、可入住日期、物业经理 |
|
搜索结果 |
房产地址、缩略图网址、挂牌状态、价格、卧室/浴室概览、上市天数、坐标 |
|
开放参观 |
预定日期、时间段、房源经纪人、相关房源网址 |
|
经纪人简介 |
经纪人姓名、经纪公司、近期成交记录、评分、服务区域 |
|
土地/地块房源 |
占地面积、土地用途、每英亩价格、可用公用设施、地形说明 |
租赁和销售端点返回不同的 JSON 架构,这在设计数据管道时至关重要。销售房源包含销售历史和市场表现指标等字段,这些在租赁响应中不会出现;而租赁数据则包含宠物政策和押金要求等租赁特有的字段。
先决条件与项目设置
本项目需要 Python 3.8 或更高版本。我们将依赖两个核心库:用于发送 HTTP 请求的 httpx(它能很好地处理异步操作且 API 简洁)以及用于解析任何 HTML 或 XML 响应的 parsel,特别是在处理站点地图时。
创建项目目录并安装依赖项:
mkdir redfin-scraper && cd redfin-scraper
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install httpx parsel
若需在导出时进行更高级的数据清理,可选添加 pandas 若需在导出时进行更高级的数据清洗:
pip install pandas
您的 requirements.txt 代码应如下所示:
httpx>=0.27.0
parsel>=1.9.0
pandas>=2.0.0
仅此即可开始。无需浏览器自动化库,无需 Selenium,也无需 Playwright。由于我们直接针对 Redfin 的隐藏 API 端点,一个简单的 HTTP 客户端就足够了。
了解 Redfin 的隐藏 API 端点
当你在浏览器中加载 Redfin 房产页面时,可见的 HTML 大多只是一个外壳。实际的房产数据是通过异步方式从内部 API 端点获取的。你可以通过打开浏览器的开发者工具(F12),切换到“网络”标签页,并在加载房源页面时按“XHR”或“Fetch”请求进行筛选,从而自行发现这些端点。
您将看到一系列发往类似以下 URL 的请求: https://www.redfin.com/stingray/api/home/details/... ,这些请求会返回结构化数据。响应通常以注释前缀(类似 {}&&{),随后是有效的 JSON 数据。该前缀是跨站脚本防护机制,因此在解析前需将其去除。
这种以API为先的Redfin抓取方法相比传统的HTML解析具有显著优势:
- 稳定性:JSON字段名称极少变更,而CSS类名可能随每次部署而变化。
- 完整性:API响应通常包含比可见页面渲染内容更多的数据。
- 速度:您只需针对每个房源发送一次请求,而非加载包含图片、脚本和样式表的完整页面。
- 简便性:无需浏览器自动化操作。标准 HTTP 客户端即可处理所有流程。
要查找任何房源类型的正确接口,请加载页面,观察网络请求,并寻找其 JSON 响应中包含大部分房产数据的那个请求。URL 模式将包含房产 ID 或 URL 编码地址等标识符。
抓取 Redfin 租房页面
让我们从租赁房源开始。Redfin 通过一个专用的 API 路径提供租赁数据,该路径与出售房源的端点不同。当您访问租赁房源页面时,浏览器会向一个端点发送请求,该端点会以 JSON 格式返回完整的租赁详情。
以下是一个完整的示例代码,用于获取租房列表:
import httpx
import json
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/125.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Referer": "https://www.redfin.com/",
}
def clean_json_response(text: str) -> dict:
"""Strip Redfin's XSS prefix and parse JSON."""
start = text.find("{")
if start == -1:
raise ValueError("No JSON object found in response")
return json.loads(text[start:])
def scrape_rental(url: str) -> dict:
"""Fetch rental property data from Redfin's internal API."""
with httpx.Client(headers=HEADERS, follow_redirects=True) as client:
resp = client.get(url)
resp.raise_for_status()
data = clean_json_response(resp.text)
payload = data.get("payload", {})
rental_info = {
"address": payload.get("streetAddress", {}).get("assembledAddress"),
"rent_price": payload.get("listingPrice"),
"beds": payload.get("beds"),
"baths": payload.get("baths"),
"sqft": payload.get("sqFt"),
"pet_policy": payload.get("petPolicy"),
"amenities": payload.get("amenities", []),
"photos": [p.get("photoUrl") for p in payload.get("photos", [])],
"listing_agent": payload.get("listingAgent", {}).get("name"),
}
return rental_info
# Example usage
listing_url = "https://www.redfin.com/stingray/api/home/details/rental/..."
# result = scrape_rental(listing_url)
# print(json.dumps(result, indent=2))
关于这段代码有几点需要注意。 clean_json_response 函数处理了 Redfin 在 API 响应前缀添加的 XSS 前缀。请求头模拟了真实的浏览器会话,这一点很重要,因为 Redfin 会拒绝那些看起来像来自纯脚本的请求。响应结构将大多数有用的字段嵌套在 payload 键下,但具体嵌套结构可能因房源类型而异。诸如 petPolicy 和 amenities 等仅适用于租赁的字段,在出售房源的响应中不会出现。
抓取 Redfin 待售房产页面
出售房源的接口遵循类似模式,但其 JSON 模式包含出售房源特有的额外字段。出售房源包含历史价格数据、市场竞争力评分以及房产税评估记录,而租赁房源则不具备这些信息。
def scrape_for_sale(url: str) -> dict:
"""Fetch for-sale property data from Redfin's internal API."""
with httpx.Client(headers=HEADERS, follow_redirects=True) as client:
resp = client.get(url)
resp.raise_for_status()
data = clean_json_response(resp.text)
payload = data.get("payload", {})
property_info = {
"address": payload.get("streetAddress", {}).get("assembledAddress"),
"list_price": payload.get("listingPrice"),
"price_per_sqft": payload.get("pricePerSqFt"),
"beds": payload.get("beds"),
"baths": payload.get("baths"),
"sqft": payload.get("sqFt"),
"year_built": payload.get("yearBuilt"),
"lot_size": payload.get("lotSize"),
"hoa_dues": payload.get("hoaDues"),
"property_type": payload.get("propertyType"),
"mls_number": payload.get("mlsId"),
"sale_history": payload.get("priceHistory", []),
"tax_history": payload.get("taxHistory", []),
"listing_agent": payload.get("listingAgent", {}).get("name"),
}
return property_info
与租赁爬虫的主要区别在于提取的字段。 priceHistory 数组提供了自房源上线以来每次价格变动的时序记录,包括挂牌、待售和已售事件。 taxHistory 字段提供随时间变化的评估值,这对投资分析很有帮助。诸如 hoaDues 和 lotSize 等字段仅出现在出售房源中。
在抓取 Redfin 的待售数据时,请注意 propertyType 字段。它会告知您当前查看的是独栋住宅、公寓、联排别墅还是多户住宅,若您需要按特定市场细分筛选结果,这一区分至关重要。
抓取 Redfin 搜索结果页面
单个房源信息虽有参考价值,但大多数房地产数据项目需要批量提取。Redfin的搜索功能同样通过内部API运行,针对指定位置或一组筛选条件返回分页结果。
该搜索接口支持接收诸如区域 ID、房产类型、价格范围和分页偏移量等参数。以下是构建搜索爬虫的方法:
import time
def scrape_search_results(region_id: str, max_pages: int = 5) -> list:
"""Scrape paginated Redfin search results for a region."""
all_listings = []
with httpx.Client(headers=HEADERS, follow_redirects=True) as client:
for page in range(1, max_pages + 1):
search_url = (
f"https://www.redfin.com/stingray/api/gis?"
f"region_id={region_id}®ion_type=6"
f"&num_homes=350&page={page}"
)
resp = client.get(search_url)
resp.raise_for_status()
data = clean_json_response(resp.text)
homes = data.get("payload", {}).get("homes", [])
if not homes:
break
for home in homes:
listing = {
"address": home.get("streetLine", {}).get("value"),
"city": home.get("city"),
"state": home.get("state"),
"price": home.get("price", {}).get("value"),
"beds": home.get("beds"),
"baths": home.get("baths"),
"sqft": home.get("sqFt", {}).get("value"),
"status": home.get("listingType"),
"days_on_market": home.get("dom"),
"latitude": home.get("latLong", {}).get("latitude"),
"longitude": home.get("latLong", {}).get("longitude"),
"url": home.get("url"),
}
all_listings.append(listing)
# Respectful delay between pages
time.sleep(2)
return all_listings
您需要获取 region_id ,可通过在 Redfin 网站上执行搜索时检查网络请求来获取。 region_type=6 参数表示城市级搜索。分页通过递增 page 参数,当 API 返回空 homes 数组时,循环即告结束。
请注意 time.sleep(2) 。若希望爬虫运行时间超过几分钟,此步骤不可省略。我们在“反机器人”章节中将详细探讨速率限制,但合理间隔请求是确保可靠抓取 Redfin 搜索结果的关键。
将抓取的数据导出为 CSV 和 JSON
收集完房产数据后,您需要将其转换为可用的格式。CSV和JSON各有其适用场景:CSV更适合电子表格分析和导入数据库,而JSON则能保留销售历史数组等嵌套结构。
import csv
def export_to_csv(listings: list, filename: str = "redfin_data.csv"):
"""Export flat listing data to CSV with basic cleaning."""
if not listings:
return
# Normalize price fields: strip $ and commas
for item in listings:
if isinstance(item.get("price"), str):
item["price"] = item["price"].replace("$", "").replace(",", "")
keys = listings[0].keys()
with open(filename, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=keys)
writer.writeheader()
writer.writerows(listings)
def export_to_json(listings: list, filename: str = "redfin_data.json"):
"""Export listing data to JSON with indentation."""
with open(filename, "w", encoding="utf-8") as f:
json.dump(listings, f, indent=2, ensure_ascii=False)
若用于生产环境,请在导出前考虑进行几步数据清洗。将日期字符串标准化为 ISO 8601 格式(YYYY-MM-DD)。将价格字符串转换为整数或浮点数,以免后续工具因货币符号而报错。若处理的是 sale_history,请将其展平为独立的列(用于 CSV)或在 JSON 中保留嵌套结构。使用 pandas 配合 json_normalize() ,可让处理大型数据集时的扁平化步骤变得轻而易举。
通过站点地图追踪新房源和更新房源
大多数 Redfin 爬取教程仅止步于抓取单个页面。但若要构建房地产监控管道,您需要一种无需重新爬取整个网站即可自动发现新房源和近期更新房源的方法。
Redfin 发布的 XML 站点地图正好解决了这一问题。截至本文撰写时,相关数据源包括:
https://www.redfin.com/newest_listings.xml用于新添加的房源https://www.redfin.com/sitemap_com_latest_updates.xml用于最近修改的房源
以下是解析这些站点地图并将 URL 导入爬虫的方法:
from parsel import Selector
def parse_sitemap(sitemap_url: str) -> list:
"""Extract property URLs and last-modified dates from a Redfin sitemap."""
with httpx.Client(headers=HEADERS) as client:
resp = client.get(sitemap_url)
resp.raise_for_status()
sel = Selector(text=resp.text, type="xml")
sel.remove_namespaces()
entries = []
for url_tag in sel.css("url"):
loc = url_tag.css("loc::text").get()
lastmod = url_tag.css("lastmod::text").get()
entries.append({"url": loc, "last_modified": lastmod})
return entries
# Example: get newest listings
# new_listings = parse_sitemap("https://www.redfin.com/newest_listings.xml")
# for listing in new_listings[:10]:
# print(listing["url"], listing["last_modified"])
策略很简单:按计划(根据需求设定为每日或每小时)轮询站点地图,将 URL 和时间戳与现有数据库进行比对,仅抓取新增或更新的条目。这种方法比暴力爬取高效得多,因为你让 Redfin 告诉你发生了什么变化,而不是靠猜测。 如果您希望更广泛地抓取网站站点地图,相同的 XML 解析技术几乎适用于所有遵循站点地图协议的网站。
应对反机器人措施并避免被封禁
Redfin 采用了多层防护机制来抵御自动化访问。如果您的 Redfin 抓取工具开始返回 403 错误或 CAPTCHA 验证页面,请参考以下检查要点及解决方法。
模拟浏览器的请求头是必须的。至少要设置一个最新的 User-Agent, Accept, Accept-Language和 Referer 标头。我们的代码示例中已包含这些标头。请定期轮换 User-Agent 字符串,因为在数千次请求中使用单一静态值会被视为异常信号。掌握如何为请求设置 HTTP 标头是任何爬取项目的基础。
请求间隔比代理更重要。在投资代理基础设施之前,请先尝试放慢请求速度。请求间隔2到5秒通常足以让您保持在大部分速率限制阈值之下。在重试时实施指数退避策略:若收到429或403错误,请先等待30秒,然后60秒,再120秒,最后才放弃。
import random
def polite_delay(base: float = 2.0, jitter: float = 1.5):
"""Sleep with randomized jitter to avoid request pattern detection."""
time.sleep(base + random.uniform(0, jitter))
当项目规模扩大时,代理轮换就变得必不可少。若需收集数千条房源信息,通过住宅代理池进行轮换,可使请求看起来像是来自不同的用户。数据中心IP在房地产网站上往往会被迅速标记。
会话管理同样有效。Redfin会在不同请求间追踪Cookie,因此保持会话(复用同一 httpx.Client 实例)实际上比发送无状态请求更能降低被怀疑的风险。只需确保定期启动新的会话即可。
对于高流量项目,专用爬取API可在单一接口后端处理反检测、代理轮换和验证码破解,让你能专注于解析逻辑。网络爬取中避免IP封禁的技巧普遍适用于所有房地产平台,而不仅限于Redfin。
法律与道德考量
在以较大规模运行 Redfin 爬虫之前,您需要了解相关的法律环境。在美国,从网站抓取公开数据通常是被允许的,但其中存在一些重要的细节。
在数据隐私方面,房产列表可能包含个人身份信息(PII):卖方姓名、经纪人联系方式,有时甚至包括电话号码。如果您存储或处理此类数据并为欧盟用户提供服务,无论您的服务器位于何处,都必须遵守《通用数据保护条例》(GDPR)的义务。请将 PII 收集范围缩减至仅满足您的具体用例所需,并实施数据保留政策。
最后,请考虑您的具体应用场景是否真的需要进行数据抓取。Redfin 提供了一个数据中心,可免费下载汇总版住房市场数据(如销售价格中位数、库存水平、在市天数)的 CSV 文件。如果您需要的是市场层面的趋势而非单个房源信息,官方数据可能已足够满足需求。
关键要点
- 使用 Redfin 的隐藏 API 接口,而非解析 HTML。与抓取渲染后的页面相比,JSON 响应更稳定、更完整,且处理速度更快。
- 将租赁和销售房源视为独立的数据模型。它们的 API 响应包含不同的字段,因此您的提取逻辑和输出模式应同时兼顾这两者。
- 通过监控站点地图来获取新房源,而非重新爬取整个网站。按计划进行轮询
newest_listings.xml效率显著更高。 - 优先遵守速率限制,其次再使用代理。请求间隔2至5秒可避免大部分封禁。代理仅用于扩展,而非替代基本的请求礼节。
- 在构建爬虫前,请先查看 Redfin 的官方数据服务。数据中心可能已提供您所需的汇总指标。
常见问题
Redfin 是否提供用于访问房产数据的公开 API?
没有。Redfin 未向第三方开发者提供任何有文档记录的公开 API。本指南中使用的端点是 Redfin 网站用于填充自身页面的内部 API。这些 API 未被文档化且可能随时变更,因此您在构建爬虫时应加入错误处理机制,以应对数据结构的变更。
抓取 Redfin 房源信息是否合法?
在美国,抓取公开数据通常是合法的,但这属于法律灰色地带。法院裁定访问公开网络数据并不违反《计算机欺诈与滥用法案》,但违反网站的服务条款可能导致违约责任。商业用途时请务必咨询法律专业人士,切勿未经授权抓取登录墙后的数据。
如何避免在抓取 Redfin 数据时被封禁?
首先设置合理的浏览器头部信息,并在请求间加入 2 至 5 秒的延迟。随机调整请求时间以避免可预测的模式。轮换 User-Agent 字符串,若进行大规模采集,请使用住宅代理。当收到 403 或 429 响应时,应实施指数退避策略。通过 Cookie 维持持久会话也能降低被检测的风险。
除了 Redfin 之外,还有哪些最佳的房地产数据替代方案?
就房源覆盖范围而言,Zillow、Realtor.com 和 Trulia 是与 Redfin 最接近的平台。MLS(多重上市服务)是权威数据源,但需要持证访问。若需汇总市场统计数据,美国人口普查局和联邦住房金融局会发布免费数据集。每个数据源的覆盖范围、更新频率和访问限制各不相同。
总结与后续步骤
现在,您已拥有一个可运行的 Python 工具包,用于抓取 Redfin 上的三种主要数据类型:租房房源、待售房产和搜索结果。隐藏 API 的方法为您提供了干净、结构化的 JSON 数据,避免了 HTML 解析的脆弱性;而站点地图监控技术则让您无需进行浪费资源的全站爬取,即可追踪新房源。
在此基础上,自然的扩展包括使用 cron 或任务队列安排抓取任务、将结果存储在 PostgreSQL 等数据库中以供历史分析,以及扩展您的数据管道以覆盖其他房地产数据源。如果您发现自己花在应对反机器人保护措施上的时间比编写解析逻辑还要多,那么像 WebScrapingAPI 的 Scraper API 这样的专用抓取 API 可以为您处理代理轮换和请求管理,从而让您能够专注于数据本身。




