Start free

How to scrape Zillow

Extract Zillow listing data (price, beds, baths, sqft, address, home type, Zestimate) as typed JSON from a single property URL, no CSS selectors.

Site:
zillow.com
Difficulty:
Hard
Rendering:
JavaScript-rendered
Anti-bot:
PerimeterX (HUMAN Security), HUMAN Challenge CAPTCHA, Behavioral scoring (mouse, timing, navigation), Access Denied / press-and-hold interstitial

What you can extract from Zillow

These are the fields teams most often pull from zillow.com, and why a hand-written selector for each one tends to break over time.

Field Type Why selectors break
price number The list price sits inside a hashed, build-generated class like span[data-testid='price'] that React rewrites on every deploy, and Zillow shows different price nodes for active, pending, sold and pre-foreclosure states, so one selector silently grabs the wrong number or null.
beds number Bed counts live in an unlabeled flex row of bed/bath/sqft facts where order and DOM position shift between condos, lots and multi-family units, so an :nth-child selector that works on a house pulls the bath value on a duplex.
baths number Baths are rendered as a bare number next to a sibling icon with no stable text label, and half-baths are sometimes folded into a combined 'ba' string, so a selector targeting the visible node returns '2' when the real value is 2.5.
sqft number Square footage appears with a comma-formatted string and a trailing 'sqft' unit inside the same hashed fact row as beds and baths, and is absent entirely on land listings, so a positional CSS selector breaks the moment the listing has no living area.
address string The address is split across an h1 plus separate city/state/zip spans, and Zillow A/B tests whether the full street line renders client-side after hydration, so a selector reading only the h1 captures a partial address or empty text on the first paint.
homeType string Home type (Single Family, Condo, Townhouse, Lot) is buried in a 'Facts and features' accordion that mounts lazily on scroll, so a CSS selector run before the section hydrates finds nothing, and the label text varies by property category.
zestimate number The Zestimate loads from a separate client-side request after initial render and is hidden behind a tooltip widget with rotating data-testid attributes, so a selector either fires before the value arrives or matches the Rent Zestimate instead of the value Zestimate.

The schema-first approach

Instead of maintaining selectors, you hand Extracto a JSON schema describing the shape you want. Here is a schema that works for Zillow:

{
  "price": "number",
  "beds": "number",
  "baths": "number",
  "sqft": "number",
  "address": "string",
  "homeType": "string",
  "zestimate": "number"
}
  1. 1

    Describe the Zillow fields you want

    Write a small JSON schema listing the fields to pull from zillow.com (for example price, beds, baths). No CSS selectors or XPath needed.

  2. 2

    Send the URL and schema to Extracto

    POST the Zillow page URL and your schema to the Extracto API. Extracto renders the page in a real browser, handling JavaScript and dynamic content automatically.

  3. 3

    Get schema-validated JSON back

    Extracto returns JSON that matches your schema exactly, validated before it leaves the API, so your pipeline never receives broken or partial data.

Common pitfalls when scraping Zillow

Is it legal to scrape Zillow?

Zillow's Terms of Use explicitly prohibit using "any robot, spider, scraper or other automated means" to access the Services without Zillow's express written permission, and its robots.txt disallows the listing paths that matter most, including /homedetails/ and /homes/. Zillow also enforces a press-and-hold HUMAN Challenge and reserves the right to apply technical blocks, account suspension and legal remedies. Treat Zillow data as off-limits for bulk automated collection unless you have a written agreement or use an official channel. Zillow does run a partner and API program (Bridge Interactive / ZGMI) and publishes free aggregate housing datasets through Zillow Research, which are the compliant routes for most analytics use cases. If you extract individual public listing pages at all, keep volume minimal, respect robots.txt and the ToS, never access login-gated or saved-search data, and consult counsel before any commercial use. This guide is educational and does not constitute legal advice; the lawful path for production real-estate data is Zillow's own data programs, not scraping.

Extracto follows robots.txt by default and rate-limits politely. You are responsible for compliance with Zillow's terms and applicable law (GDPR, CCPA) in your use case.

Scraping Zillow: FAQ

Can I legally scrape Zillow listings?
Zillow's Terms of Use explicitly forbid automated access without written permission, and its robots.txt disallows /homedetails/ and /homes/. For production or commercial real-estate data the compliant routes are Zillow's partner and API programs (Bridge Interactive) and the free aggregate datasets from Zillow Research. Any extraction of individual public pages should be minimal, respect robots.txt, and be reviewed with legal counsel first.
Why does Zillow show a CAPTCHA or Access Denied when I request a page?
Zillow runs PerimeterX (now HUMAN Security), which scores each session on headers, cookies, request timing, navigation patterns and JavaScript execution. When the score crosses a threshold it serves a press-and-hold HUMAN Challenge or an Access Denied interstitial instead of the listing. Extracto's managed rendering layer handles this protected-site infrastructure for you, with proxies managed and no setup, so a request returns the rendered page rather than a challenge.
Do I need CSS selectors to extract Zillow fields?
No. Zillow's class names are hashed and regenerated on every front-end deploy, and the bed/bath/sqft facts live in an unlabeled row whose order shifts by property type, so selector-based scrapers break constantly. With Extracto you send the URL plus a JSON schema naming price, beds, baths, sqft, address, homeType and zestimate, and you get typed JSON back without writing or maintaining a single selector.
How does Extracto handle the Zestimate versus list price?
The Zestimate is a separate, asynchronously loaded estimate that renders in a widget nearly identical to the Rent Zestimate, and on for-sale homes it may be suppressed behind the list price. Because you declare zestimate and price as distinct fields in your schema, Extracto maps each to the right value and returns null for a Zestimate that genuinely is not present, instead of guessing or copying the rent figure into it.
What happens to fields that do not exist on a listing, like sqft on a vacant lot?
Land and lot listings often have no beds, baths or square footage, and some homes omit a Zestimate. Extracto returns those fields as null rather than inventing a value or borrowing from a neighboring field. That makes mixed result sets (houses, condos, lots, sold homes) safe to load straight into a database without post-processing to detect fabricated numbers.