Zillow 爬虫:如何使用 Python 抓取房产数据

当价格不断变动、昨天的电子表格已经过时时,跨城市比较房源会变得很繁琐。Zillow 爬虫会从房源页面收集房产信息,并整理成可比较、可更新、可导出的记录,无需手动重建电子表格。
TL;DR
先确认访问权限:Zillow 公布的条款禁止自动化查询,包括爬取。在规划实时提取之前,请选择官方批准的数据计划,或其他你有权收集和复用的数据来源。
先测试小样本。将提取到的价格、地址和房产详情与来源逐一核对。
谨慎处理更多结果。跟进分页、去除重复项,并排查缺失值。
让代理设置与任务匹配。如需控制位置和会话,可考虑 轮换住宅代理;如需不限流量,可考虑 移动代理。两者都不能保证可以访问 Zillow。
导出前先验证。将挂牌价与估值分开存放,并保留采集日期。
CSV 列预览
zpid,address,listing_status,asking_price,bedrooms,bathrooms,source_url,collected_at
Zillow 爬虫可以提取哪些数据
Zillow 数据爬虫可以从搜索结果、单个房产页面和专业人士资料页中收集不同的信息。搜索结果通常提供房源摘要,而房产页面可能还包含描述、估值或历史记录。联系方式字段需要单独核实,因为经纪人或经纪公司的联系方式不同于房主的信息。
在爬取 Zillow 数据之前,先定义输出结构。下面的 Python 示例使用这个仅限出售房源的结构:
字段 | 格式 | 在本示例中的含义 |
zpid | 必填字符串 | 房产标识符,以文本形式保存,用于去重。 |
address | 必填字符串 | 搜索记录提供的地址文本。 |
listing_status | 必填字符串 | FOR_SALE;示例排除其他挂牌状态。 |
asking_price | 数字或 null | 出售挂牌价,本示例假定单位为美元。 |
bedrooms | 数字或 null | 上报的卧室数量;0 仍是有效值。 |
bathrooms | 数字或 null | 上报的卫生间数量,若来源提供则包含小数值。 |
zestimate | 数字或 null | 在示例的美元假设下的房产估值,与挂牌价分开存放。 |
description | 字符串或 null | 从所选房产详情文档中提取的描述。 |
source_url | 必填字符串 | 与搜索记录关联的房产 URL。 |
search_source_url | 必填字符串 | 标识提供该记录的搜索文档的 URL。 |
collected_at | 带时区的时间戳 | 搜索文档的观测时间。 |
detail_collected_at | 时间戳或 null | 详情文档单独的观测时间;若未处理详情文档则为 null。 |
synthetic | 布尔值 | 标记由声明为合成数据的输入生成的记录。值为 false 并不能独立证明数据真实。 |
对于已保存的文档,观测时间来自输入清单;可选的实时抓取器则记录抓取完成时间。所提供的演示使用虚构的时间戳。
可选的缺失值在 JSON 中保持为 null,在 CSV 中保持为空白。缺少必填字段或数字格式错误时,示例会停止运行,而不是生成猜测的值。
租金、最近成交价、面积、历史记录以及专业人士联系方式字段需要额外的、经过验证的映射,本示例不导出这些字段。扩展结构时,请将挂牌价、成交价、租金金额和估值分开存放。
在 Python、爬取工具和 Zillow 数据接入之间做选择
请根据所需的数据集和你能承担的维护工作量来选择方案。一次性的电子表格导出可能适合托管工具,而带自定义字段的重复采集则需要对提取、验证和更新有更多控制。
方案 | 适用场景 | 主要注意事项 |
Python 脚本 | 自定义提取、验证和存储 | 需要自行维护代码并核实来源的访问权限。 |
通过本地服务实现可复用的浏览器提取 | 需要部署,并需要针对目标页面测试过的提取预设。 | |
浏览器扩展或托管爬虫 | 无需维护采集器即可导出 | 检查可用字段、采集限制、隐私政策和总成本。 |
托管爬取 API | 将页面抓取或提取外包 | 确认返回内容是 HTML 还是结构化的房产记录。 |
官方数据计划或研究数据下载 | 经批准的集成或区域市场分析 | 检查数据集覆盖范围、访问条件和允许的用途。 |
在选定工具之前,先用包含所需字段的小样本进行测试。检查它能否获取房产详情还是只能获取搜索摘要,如何处理缺失值,以及导出时是否保留标识符和日期。
以“Zillow API”名义销售的第三方产品不一定是 Zillow 官方服务。它可能只是采集网站内容,再通过自己的端点提供;仅凭名称无法确定其隶属关系、授权或采集许可。
Zillow Research 提供区域住房指标,包括房价、租金和库存。Zillow Group 的开发者计划涵盖特定产品,各有其访问条件。这些选项用途各不相同,不应被视为可以不受限制地访问所有房源或联系方式字段。
进一步了解无代码网页爬虫。
如何用 Python 爬取 Zillow
此示例将已保存的搜索页和房产页文档转换为 CSV 和 JSON 记录。它基于合成 HTML 样本和模拟的 HTTP 响应进行了测试,验证期间没有抓取任何 Zillow 实时页面。
脚本读取一个搜索文档,最多保留五个不重复的 FOR_SALE 房产,并可用匹配的详情文档补充一个选定的房产。五条记录的上限只属于本示例,并非 Zillow 搜索结果的上限。
测试环境:Linux x86_64、Python 3.12.14、Requests 2.34.2 和 Beautiful Soup 4.15.0。依赖项已在 requirements.txt 中固定版本。该示例于 2026 年 11 月 4 日测试,并于 2026 年 11 月 5 日复查。可下载 Python 示例、固定版本的依赖项、已测试的示例、自动化测试,以及对应的 CSV 和 JSON 导出文件。
设置 Python 并定义输出
解压随附的示例文件,进入其目录,并创建 Python 环境:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt输出包含房产标识符、地址、挂牌状态、挂牌价、卧室数、卫生间数、来源 URL 和观测时间戳。可选的房产详情包括 Zestimate 和描述。
请将凭据保存在脚本之外。其可选的 HTTP 代理配置会从环境变量中读取 ZILLOW_PROXY_URL;不设置则使用直连。这个 Python 客户端不会继承桌面版 Chrome 的代理设置。
所提供的 sample.json 指定了两个合成 HTML 文件及其示例抓取时间。处理已保存页面时会保留这些时间戳,而不是把解析日期当作采集日期。
检查一个搜索页和一个房产页
在决定如何从 Zillow 爬取数据之前,先检查你获得授权的页面。将原始 HTML 响应与浏览器渲染后的页面进行对比,确定所需信息出现在哪里。
示例解析器会在以下位置查找内嵌的 JSON:
<script id="__NEXT_DATA__" type="application/json">然后检查这些特定结构:
Search records:
props.pageProps.searchPageState.cat1.searchResults.listResults
Property details:
props.pageProps.componentProps.gdpClientCache这些只是所提供样本验证过的假设,并非 Zillow 的永久接口。使用解析器之前,请对照你获准使用的来源文档进行验证。
如果字段只有在 JavaScript 运行后才出现,HTTP 请求和 HTML 解析器无法复现这种渲染。可能需要使用浏览器自动化,但渲染本身既不代表获得许可,也不能保证访问成功。
抓取并验证响应
完整脚本中的 fetch_html() 函数会在返回 HTML 之前检查 HTTP 状态、最终 URL 和内容类型:
defp fetch_html(url):
url = source_url(url)
proxy = os.getenv("ZILLOW_PROXY_URL")
if proxy and urlsplit(proxy).scheme != "http":
raise ValueError("This example supports an HTTP proxy endpoint only")
with requests.Session() as session:
session.trust_env = False
if proxy:
session.proxies = {"https": proxy}
with session.get(
url,
timeout=(5, 20),
allow_redirects=False,
) as response:
if response.status_code != 200:
raise ValueError(
f"Stopped: HTTP {response.status_code}; no automatic retry"
)
if response.url != response.request.url:
raise ValueError("Unexpected final URL")
source_url(response.url)
media_type = (
response.headers.get("Content-Type", "")
.split(";")[0]
.lower()
.strip()
)
if media_type not in {"text/html", "application/xhtml+xml"}:
raise ValueError("Expected an HTML response")
return response.text超时参数允许 5 秒用于建立连接,以及 20 秒的读取空闲时间;它们并不是总执行期限。Requests 在其超时说明中记录了这一区别。
HTTP 200 响应仍需验证内容。验证页面、意外的文档或已变更的数据结构,不能被当作包含零条记录的成功导出。下一个函数会检查是否存在预期的 JSON 容器:
def page_props(html):
soup = BeautifulSoup(html, "html.parser")
node = soup.find("script", id="__NEXT_DATA__")
if node is None:
raise ValueError(
"Expected JSON block absent: inspect the returned page"
)
try:
props = json.loads(node.get_text())["props"]["pageProps"]
except (KeyError, TypeError, json.JSONDecodeError) as exc:
raise ValueError("Invalid or changed page-data structure") from exc
if not isinstance(props, dict):
raise ValueError("Expected a pageProps object")
return props提取房源记录和选定房产的详情
示例只保留明确标记为 FOR_SALE 的记录,要求有标识符和地址,并在应用五条记录的上限之前去除重复的 ZPID。租赁和已售记录会被排除,以避免它们的价格被标成挂牌价。
可选的数值字段在缺失时保持为 None。意外的字符串、负值和无效数字会使处理停止,而不是被悄悄转换:
def number(value, positive=False):
if value is None:
return None
if (
isinstance(value, bool)
or not isinstance(value, (int, float))
or not math.isfinite(value)
or value < 0
or (positive and value == 0)
):
raise ValueError(
"Unexpected numeric value; review the field mapping"
)
return value仅在需要额外字段时才引入可选的详情页。其 URL 和房产标识符必须与所选的搜索记录匹配。解析器会将其 Zestimate 与挂牌价分开存储,并记录单独的详情页时间戳。
这正是准确爬取 Zillow 数据的关键:保留每个字段的含义,绝不凭空生成缺失值。
运行小样本并检查结果
针对所提供的合成页面运行完整脚本:
python zillow_sample.py sample.json --output run-01输出目录必须是新建的。已测试的样本会产生:
Saved 2 unique listings to run-01其中一条结果记录如下。以下所有房产信息均为虚构:
{
"zpid": "9000000001",
"address": "100 Example Lane, Sample City, TX 00000",
"listing_status": "FOR_SALE",
"asking_price": 395000,
"bedrooms": 3,
"bathrooms": 2,
"source_url": "https://www.zillow.com/homedetails/example-property/9000000001_zpid/",
"search_source_url": "https://www.zillow.com/synthetic-test-city/",
"collected_at": "2026-11-04T10:00:00+00:00",
"zestimate": 400000,
"description": "Synthetic property description for parser testing only.",
"detail_collected_at": "2026-11-04T10:01:00+00:00",
"synthetic": true
}CSV 导出中的部分列展示了缺失值是如何保留的:
zpid,listing_status,asking_price,bedrooms,bathrooms,zestimate
9000000001,FOR_SALE,395000,3,2,400000
9000000002,FOR_SALE,,0,1,将标识符、价格、缺失字段和重复数量与输入数据进行核对。第二条记录有意保留了缺失的价格,同时保留了 0 间卧室。
对于获得授权的实时采集,请提供包含已验证 URL 的单独清单并使用 --live;在该模式下,脚本会拒绝合成输入。可以用以下命令重新运行随附的测试:
python -m unittest -v这些测试通过只能证明所演示的行为,不代表当前可以访问 Zillow。在扩大采集之前,请验证真实的页面结构和获准使用的样本。
验证说明
全部 13 项自动化测试于 2026 年 11 月 4 日通过,并在 11 月 5 日再次通过。检查使用了合成 HTML 样本和模拟的 HTTP 响应。
检查项 | 结果 |
样本范围 | 一个搜索文档和一个匹配的详情文档。 |
原始条目 | 去重前有三条搜索条目。 |
导出记录 | 去除一条重复项后,剩余两个不重复的房产。 |
被拒绝的记录 | 所提供的演示样本中没有。 |
不完整的记录 | 有一条保留的记录包含缺失的可选值。 |
缺失值 | 该记录中的 asking_price、zestimate、description 和 detail_collected_at 为 null。 |
导出对比 | 重新生成的 CSV 和 JSON 与所提供的样本文件一致。 |
缺失详情时间戳表示没有处理该房产的详情文档,并不意味着详情请求失败。
另有测试检查了预期的失败情况,包括被拒绝的响应、意外的 URL 或内容类型、缺失的标识符、格式错误的 JSON 以及变更后的结构。尚未验证与 Zillow 实时页面的兼容性、分页,以及通过代理服务商的连接。
使用 Yozh Scraper 实现可复用的提取
GitHub 上的 Yozh Scraper 提供自托管服务,支持浏览器渲染、字段提取和异步任务。你可以在多次请求中复用提取规则,同时由 Python 负责验证和存储。
Yozh Scraper 提供浏览器渲染、提取规则和可复用的预设,用于构建自定义配置。本指南不包含经实时页面验证的 Zillow 预设。在将自定义预设用于定期的房产采集,并将其作为 Zillow 网页爬虫使用之前,请对照获准使用的来源文档验证所需字段。
启动服务并检查其健康状态。克隆仓库后,将 .env.example 复制为 .env,然后运行 docker compose up --build。在另一个终端中检查 http://localhost:8000/api/v1/health。健康的响应只能确认服务可用,并不代表房产提取成功。
创建并验证自定义预设。打开 http://localhost:8000/docs,使用经过验证的 CSS 或 XPath 规则创建预设。名称以 user_ 开头,例如 user_zillow_property。使用获准使用的样本,通过 POST /api/v1/presets/{name}/test 进行测试。在复用规则之前,再检查另一个房产页面。
运行一个小任务。使用已保存预设的配置,通过 POST /api/v1/scrape/preset/page 提交单个房产 URL。保留返回的 job_id;它标识的是后台任务,并不表示提取已成功。
验证房产字段。将结果映射到与 Python 示例相同的结构:房产标识符、地址、挂牌状态、挂牌价、卧室数、卫生间数和来源 URL。添加采集时间戳,检查数值类型,并保留缺失的可选值。在增加采集量之前,将输出与来源进行比较。
通过 Python 获取结果。以一定的延迟和总超时轮询 GET /api/v1/scrape/{job_id},并处理失败或已取消的任务。任务完成后,请求 GET /api/v1/scrape/{job_id}/results,检查错误和警告,然后在导出之前验证这些记录。
有关更完整的安装说明和可选的可视化测试器,请参阅屏幕抓取指南。
处理分页和不完整的搜索结果
一次 Zillow 搜索应被视为一个限定的样本,而不是完整的区域库存。即使搜索显示了更大的总数,地理边界、房源筛选条件、分页和采集失败都会影响最终出现哪些记录。
在扩大采集之前,先定义一条记录代表什么:一处房产、一个单元、一栋楼,还是一条挂牌信息。不应仅因为公寓楼与其可租售单元共用同一街道地址就将它们合并。
上面的 Python 示例只读取一个搜索页,没有实现分页。要扩展它,请检查获准使用的搜索界面,并确认下一页是如何请求的。在整个运行过程中保持原有的地理边界、筛选条件和排序设置,并将它们与数据集一起记录下来。
当页面重复、不再返回新标识符,或达到你设定的页数、记录数或运行时间上限时,就停止采集。记录停止原因,以便区分有意限制的样本和失败的采集。
对重叠的结果去重时,应使用能标识目标实体的 ZPID,或其他经验证的房产、单元或挂牌标识符。避免仅依赖地址。
每次运行时,请记录:
原始记录:去重和验证之前返回的条目。
唯一记录:去除重复项后保留的条目。
被拒绝的记录:被排除的条目及原因。
不完整的记录:缺少可选字段但被保留的条目;它们同样计入唯一记录。
报告所测试方法实际展示的覆盖范围。已查阅的指南描述了不同的搜索限制和方法,因此不要把某个固定数字当作 Zillow 通用的结果上限。
清洗 Zillow 数据并导出 CSV 或 JSON
提取的记录只有在排序、筛选和比较之后仍保持其含义时才有用。导出之前,请统一规范每个字段,并对照来源检查几条记录。
将邮政编码保存为字符串。前导零很重要,因此在 Excel 中打开 CSV 时,请将该列按文本导入。
拆分价格组成部分。分别存储金额、币种、价格类型和租金周期。挂牌价、最近成交价和 Zestimate 应放在不同的字段,或带有清晰标签的不同记录中。
保留面积单位。将度量值与其单位(如平方英尺)一起保存,以便后续计算使用可比较的数值。
区分缺失值和零。对不可用的信息使用 JSON null 或空白的 CSV 单元格。0 间卧室可能表示单间公寓,不应自动变成缺失值。
保留数据出处。包含来源 URL 和采集时间戳,以便追溯和核查每次观测。
CSV 适合扁平的房产记录。嵌套的价格或挂牌历史请保留在 JSON 中,或导出到以房产标识符关联的单独表中。
做监控时,请保留带日期的观测记录,而不是覆盖之前的值。某条房源在之后的运行中消失,并不能证明该房产已售出:筛选条件变化、采集不完整或房源下架都可能是原因。
来自所提供测试文件的 JSON 记录示例
这是 Python 示例生成的第二条记录。其房产标识符、地址、URL 和观测时间戳均为虚构。缺失的挂牌价保持为 null,而 0 间卧室保持为数值 0。
然后插入这段 JSON:
{
"zpid": "9000000002",
"address": "200 Example Lane, Sample City, TX 00000",
"listing_status": "FOR_SALE",
"asking_price": null,
"bedrooms": 0,
"bathrooms": 1,
"source_url": "https://www.zillow.com/homedetails/second-example/9000000002_zpid/",
"search_source_url": "https://www.zillow.com/synthetic-test-city/",
"collected_at": "2026-11-04T10:00:00+00:00",
"zestimate": null,
"description": null,
"detail_collected_at": null,
"synthetic": true
}下面的检查项也涵盖你之后可能添加的字段;所提供的脚本使用前面定义的更窄的、仅限出售的结构。
为任务选择代理和会话设置
代理控制爬虫使用的网络路由和出口 IP。请根据流量规模、位置和会话连续性来选择连接;没有任何代理类型能保证访问 Zillow。
连接类型 | 适用的工作负载 | 价格 |
轮换住宅代理 | 支持位置定向,并提供粘性会话最长 24 小时,用于需要连续的步骤,最适合网页爬取、AI 数据采集、自动化、SEO 和市场调研。 | 标准 IP 池:$2/GB,地理定位免费 |
移动代理 | 干净的 IP 搭配不限流量,在反复渲染下载内容、脚本和图片时有助于控制带宽成本,最适合网页爬取、自动化、SEO 和市场调研。 | LTE/5G $1.7/天 ,支持 HTTP、SOCKS5 和 UDP 协议。 |
固定地址适合需要一致路由的重复观测,提供不限带宽,最适合网页爬取、QA 测试、自动化和特定地区访问。 | $5.29/月 ,正常运行时间 99.9%。 | |
高速独享 IPv4/IPv6 数据中心代理,不限带宽;最适合网页爬取、自动化、监控、API 集成和大规模数据采集。 | $2.18/月 ,支持 HTTP 和 SOCKS5 协议。 |
对于基于浏览器的采集器,一种有文档记载的配置是 Ubuntu 24.04、Playwright Chromium,以及使用用户名/密码认证的 HTTP 代理。通过 browser.new_context(proxy=...) 设置服务器、用户名和密码,并从环境变量加载凭据。Playwright 的文档将 HTTP 代理凭据与 SOCKSv5 支持分开说明,因此不要假定带认证的 SOCKS5 的工作方式与之完全相同。
在同一个浏览器上下文中打开 IP 检测页面,核实出口 IP 和位置。然后测试具有代表性且获准的提取,并在增加工作负载之前检查返回的记录。
使用 CyberYozh App 的IP 地址检测,借助主流反欺诈系统获得真实评估。
如何爬取包含电话号码的 Zillow 数据
联系方式字段是否可用,取决于房源、来源和提取方法。在为 Zillow 爬虫添加电话号码之前,请确认获准使用的来源确实公开了该联系方式字段,并标明其所属对象。
经纪人和经纪公司的联系方式不同于房主的信息。房源旁显示的号码不应自动成为数据集中的 owner_phone。请查看其附带的姓名和标签,保留来源所声明的关系,而不是猜测接听者是谁。
对于每条获准使用的专业人士联系方式,请保留:
电话号码:以文本形式存储,包括所显示的任何分机号。
姓名和角色:与来源标识的完全一致。
来源 URL:包含该联系信息的页面。
采集时间戳:观测到该信息的时间。
将多个联系人分开存放,以免经纪人的号码被关联到经纪公司的名称上。缺失的号码在 JSON 中应保持为 null,在 CSV 中保持为空白;提取错误请单独记录,而不是用猜测的内容填补空缺。
公开显示并不等于允许任何形式的采集、复用或联络。在采集或使用专业人士联系方式之前,请查阅来源的访问条件和适用的隐私要求。
排查常见的 Zillow 爬取问题
在更改设置之前,先确定失败发生在哪个环节。连接问题涉及路由、认证或传输;访问限制会拒绝或限制请求;提取错误则发生在采集器无法正确解读返回内容时。
症状 | 可能的原因 | 下一步检查 |
403 响应或验证页面 | 访问被拒绝或自动化流量检测 | 检查响应正文,确定是哪个服务返回的。暂停重复尝试,并核实获准的访问方式。 |
请求速率限制 | 如有 Retry-After,请遵守。降低请求频率和并发数,使用逐步增加的重试延迟,并限制尝试次数。 | |
HTTP 200,但没有记录 | 意外的内容或提取不匹配 | 检查最终 URL、内容类型和已保存的文档。在修改选择器之前,先确认预期字段确实存在。 |
房源覆盖不完整 | 筛选条件、分页、采集限制或请求失败 | 比较地理边界和筛选条件;检查页数、被拒绝的记录和停止原因。 |
房产重复 | 搜索结果重叠或页面重复 | 使用经验证的标识符去重,同时保留不同的单元和带日期的观测记录。 |
缺少电话号码 | 字段不可用或映射错误 | 检查来源和联系方式标签。保留缺失值,不要猜测。 |
价格错误或缺失 | 价格字段错误或转换错误 | 分别核实挂牌价、租金、成交价和 Zestimate。检查币种和租金周期。 |
页面变更后解析器停止运行 | HTML 或内嵌 JSON 结构已变更 | 对比已保存的样本,更新受影响的选择器或路径,并再次验证小样本。 |
对于连接失败,请在发出请求的客户端中核实端点、认证和实际路由。每次修正后,在重新启动更大规模的运行之前,先将提取的值与来源进行比较。
构建可验证的数据集
选择获准使用的来源,验证小样本,并在导出时保留每个字段的含义。只有在将输出与来源比较之后才扩大规模。在设置定期采集之前,请先在 Yozh Scraper 中测试你验证过的提取规则。
如果采集需要特定的出口位置或固定的 IP,请从代理目录中选择合适的连接,并配合你验证过的提取设置进行测试。
