---
title: "Zillow scraper"
description: "Scrape Zillow search results and property pages: address, price, beds, baths, square feet, brokerage and ZPID as JSON. One API request from Python, Node.js or MCP."
canonical: https://www.zenrows.com/scrape/zillow
updated: 2026-10-20
---

# Zillow scraper

The Zenrows Zillow scraper turns a Zillow search or property URL into structured JSON: ZPID, address, asking price, bedrooms, bathrooms, square feet, listing brokerage, listing date, year built, lot size and coordinates. Fetch in Adaptive Stealth Mode opens the page and Extract returns the fields. Run it from an API call, the CLI or an AI agent.

- Site: zillow.com
- Returns: Listings, prices, home facts, brokerages and ZPIDs

## Run it: one Zillow search in, JSON out

```json Input
{
  "url": "https://www.zillow.com/austin-tx/",
  "mode": "auto",
  "extract": "auto",
  "proxy_country": "us"
}
```

```json Output
{
  "search_context": {
    "canonical_url": "https://www.zillow.com/austin-tx/",
    "location_label": "Austin TX",
    "total_result_count": 5714
  },
  "listings": [
    {
      "zpid": "305433145",
      "address": "11900 Cimaizon Dr, Austin, TX 78725",
      "postal_code": "78725",
      "asking_price": 400000,
      "bedrooms": 4,
      "bathrooms": 3,
      "area_sqft": 2174,
      "listing_agent_or_brokerage": "KELLER WILLIAMS REALTY",
      "listing_badge": "Showcase",
      "listing_date": "2026-10-06T15:30:46.495Z",
      "detail_url": "https://www.zillow.com/homedetails/11900-Cimaizon-Dr-Austin-TX-78725/305433145_zpid/",
      "primary_photo_url": "https://photos.zillowstatic.com/fp/1eef4560d63824c1cf7a1355370d3e28-p_e.webp"
    },
    {
      "zpid": "58313397",
      "address": "2307 Cheyenne Cir, Austin, TX 78745",
      "postal_code": "78745",
      "asking_price": 995000,
      "bedrooms": 4,
      "bathrooms": 2,
      "area_sqft": 2049,
      "listing_agent_or_brokerage": "CHRISTIES INTL REAL ESTATE LONE STAR",
      "listing_badge": "Showcase",
      "listing_date": "2026-10-16T15:28:30.495Z",
      "detail_url": "https://www.zillow.com/homedetails/2307-Cheyenne-Cir-Austin-TX-78745/58313397_zpid/",
      "primary_photo_url": "https://photos.zillowstatic.com/fp/3dafce257cca8f790e05deeab8c0f22a-p_e.webp"
    }
  ]
}
```

Request and output of a live call to a Zillow search page on Oct 20, 2026, trimmed for length. A field is returned when the page shows it.

## What data can you scrape from Zillow?

Search pages return a list of homes with the facts on each card. Property pages (`/homedetails/`) return one home with its pricing, location and building facts. These are the fields teams use most, with the path where each lives in the JSON.

| Field | What it holds | Example |
|---|---|---|
| `listings[].zpid` | Zillow's property ID, stable across listings and price changes | `305433145` |
| `listings[].address` | Full street address with city, state and ZIP | `11900 Cimaizon Dr, Austin, TX 78725` |
| `listings[].asking_price` | The price on the card, in US dollars | `400000` |
| `listings[].bedrooms` | Bedroom count | `4` |
| `listings[].bathrooms` | Bathroom count | `3` |
| `listings[].area_sqft` | Living area in square feet | `2174` |
| `listings[].listing_agent_or_brokerage` | The brokerage that holds the listing | `KELLER WILLIAMS REALTY` |
| `listings[].listing_date` | When the listing went live, as an ISO timestamp | `2026-10-06T15:30:46.495Z` |
| `listings[].listing_badge` | The label on the card | `Showcase` |
| `listings[].detail_url` | The property page, ready to request next | `https://www.zillow.com/homedetails/…_zpid/` |
| `search_context.total_result_count` | How many homes match the search | `5714` |
| `pricing.list_price` (property) | The price shown on the property page | `438400` |
| `pricing.zestimate` (property) | Zillow's estimated value, when the page shows one | `null` on this off-market home |
| `listing.listing_status` (property) | For sale, pending, sold or off market | `off_market` |
| `property.year_built` (property) | Year of construction | `1935` |
| `property.lot_size_sqft` (property) | Lot size in square feet | `5375` |
| `property.price_per_sqft` (property) | Price divided by living area | `399` |
| `property.latitude`, `property.longitude` (property) | Coordinates of the home | `30.266376`, `-97.71955` |

## How to scrape Zillow with Zenrows

1. **Create a free Zenrows account** and copy your API key from the dashboard.
2. **Pick the Zillow URL.** A city or ZIP search (`/austin-tx/`, `/austin-tx-78704/`), a rentals or sold search (`/austin-tx/rentals/`, `/austin-tx/sold/`) or a property page (`/homedetails/…/ZPID_zpid/`) all work the same way.
3. **Send it with the default configuration**: Adaptive Stealth Mode to open the page, Extract to return the fields, and the US as the country, since Zillow serves US visitors.
4. **Read `parsed`** from the response. For a search, loop over `listings` and keep each `detail_url` if you want the full property record.
5. **Send the property URLs to Batch** to collect year built, lot size, coordinates and pricing for every home in the search.

```python Python
# pip install requests
import json
import requests

params = {
    "apikey": "YOUR_ZENROWS_API_KEY",
    "url": "https://www.zillow.com/austin-tx/",
    "mode": "auto",          # Adaptive Stealth Mode
    "extract": "auto",       # structured JSON
    "proxy_country": "us",   # Zillow serves US visitors
}
response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
response.raise_for_status()

data = response.json()["parsed"]
print(data["search_context"]["total_result_count"], "homes")
for home in data["listings"]:
    print(home["zpid"], home["address"], home["asking_price"], home["bedrooms"], home["area_sqft"])
```

```javascript Node.js
// Node 18+ (built-in fetch)
const params = new URLSearchParams({
  apikey: "YOUR_ZENROWS_API_KEY",
  url: "https://www.zillow.com/austin-tx/",
  mode: "auto",
  extract: "auto",
  proxy_country: "us",
});

const response = await fetch(`https://api.zenrows.com/v1/?${params}`, {
  signal: AbortSignal.timeout(180_000),
});
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);

const { parsed } = await response.json();
for (const home of parsed.listings) {
  console.log(home.zpid, home.address, home.asking_price);
}
```

```bash cURL
curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.zillow.com%2Faustin-tx%2F&mode=auto&extract=auto&proxy_country=us"

# Only the fields you need
curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.zillow.com%2Faustin-tx%2F&mode=auto&extract=auto&proxy_country=us&extract_fields=listings.zpid%2Clistings.address%2Clistings.asking_price%2Clistings.area_sqft"
```

```python Python SDK
# pip install zenrows
from zenrows import ZenRowsClient

client = ZenRowsClient("YOUR_ZENROWS_API_KEY")
response = client.get(
    "https://www.zillow.com/homedetails/1909-Tillotson-Ave-Austin-TX-78702/29386155_zpid/",
    params={"mode": "auto", "extract": "auto", "proxy_country": "us"},
)
home = response.json()["parsed"]
print(home["property"]["street_address"], home["property"]["year_built"], home["pricing"]["list_price"])
```

## Input

The scraper takes one Zillow URL per request, plus the options below. The same parameters go into every task of a Batch job, so the input for a whole metro is a list of search or property URLs with these settings.

| Parameter | Type | Default | Description |
|---|---|---|---|
| `url` | string | required | A Zillow search (city, ZIP, neighborhood, rentals, sold) or a property page (`/homedetails/…_zpid/`) |
| `mode` | string | `auto` | Adaptive Stealth Mode: starts with the lightest setup and adds rendering and protected-site handling only when the page needs them |
| `extract` | string | `auto` | Returns the page's fields as JSON in `parsed`, next to the raw `html` |
| `extract_fields` | string | all fields | Comma-separated paths to keep, such as `listings.zpid,listings.asking_price` |
| `proxy_country` | string | any | Two-letter country code. Use `us`: Zillow is a US site and serves US visitors |
| `wait_for` | string | none | A CSS selector to wait for before the page is read, for example the results list on a search |
| `session_id` | integer | none | Reuse the same session across several requests, up to 10 minutes, such as the pages of one search |

```json Batch input
{
  "type": "regular",
  "status": "closed",
  "zenrows_params": {
    "mode": "auto",
    "extract": "auto",
    "proxy_country": "us"
  },
  "tasks": [
    { "url": "https://www.zillow.com/austin-tx/", "external_id": "search-austin" },
    { "url": "https://www.zillow.com/homedetails/1909-Tillotson-Ave-Austin-TX-78702/29386155_zpid/", "external_id": "29386155" },
    { "url": "https://www.zillow.com/homedetails/11900-Cimaizon-Dr-Austin-TX-78725/305433145_zpid/", "external_id": "305433145" }
  ]
}
```

## Output

Every response is JSON with two keys: `html`, the page as Zillow served it, and `parsed`, the structured record. A search page returns `search_context` and `listings`; a property page returns `listing`, `pricing`, `property` and `monthly_payment_estimate`. Both samples below are trimmed outputs of live requests.

```json Search results
{
  "search_context": {
    "canonical_url": "https://www.zillow.com/austin-tx/",
    "location_label": "Austin TX",
    "total_result_count": 5714
  },
  "listings": [
    {
      "zpid": "29362448",
      "address": "8500 Adirondack Cv, Austin, TX 78759",
      "postal_code": "78759",
      "asking_price": 1300000,
      "bedrooms": 5,
      "bathrooms": 3,
      "area_sqft": 2676,
      "listing_agent_or_brokerage": "LPT REALTY LLC - ASCEND GROUP",
      "listing_badge": "Showcase",
      "listing_date": "2026-10-16T21:46:42.495Z",
      "listing_status": null,
      "property_type": null,
      "detail_url": "https://www.zillow.com/homedetails/8500-Adirondack-Cv-Austin-TX-78759/29362448_zpid/",
      "primary_photo_url": "https://photos.zillowstatic.com/fp/0027d3d5ac3b773da7bf1e86891d2fd8-p_e.webp"
    },
    {
      "zpid": "29483708",
      "address": "5626 Wagon Train Rd, Austin, TX 78749",
      "postal_code": "78749",
      "asking_price": 375000,
      "bedrooms": 3,
      "bathrooms": 2,
      "area_sqft": 1270,
      "listing_agent_or_brokerage": "SPYGLASS REALTY",
      "listing_badge": "Showcase",
      "listing_date": "2026-10-14T19:14:30.495Z",
      "listing_status": null,
      "property_type": null,
      "detail_url": "https://www.zillow.com/homedetails/5626-Wagon-Train-Rd-Austin-TX-78749/29483708_zpid/",
      "primary_photo_url": "https://photos.zillowstatic.com/fp/486e679633a7b3e87cbd7be36d28c631-p_e.webp"
    }
  ]
}
```

```json Property page
{
  "listing": {
    "zpid": "29386155",
    "canonical_url": "https://www.zillow.com/homedetails/1909-Tillotson-Ave-Austin-TX-78702/29386155_zpid/",
    "listing_status": "off_market",
    "days_on_zillow": null,
    "mls_id": null
  },
  "pricing": {
    "currency": "$",
    "list_price": 438400,
    "zestimate": null,
    "hoa_fee_monthly": null
  },
  "property": {
    "street_address": "1909 Tillotson Ave",
    "city": "Austin",
    "state": "TX",
    "postal_code": "78702",
    "latitude": 30.266376,
    "longitude": -97.71955,
    "lot_size_sqft": 5375,
    "price_per_sqft": 399,
    "year_built": 1935,
    "bedrooms": null,
    "bathrooms": null,
    "living_area_sqft": null
  },
  "monthly_payment_estimate": null
}
```

The property sample is an off-market home, so Zillow shows no Zestimate, bed count or days on market and those fields come back `null`. On an active listing the same keys carry values. Load `parsed` into pandas with `pd.json_normalize(data["listings"])` to get one row per home, or ask for Markdown when the page goes to an LLM.

## Scrape Zillow from your coding agent

Claude Code, Cursor, Codex and any MCP client can scrape Zillow from a prompt.

The agent sends the same Fetch and Extract request shown above and reads the same JSON, so a question like "what sold under $500k in 78704" turns into one search request and a filter. Connect the Zenrows MCP server once, or install the CLI and let the agent run it in the terminal.

```bash Connect the MCP server (Claude Code)
claude mcp add zenrows -e ZENROWS_API_KEY=YOUR_ZENROWS_API_KEY -- npx -y @zenrows/mcp
```

```json MCP config (Cursor, Claude Desktop, Windsurf)
{
  "mcpServers": {
    "zenrows": {
      "command": "npx",
      "args": ["-y", "@zenrows/mcp"],
      "env": { "ZENROWS_API_KEY": "YOUR_ZENROWS_API_KEY" }
    }
  }
}
```

```bash Or run the CLI
npx -y @zenrows/cli init
zenrows extract "https://www.zillow.com/austin-tx/" --proxy-country us
```

```text Then ask your agent
Use Zenrows to read https://www.zillow.com/austin-tx/ and list every
home with 4 or more bedrooms under $700,000. Show address, price,
square feet, price per square foot and the brokerage, sorted by
price per square foot.
```

## Scrape Zillow at scale with Batch and Crawl

**Batch** runs a list of Zillow URLs as one job, in parallel, with your own ID on each result. Put the ZPID in `external_id` and every property record joins back to your own table. Teams use it for a nightly pass over the ZIP codes they cover, then a second job over the `detail_url` of each new listing.

**Crawl** collects the property pages for you. Start it on a city or ZIP search, keep only links that contain `/homedetails/`, and it returns every property page it reaches. Send that list to Batch with Extract to get the structured records.

```bash Batch (cURL)
curl -X POST "https://async.api.zenrows.com/v1/jobs" \
  -H "X-API-Key: YOUR_ZENROWS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "regular",
    "status": "closed",
    "zenrows_params": { "mode": "auto", "extract": "auto", "proxy_country": "us" },
    "tasks": [
      { "url": "https://www.zillow.com/homedetails/1909-Tillotson-Ave-Austin-TX-78702/29386155_zpid/", "external_id": "29386155" },
      { "url": "https://www.zillow.com/homedetails/11900-Cimaizon-Dr-Austin-TX-78725/305433145_zpid/", "external_id": "305433145" }
    ]
  }'

# Read the results when the job finishes
curl "https://async.api.zenrows.com/v1/jobs/JOB_ID/results?status=successful" \
  -H "X-API-Key: YOUR_ZENROWS_API_KEY"
```

```bash Batch (CLI)
# homes.jsonl: one {"url": "...", "external_id": "...", "extract": "auto"} per line
zenrows batch create homes.jsonl --proxy-country us --wait
zenrows batch results <job-id> --out zillow-homes.jsonl
```

```bash Crawl (cURL)
curl -X POST "https://api.zenrows.com/v1/crawls" \
  -H "X-API-Key: YOUR_ZENROWS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://www.zillow.com/austin-tx-78704/",
    "depth": 2,
    "max_pages": 300,
    "include_patterns": ["/homedetails/"]
  }'
```

**Scheduled jobs** run the same Batch job on a timetable, on Zenrows' side: every few hours, once a day, at set times on chosen days, or once at a future date. Each run keeps its own results, a webhook can tell your app when a run finishes, and you can pause or resume the schedule at any time, with no cron server to keep alive.

```bash Scheduled job (cURL)
# Runs every day on Zenrows' side; each run keeps its own results
curl -X POST "https://async.api.zenrows.com/v1/jobs" \
  -H "X-API-Key: YOUR_ZENROWS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
      "type": "scheduled",
      "status": "closed",
      "schedule": {
        "rate": {
          "every": 1,
          "unit": "day"
        }
      },
      "zenrows_params": {
        "mode": "auto",
        "extract": "auto",
        "proxy_country": "us"
      },
      "tasks": [
        {
          "url": "https://www.zillow.com/austin-tx/",
          "external_id": "zillow"
        }
      ]
    }'

# Or at set times: "schedule": { "calendar": { "times_of_day": ["09:00"],
#   "cadence": { "weekly": { "days": ["mon", "thu"] } } }, "timezone": "Europe/Madrid" }
```

## What teams do with Zillow data

| Use case | Data they pull | Who runs it |
|---|---|---|
| Investment deal sourcing | New listings by ZIP with price, beds, square feet and price per square foot, checked daily | Real estate investors and iBuyers |
| Comparable sales and valuation | Sold searches with price, size, year built and coordinates around a subject property | Appraisers, lenders and valuation models |
| Rental market analysis | Rentals searches by city or ZIP with rent, beds and listing date | Property managers and build-to-rent teams |
| Brokerage market share | Listing brokerage per listing, counted by ZIP and month | Brokerages and real estate marketing teams |
| Housing research and dashboards | Inventory counts and asking prices over time | Analysts, economists and journalists |
| AI home search assistants | The full property record, or the page as Markdown, for an agent to read | AI and proptech product teams |

## Tips for scraping Zillow

- **Use the ZPID as your key.** Every property URL ends in `NUMBER_zpid/`, and the same number comes back in `zpid`. Store it, dedupe on it, and you can track one home through price cuts, pending and sold.
- **Request from the US.** Zillow is a US site. Set the country to `us` on every request, including Batch jobs.
- **Narrow big searches by ZIP code.** A metro like Austin matches thousands of homes. A search per ZIP (`/austin-tx-78704/`) keeps each result set small enough to page through, and `total_result_count` tells you how many to expect.
- **Page through search results with the URL.** Page two of a search is `/austin-tx/2_p/`, page three `/austin-tx/3_p/`, and so on. Keep the same session for the pages of one search.
- **Rentals and sold homes have their own paths.** Add `/rentals/` or `/sold/` to a location path to switch the list. Sold searches are where comparable sales come from.
- **The address in the URL can change; the ZPID does not.** Zillow redirects an old slug to the current one, so read `listing.canonical_url` from the response instead of trusting the URL you sent.
- **Expect `null` on off-market homes.** A home that is not listed shows fewer facts. Treat `null` as "not shown on the page" rather than zero.

## Frequently asked questions

### Is it legal to scrape Zillow?

Collecting publicly available listing data from Zillow, such as prices, addresses and home facts shown to every visitor, is common practice for market research and investment analysis. Listings can name agents and brokerages, and personal data is protected by laws such as the GDPR and the CCPA, so collect only what you have a legitimate reason to use. Zillow's terms of use restrict automated access, so read them for your use case and ask your lawyer if you are unsure.

### Is there an official Zillow API?

Zillow retired its old public API, the one that took a ZWSID key and returned Zestimates by address. Zillow Group's data APIs now run through Bridge Interactive: you apply for each dataset, approval is case by case, listing data needs approval from the MLS that owns it, and the terms limit how you store and display what you get. None of them returns any Zillow search or property page on demand, which is why analysts and investors use a Zillow scraper API for market-wide data.

### What Zillow pages can I scrape?

City, ZIP code and neighborhood searches, rentals and sold searches, and property pages ( /homedetails/…_zpid/ ). Send the URL as it appears in your browser, with the filters already applied.

### Can I scrape Zestimates and price history?

The property record has a pricing.zestimate field that is filled when Zillow publishes a Zestimate for the home, next to the list price, price per square foot and HOA fee. Off-market homes often show none. For a price series over time, request the same ZPIDs on a schedule and store each result with its date.

### How do I scrape Zillow with Python?

Send a GET request to https://api.zenrows.com/v1/ with your API key, the Zillow URL, Adaptive Stealth Mode and Extract turned on and the US as the country, as in the Python example above. response.json()["parsed"]["listings"] is a list of dictionaries, one per home. The zenrows package on PyPI wraps the same request.

### How many Zillow listings can I scrape?

Send one URL per request, or a whole list as a Batch job that runs the URLs in parallel and keeps your ZPID on each result. One Batch job accepts more than 10,000 URLs, sent inline or as a CSV file. To cover a metro, split it into ZIP code searches and page through each one.

### Can I get the same fields from Zillow and other real estate sites?

Yes. Extract has a canonical real estate schema: send extract=standard instead of extract=auto and Zillow, Redfin and Idealista pages come back with the same field names, ready for one table. The default stays auto , which keeps each site's own names.

### Can AI agents scrape Zillow with Zenrows?

Yes. Connect the Zenrows MCP server to Claude, Cursor, Codex or any MCP client, or give your agent the Zenrows CLI. Ask for homes in plain language, such as "3-bed homes under $600k in 78745", and the agent calls Fetch and Extract for you.

### Can I scrape Zillow on a schedule?

Yes. Create a scheduled Batch job with your Zillow URLs and the same settings as a single request, and Zenrows runs it on its side: every few hours, once a day, at set times on chosen days, or once at a future date. Each run keeps its own results, a webhook can notify your app when a run finishes, and the schedule can be paused or resumed at any time.

### Which integrations work with the Zillow scraper?

Zenrows works with n8n, Make, Zapier, Clay, Dify, LangChain, LlamaIndex, CrewAI and Mastra, plus the Python, Node.js and Go SDKs and any HTTP client. A common setup is a daily n8n or Make workflow that sends new listings to Google Sheets or a CRM. See the integrations page for the full list.

### What format does the data come in?

JSON by default, with the structured record in parsed and the original page in html . You can also ask for the page as Markdown or plain text, which is what most LLM and RAG pipelines want. For a spreadsheet, flatten listings with pandas and write CSV or Excel.

## More Zillow scrapers

- [Zillow agents scraper](https://www.zenrows.com/scrape/zillow-agents.md)

All scrapers: https://www.zenrows.com/scrape. Start free: https://app.zenrows.com/register. Docs: https://docs.zenrows.com
