~ / guides / How to Scrape Zillow Data (Complete Guide)

How to Scrape Zillow Data (Complete Guide)

DC
Dana Cole
Zillow data engineer · about the author
the short version
  • Zillow does not keep listing data in the visible HTML. The price, beds, baths, sqft, address and zpid live in a hidden __NEXT_DATA__ JSON blob, inside a double-encoded gdpClientCache string. I parse that and the fields fall out clean.
  • A plain request from a datacenter IP returns an HTTP 403 or a PerimeterX Press & Hold challenge. Zillow runs HUMAN Security (PerimeterX), so the IP and browser fingerprint decide the outcome before you see any data.
  • Three routes work: parse the hidden JSON with Python (free, breaks when the page changes), a no-code exporter to Excel for one-off pulls, or a Zillow scraper API that returns parsed JSON and handles proxies for volume.
  • I verified the Python parser and the CSV export against real Zillow page structure in June 2026. The extraction code in this guide runs.

I tried to scrape a Zillow listing the obvious way first: fetch the property URL, parse the HTML, read the price out of a <span>. The price was not in the HTML. Neither were the beds, the baths, or the square footage. Zillow renders almost nothing server-side that you can grab with a CSS selector.

This guide is how to scrape Zillow data the way the page actually works. I will show you where Zillow hides the listing fields, the Python that pulls price, beds, baths, sqft, address and zpid out of that blob, how to export the results to Excel, and what to do about the 403 and the Press & Hold CAPTCHA that stop most scrapers before they start. Every code block here is code I ran.

How do you scrape Zillow data?

You scrape Zillow data by extracting the hidden JSON that Zillow embeds in each page, because the visible HTML does not contain the listing fields. Zillow is a real estate marketplace, so the data people want is the standard set of property fields: price, beds, baths, sqft, address and the zpid identifier. There are three practical routes to get them, and the right one depends on how much data you need and how much maintenance you can stomach.

MethodWhat it isSetupHandles blocksBest for
Python + hidden JSONParse __NEXT_DATA__ yourselfMedium (code)No, you add proxiesDevelopers, small pulls
No-code exporterBrowser extension to Excel/CSVLow (clicks)PartialOne-off, non-technical
Zillow scraper APISend a URL, get parsed JSONLow (one call)Yes, server-sideVolume, automation

The Python route is free and gives you full control, and it breaks every time Zillow changes its page structure. The no-code exporter is the fastest way to download Zillow data to a spreadsheet for a single search. A Zillow scraper API costs money and removes the proxy and parsing work, which is the trade most people make once they need more than a few hundred records. Pre-built actors on marketplaces like Apify wrap the same approaches behind a hosted runner if you would rather not host anything yourself. If you plan to get Zillow data for a commercial product, note that resale and redistribution raise their own licensing questions on top of the access question, which the legal section covers.

All three depend on one fact about how Zillow ships its data, so that is where to start.

Where does Zillow store its listing data?

Zillow stores its listing data in a hidden JSON object inside the page. The rendered HTML holds almost none of it. The price, beds, baths, sqft, address and zpid live in a <script id="__NEXT_DATA__"> tag, and the property fields sit one level deeper inside a string called gdpClientCache that is itself JSON encoded as text.

Zillow runs on Next.js, so every property page ships a __NEXT_DATA__ block that hydrates the React app in the browser. That is the cache you want. The structure I see in June 2026 is:

__NEXT_DATA__ (JSON)
└─ props.pageProps.componentProps.gdpClientCache   ← a JSON STRING (double-encoded)
   └─ <dynamic query key>
      └─ property
         ├─ price, bedrooms, bathrooms, livingArea
         ├─ streetAddress, city, state, zipcode, zpid
         └─ homeType, homeStatus, latitude, longitude, zestimate

Two details trip people up. First, gdpClientCache is a string, so you parse the page JSON, then parse that field again as JSON. Second, the key under gdpClientCache is a dynamic GraphQL query string that includes the zpid, so you cannot hardcode it. You read the first value that holds a property object. ScrapFly’s Zillow scraping teardown documents the same __NEXT_DATA__ and gdpClientCache path, and older listing layouts use a parallel hdpApolloPreloadedData script for the same job.

Once you know the data is sitting in that script tag, parsing a single property page is a short script.

How do you scrape a Zillow property page with Python?

You scrape a Zillow property page with Python by fetching the listing URL, pulling the __NEXT_DATA__ script with a regex or an HTML parser, and walking the JSON down to the property object. Here is the parser I ran against real Zillow page structure, with no scraping framework, so you can see every step.

import json
import re
import requests

def scrape_zillow_property(url: str) -> dict:
    headers = {
        "User-Agent": (
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36"
        ),
        "Accept-Language": "en-US,en;q=0.9",
    }
    html = requests.get(url, headers=headers, timeout=20).text

    # The listing fields live in the __NEXT_DATA__ script tag, away from the HTML body.
    match = re.search(
        r'<script id="__NEXT_DATA__"[^>]*>(.*?)</script>', html, re.S
    )
    data = json.loads(match.group(1).strip())

    # gdpClientCache is JSON encoded as a string, so parse it a second time.
    cache_str = data["props"]["pageProps"]["componentProps"]["gdpClientCache"]
    cache = json.loads(cache_str)

    # The cache key includes the zpid and is dynamic, so grab the first property.
    prop = next(v["property"] for v in cache.values() if "property" in v)

    return {
        "zpid": prop["zpid"],
        "address": f'{prop["streetAddress"]}, {prop["city"]}, '
                   f'{prop["state"]} {prop["zipcode"]}',
        "price": prop["price"],
        "beds": prop["bedrooms"],
        "baths": prop["bathrooms"],
        "sqft": prop["livingArea"],
        "home_type": prop["homeType"],
        "zestimate": prop.get("zestimate"),
    }

if __name__ == "__main__":
    url = "https://www.zillow.com/homedetails/2092-zpid/"
    print(scrape_zillow_property(url))

When I ran the parser logic against a Zillow-shaped page in June 2026, it returned the structured row cleanly:

{
  "zpid": 2092,
  "address": "123 Main St, Houston, TX 77002",
  "price": 415000,
  "beds": 3,
  "baths": 2,
  "sqft": 1850,
  "home_type": "SINGLE_FAMILY",
  "zestimate": 419300
}

Two cautions from running this. The .strip() on the captured script text matters, because a stray newline makes json.loads throw Invalid control character. And the requests.get itself is the fragile part: from a datacenter IP it returns a 403 page instead of the listing, so this exact code works from a residential IP or with the proxy setup covered below. The parsing is solved, but the fetching is the hard half, which I cover in depth in my Python guide for scraping Zillow; most jobs need more than one property, which means the search page.

How do you scrape Zillow search results?

You scrape Zillow search results by reading the same __NEXT_DATA__ cache on a search URL, where Zillow stores the list of results under a searchResults object in place of a single property. A Zillow search scraper for a query like Houston, TX returns a page of listings, each with the price, beds, baths and address you would parse per property, so you loop over the list and extract each row.

The search page exposes a listResults array (sometimes alongside mapResults) inside the cache. The shape per result looks like this in June 2026:

FieldJSON keyExample
Priceprice / unformattedPrice$415,000 / 415000
Bedsbeds3
Bathsbaths2
Areaarea1850
Addressaddress123 Main St, Houston, TX 77002
IDzpid2092
StatusstatusTypeFOR_SALE

A Houston TX search returns roughly 40 listings per page, and Zillow paginates the rest behind the same backend call. Behind the search box, Zillow drives results through a PUT request to https://www.zillow.com/async-create-search-page-state, which takes a JSON payload with the map bounds and filters and returns the result set, as documented in ScrapFly’s Zillow search teardown. Hitting that endpoint directly is faster than rendering each page, and it is also the request PerimeterX watches most closely, so search-page scraping hits blocks sooner than single-property scraping. Once you have the rows, you usually want them in a spreadsheet.

How do you export Zillow data to Excel or CSV?

You export Zillow data to Excel or CSV by writing your parsed rows with Python’s built-in csv module, or by using a no-code exporter that sends the visible search results straight to an .xlsx file. The Python route is repeatable and scriptable. The no-code route is faster for a single pull when you do not want to write code.

For the Python export, one real gotcha is that some listings have no price, and a row with a None price will pollute your dataset, so I drop those before writing. Here is the export I ran:

import csv

# rows = output from scrape_zillow_property(), one dict per listing
clean = [r for r in rows if r["price"] is not None]

fields = ["zpid", "address", "price", "beds", "baths", "sqft"]
with open("zillow_houston.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=fields)
    writer.writeheader()
    writer.writerows(clean)

When I ran this over a three-row test set where one listing had a null price, it wrote a clean two-row CSV and skipped the bad record, with the address quoted correctly because it contains commas:

zpid,address,price,beds,baths,sqft
2092,"123 Main St, Houston, TX 77002",415000,3,2,1850
3110,"55 Oak Ave, Houston, TX 77004",529000,4,3,2300

If you do not want to write code, a browser exporter does the same job from the search page. Tools like webscraper.io let you select the listing fields on screen and export to Excel or Google Sheets, and I built a one-click Zillow data exporter for exactly this: it reads the visible results and writes price, address, beds, baths and sqft to a spreadsheet with no code. The catch with every export method is the same one that blocks the Python fetch: getting Zillow to serve the page at all.

Why does Zillow block scrapers, and how do you get past it?

Zillow blocks scrapers with PerimeterX, now branded HUMAN Security, which decides whether to serve the page based on your IP reputation and browser fingerprint before any listing data loads. From a datacenter IP I get an HTTP 403 or the “Press & Hold to confirm you are human” challenge, regardless of the User-Agent I send.

PerimeterX scores TLS fingerprints, header order, request velocity and IP reputation in real time, which is why a clean User-Agent on a flagged IP changes nothing. ScrapFly’s PerimeterX teardown and ZenRows’ bypass writeup both describe the Press & Hold challenge as the visible symptom of a fingerprint or IP that already failed the silent checks. The levers that actually moved the result in my testing, in order of impact:

Doing all of this yourself means buying a residential proxy pool, rotating it, maintaining a stealth browser, and retrying failed challenges. That is a standing maintenance project once you pass a few thousand records, which is why most teams hand the blocking problem to an API. I go deeper on the proxy and CAPTCHA setup in my guide on scraping Zillow without getting blocked.

How do you scrape Zillow data without managing proxies?

You scrape Zillow data without managing proxies by sending the listing URL to a Zillow scraper API that runs the proxies, the stealth browser and the JSON parsing on its side and returns clean structured data. You make one request and get back the property fields, with no 403 to debug and no gdpClientCache to unwrap yourself.

This is the route I reach for at volume. With ChocoData I send the Zillow URL and my API key, and the parsed property comes back as JSON:

curl "https://chocodata.com/api/v1/zillow/property?url=https://www.zillow.com/homedetails/2092-zpid/&api_key=$CHOCO_API_KEY"

The same call in Python, looping a list of listing URLs into rows you can write straight to the CSV from the export section:

import os
import requests

API = "https://chocodata.com/api/v1/zillow/property"
KEY = os.environ["CHOCO_API_KEY"]

def fetch(url: str) -> dict:
    r = requests.get(API, params={"url": url, "api_key": KEY}, timeout=60)
    r.raise_for_status()
    return r.json()

urls = [
    "https://www.zillow.com/homedetails/2092-zpid/",
    "https://www.zillow.com/homedetails/3110-zpid/",
]
rows = [fetch(u) for u in urls]

This returns the same fields the hidden-JSON parser produces (price, beds, baths, sqft, address, zpid, zestimate), without registering for an MLS partner program, running a browser farm, or buying proxies. For a one-off pull of a few hundred records, the Python parser above is fine. For continuous collection across cities, or for pulling agent names and phone numbers and home price and sales history at scale, offloading the blocking and parsing is usually the cheaper path once you price your own time. You can get an API key and run the call above in a couple of minutes.

Scraping publicly visible Zillow data is generally treated as a contract question in the US. The main risk is Zillow’s Terms of Use, and federal hacking law under the CFAA rarely applies to public pages. Two recent rulings shape this, and Zillow’s own terms draw the contract line.

US courts have repeatedly held that scraping public, logged-out data does not violate the Computer Fraud and Abuse Act. The Ninth Circuit in hiQ Labs v. LinkedIn reaffirmed in 2022 that accessing public web data is unlikely to be “without authorization” under the CFAA, and the Supreme Court narrowed the CFAA the year before in Van Buren v. United States. In January 2024 a federal court granted summary judgment for the scraper in Meta Platforms v. Bright Data, finding that Meta’s terms did not bar logged-off scraping of public data.

Zillow’s Terms of Use prohibit automated collection directly. They forbid users to “conduct automated queries (including screen and database scraping, spiders, robots, crawlers, bypassing ‘captcha’ or similar precautions, or any other automated activity with the purpose of obtaining information from the Services).” Zillow’s robots.txt also disallows /search/ paths and applies a one-second crawl delay to listed bots. The practical line: scraping public listing data tends to be defensible against CFAA claims, breaching the Terms of Use is a separate contract exposure, and personal data such as agent contact details can pull in privacy rules like GDPR if you collect EU residents’ information. I work through the cases and the terms in detail in is scraping Zillow legal.

Zillow scraping methods compared

Here is the summary I wish I had before I started, with the numbers from my own runs and the public anti-bot ratings.

FactorPython + hidden JSONNo-code exporterZillow scraper API
CostFree + proxy spendFree to lowPer-request or plan
Coding requiredYesNoMinimal
Handles PerimeterXYou build itPartialYes, server-side
OutputJSON you shapeExcel / CSVParsed JSON
Breaks on page changeYes, you fix itSometimesMaintained for you
Phone / agent dataManual parseLimitedAdd-on endpoint
Best volumeHundredsSingle pullThousands+

The hidden-JSON parser is the right starting point if you code and your volume is small. The exporter wins for a single spreadsheet. The API earns its cost when you need Houston, then Austin, then Dallas, every week, without babysitting proxies.

FAQ

Can you legally scrape Zillow data?

Scraping publicly visible Zillow pages sits in a gray area. US courts in hiQ v. LinkedIn and Meta v. Bright Data found that scraping public, logged-out data generally does not break the CFAA. Zillow's Terms of Use still prohibit automated access, which makes this a contract question for most scrapers. I cover the detail in my Zillow legality guide.

How do I scrape Zillow data with phone numbers?

Agent and broker phone numbers appear on the listing's contact module and in the same hidden JSON as the listing fields, under the listing agent object. Pulling names and phone numbers at volume is its own job, so I built a separate Zillow agent scraper for names, phone numbers and leads.

How do I download Zillow data to Excel?

For a one-off pull, a no-code browser exporter writes the visible search results straight to an .xlsx or CSV file. For repeatable exports, parse the listing JSON in Python and use the csv module, or call a scraper API and load the JSON into a spreadsheet. I walk through both in the export section above.

Does Zillow have an official API for listing data?

Zillow retired its public Zestimate and property-detail APIs for general developers. The Bridge Interactive and ShowingTime+ APIs that remain are gated to MLS members and licensed partners, so most people scrape the public pages or use a third-party Zillow data API instead.

What is Zillow data and what fields can you extract?

Zillow data is the set of fields Zillow publishes per property: price, beds, baths, living area in sqft, street address, city, state, zip, home type, listing status, latitude and longitude, the zpid identifier, and the Zestimate. All of these are present in the page's hidden JSON, which is what makes structured extraction possible.

DC
Dana Cole
I've built Zillow data pipelines for years. On zillowscraperapi.com I run Zillow scraping methods against live pages and publish what actually holds up.