# Zillow Search Scraper - Listings by City or ZIP (`studioxl/zillow-search-scraper`) Actor

Scrape Zillow listings by city, ZIP code, county, or neighborhood, or paste a Zillow search URL. Get price, Zestimate, rent estimate, beds, baths, size, lot, address, coordinates, and broker across for-sale, for-rent, and sold homes. Breaks Zillow's 820-result cap. Export to JSON, CSV, or Excel.

- **URL**: https://apify.com/studioxl/zillow-search-scraper.md
- **Developed by:** [Kamil Ertekin](https://apify.com/studioxl) (community)
- **Categories:** Lead generation, Automation, Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 1,000 search results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

Type a place, or paste a Zillow search URL, and pull every matching listing back as **structured data**. Zillow Search Scraper resolves **plain-English locations** such as "Austin, TX" or "78731" through Zillow's own autocomplete, then reads the search results page directly, with no browser involved. Each row carries **41 fields**, and this page publishes the measured fill rate for every one of them, so you know what you are buying before you spend anything.

### What can Zillow Search Scraper do?

📍 Search by **city, ZIP code, county, or neighborhood**, typed in plain English. Or paste a Zillow search URL and it runs exactly as given.

🔎 Filter on price, beds, baths, square footage, lot size, year built, pool, waterfront, single-story, garage, air conditioning, basement, open house, 3D tour, and days listed, then sort by newest, price, size, or lot. Every one of these keys was checked live against Zillow's own result count before it shipped.

🏠 **For sale, for rent, and recently sold**, all from one Actor.

🧩 Get past Zillow's 820-result cap. Large areas split into smaller map sections and search each one separately, automatically.

💰 The rental price trap, handled. Zillow accepts a price filter on rental searches and quietly ignores it. This Actor switches to the key that moves the result count.

🌐 Residential US proxies by default. In a same-second A/B, residential returned 60 of 60 properties in 101 seconds; datacenter returned nothing in 408.

💵 Only rows that reach your dataset are billed. Retries, blocks, duplicates, and listings that miss your filters cost nothing.

🤖 Call it from the Apify API or an MCP server to drive a Zillow search from an agent.

### What Zillow data can you extract?

Every listing returns 41 fields:

| Field | What it is |
|---|---|
| `zpid` · `url` | Zillow's internal ID and the listing page |
| `price` · `priceLabel` · `currency` | Price as a number, Zillow's formatted string, and the currency |
| `zestimate` · `rentZestimate` | Zillow's own value and rent estimates |
| `taxAssessedValue` | Assessed value, where published |
| `bedrooms` · `bathrooms` · `livingArea` | Size |
| `lotAreaValue` · `lotAreaUnits` | Lot size, with its unit |
| `streetAddress` · `city` · `state` · `zipcode` · `address` | Location |
| `latitude` · `longitude` | Coordinates |
| `homeStatus` · `statusText` · `daysOnZillow` | Listing state |
| `brokerName` | Listing brokerage |
| `homeType` · `listingType` | Property type and search type |
| `mainImage` · `hasVideo` · `has3DModel` | Media |
| `isZillowOwned` · `isFeaturedListing` · `isUndisclosedAddress` | Flags |
| `isBuilding` · `buildingName` | Whether the row is a whole apartment building, and its name |
| `priceMin` · `priceMax` · `bedroomsMin` · `bedroomsMax` | Rent and bedroom span across a building's units |
| `availableUnitCount` · `unitCount` | How many units the building has available |
| `searchLocation` | Which input location produced this row |

#### How often is each field actually filled?

Measured on 2026-08-24 across **5,000 unfiltered Austin, TX for-sale results**:

| Fill rate | Fields |
|---:|---|
| **100%** | `zpid`, `url`, `price`, `priceLabel`, `currency`, `streetAddress`, `city`, `state`, `zipcode`, `address`, `homeType`, `homeStatus`, `statusText`, `listingType`, `daysOnZillow`, `mainImage`, and the flag fields |
| 99.7% | `latitude`, `longitude` |
| 99% | `bedrooms`, `bathrooms` |
| 95% | `brokerName` |
| 83% | `livingArea`, `taxAssessedValue` |
| **27%** | `rentZestimate` |
| **19%** | `zestimate` |

Zillow itself publishes a Zestimate on roughly one active listing in five, and a rent Zestimate
on roughly one in four. That holds on the listing page as much as in search, so no Zillow
scraper can return one where Zillow has not published one.

Everything you would use to filter, join, map, or price a dataset (`zpid`, `price`,
address, coordinates, beds, baths, area, days on market) comes back on every row.

#### Rentals behave differently, and this Actor handles it

Most rental search results are not single homes. They are whole apartment buildings, and
Zillow returns those in a different shape with no single price and no single bedroom count.
On page one of a Denver rental search, 38 of 41 results were buildings.

A building row gets `isBuilding: true`, its `buildingName`, the rent span in `priceMin` and
`priceMax`, the bedroom span in `bedroomsMin` and `bedroomsMax`, and `availableUnitCount`.
`price` is filled with the lowest available rent and `priceLabel` reads `$1,720 to $2,400/mo`,
so sorting and filtering keep working across a mixed result set. `bathrooms` and `livingArea`
stay empty on those rows, because a building does not have one of either.

### How to scrape Zillow search results

1. **Enter your locations.** Type `Austin, TX`, `78731`, or `Brooklyn, NY`, or paste a Zillow search URL.
2. **Set your filters**, or leave them empty to pull everything in the area.
3. **Set Maximum results.** This is your spend cap, and it defaults to 100. Searching several places at once? **Maximum per location** stops one large city from eating the whole run.
4. **Click Start**, then download from the Storage tab, or pull results through the API.

### Getting past Zillow's 820-result limit

A Zillow search reports its true total, then caps out at 20 pages of 41 results, 820, no matter what that total says. Austin, TX reported 5,787 for-sale listings on 2026-08-24 and handed back exactly 820.

This Actor works around the cap. When an area holds more listings than one search can return, it splits the map bounding box into quarters and searches each one separately, recursing up to 4 levels deep and removing duplicates by `zpid`. A real run on Austin, TX:

```
Austin, TX: 5,787 listings match.
Austin, TX holds 5,787 listings, more than one search returns (820). Splitting into 4 smaller areas.
Austin, TX holds 3,077 listings, more than one search returns (820). Splitting into 4 smaller areas.
Austin, TX holds 2,523 listings, more than one search returns (820). Splitting into 4 smaller areas.
...
Done. 1500 records pushed from 43 pages (209 duplicates dropped, 0 unchanged, 0 blocked)
```

That run took **52 seconds** on residential proxies. The duplicates are listings that appeared
in more than one map section after the split, and you are not charged for them.

Pushed harder, the same search returned **5,000 listings in 3 minutes 49 seconds** across 186
pages, with 2,456 duplicates filtered out and zero blocked requests. Every one of the 5,000
rows was a distinct property, spanning 63 ZIP codes, 864 brokerages, and prices from $2,995 to
$63,000,000. Those 2,456 duplicate fetches are absorbed on our side, not billed to you.

Turn off **Cover areas larger than one search can return** to stop at 820 per location and spend less.

### How much does it cost to scrape Zillow?

You pay per result delivered to your dataset.

| Your Apify discount tier | Price per 1,000 results |
|---|---:|
| Free | $1.50 |
| Bronze | $1.50 |
| Silver | $1.20 |
| Gold | $0.90 |

Platform usage (compute, proxies, storage) is included in that price. Retries, blocked requests, duplicates, and listings that do not match your filters are free. Zillow applies your filters before it returns anything, so a narrower search returns fewer rows and costs less.

### ⬇️ Input example

**Sale, with filters:**

```json
{
  "locations": ["Austin, TX", "78731"],
  "listingType": "forSale",
  "minPrice": 400000,
  "maxPrice": 900000,
  "minBedrooms": 3,
  "hasPool": true,
  "maxItems": 500
}
```

**Rentals.** Price here means monthly rent:

```json
{
  "locations": ["Denver, CO"],
  "listingType": "forRent",
  "minPrice": 1500,
  "maxPrice": 2500,
  "maxItems": 200
}
```

**New listings only**, for a scheduled run:

```json
{
  "locations": ["Phoenix, AZ"],
  "daysOnZillow": "1",
  "maxItems": 0
}
```

### ⬆️ Output example

A real row:

```json
{
  "zpid": "29342650",
  "url": "https://www.zillow.com/homedetails/5014-W-Frances-Pl-Austin-TX-78731/29342650_zpid/",
  "price": 750000,
  "priceLabel": "$750,000",
  "zestimate": 726800,
  "rentZestimate": 4839,
  "bedrooms": 4,
  "bathrooms": 3,
  "livingArea": 2013,
  "streetAddress": "5014 W Frances Pl",
  "city": "Austin",
  "state": "TX",
  "zipcode": "78731",
  "homeStatus": "FOR_SALE",
  "listingType": "forSale",
  "searchLocation": "78731"
}
```

### Integrations

Results do not have to stop at a dataset. Every public Actor on Apify carries the platform's
integrations, so a run can push listings wherever your team already works.

- **n8n**, **Make**, **Zapier** and **Pipedream** for no-code automation.
- **HubSpot** and **Airtable** to land listings as CRM records or rows.
- **Slack**, **Telegram** and **Gmail** to push new listings to people rather than to storage.
- **Google Drive**, **Snowflake**, **Keboola** and **Airbyte** for exports and warehouse loads.
- **Webhooks** and the **GitHub** integration for anything custom.
- **LangChain**, the **OpenAI Agents SDK**, the **Vercel AI SDK** and **Google ADK** to hand
  listings to an LLM app.

Two combinations worth setting up on day one:

**A daily new-listings feed.** Schedule a run with **Listed within** set to 1 day and point a
webhook or Slack step at the finished dataset. Only the rows that appeared overnight count
against your bill, not a re-scrape of the whole area.

**An agent that searches Zillow.** The Apify **API** and **MCP server** both expose this Actor
as a callable tool. See the FAQ below for how its permissions work.

### ❓ FAQ

#### Is it legal to scrape Zillow?

You are responsible for how you use the data you collect. Read Apify's guide on the [legality of web scraping](https://blog.apify.com/is-web-scraping-legal/).

#### Does this Actor respect robots.txt?

No. Zillow's robots.txt disallows the `/*?searchQueryState=*` route this Actor uses to run filtered searches, and there is no other route that carries prices, so a filtered Zillow search cannot be run compliantly by anyone. A **Respect robots.txt** switch sits in the Advanced section anyway. Flip it on and the Actor stops and tells you why, instead of quietly returning nothing. If Zillow's robots.txt cannot be fetched at all, the run logs a warning and carries on, since there is nothing to check against.

The permitted alternative is Zillow's sitemap. It publishes addresses without prices, which means it cannot answer "three-bed homes under $600k." Every Zillow search Actor on the Apify Store uses the same route this one does.

#### Why does a price filter do nothing on Zillow rentals?

On this Actor it does work. Everywhere else, treat a rental price filter as decoration. Zillow accepts the normal price key on rental searches and silently drops it. Applying a $1,500 to $2,500 filter to 24,938 Austin rentals returned 24,938 rentals. Rentals use a different key internally, and this Actor switches to it for you.

#### Why did I get fewer results than Zillow says exist?

Either your **Maximum results** cap was reached, or area splitting is off and the search hit the 820 ceiling. Check the run log; it states which one happened.

#### Can I use my own Zillow search URL?

Yes. Paste it into **Zillow search URLs** and it runs exactly as given, so any filter Zillow supports will work. Area splitting cannot apply to a pasted URL, so those searches stop at 820.

#### Can I use this with the Apify API or an MCP server?

Yes. Use the **API** and **MCP** tabs on this Actor's page. It runs under limited permissions, so an agent can call it without broad account access.

### Support

Missing a filter, or want a different output shape? Open an issue on the **Issues** tab. Filter requests are welcome. If Zillow's own search supports a field, it can usually be added.

# Actor input Schema

## `locations` (type: `array`):

Cities, ZIP codes, counties, or neighborhoods. For example "Austin, TX", "78731" or "Brooklyn, NY". Anything that cannot be matched is reported in the log and skipped rather than failing the run.

## `searchUrls` (type: `array`):

Paste the address bar from a Zillow search you have already set up, filters and all. Use this when you want a filter this Actor does not expose. Pasted URLs are used exactly as given, so the large-area splitting below cannot apply to them.

## `listingType` (type: `string`):

For sale, for rent, or recently sold.

## `maxItems` (type: `integer`):

Your spend limit for the whole run. The search stops as soon as this many results have been delivered. Set 0 for no limit.

## `maxItemsPerLocation` (type: `integer`):

Cap results from each location separately, so one big city cannot use up the whole run. Set 0 for no per-location cap.

## `minPrice` (type: `integer`):

Total price for sales and sold listings; monthly rent for rentals.

## `maxPrice` (type: `integer`):

Total price for sales and sold listings; monthly rent for rentals.

## `minBedrooms` (type: `integer`):

Fewest bedrooms a listing may have.

## `maxBedrooms` (type: `integer`):

Most bedrooms a listing may have.

## `minBathrooms` (type: `integer`):

Fewest bathrooms a listing may have.

## `minLivingArea` (type: `integer`):

Smallest interior area, in square feet.

## `maxLivingArea` (type: `integer`):

Largest interior area, in square feet.

## `minLotSize` (type: `integer`):

Smallest lot, in square feet.

## `maxLotSize` (type: `integer`):

Largest lot, in square feet.

## `minYearBuilt` (type: `integer`):

Earliest year of construction.

## `maxYearBuilt` (type: `integer`):

Latest year of construction.

## `daysOnZillow` (type: `string`):

Only listings posted within this window. Useful on a schedule to catch new listings.

## `hasPool` (type: `boolean`):

Only listings with a pool.

## `isWaterfront` (type: `boolean`):

Only listings on the water.

## `singleStoryOnly` (type: `boolean`):

Only single-story homes.

## `hasGarage` (type: `boolean`):

Only listings with a garage.

## `hasAirConditioning` (type: `boolean`):

Only listings with air conditioning.

## `hasBasement` (type: `boolean`):

Only listings with a basement.

## `mustHaveOpenHouse` (type: `boolean`):

Only listings with an open house scheduled.

## `mustHave3dTour` (type: `boolean`):

Only listings with a 3D tour.

## `sortBy` (type: `string`):

Which listings come back first. Only matters when Maximum results is lower than the number of matches.

## `splitLargeAreas` (type: `boolean`):

Zillow never returns more than 820 results for a single search, however many matches it reports. Austin reports 5,717 and hands back 820. With this on, an area holding more than that is split into smaller areas and each is searched separately, so you get the whole set. Turn it off to stop at 820 per location and spend less.

## `respectRobotsTxt` (type: `boolean`):

Zillow's robots.txt disallows the filtered-search route this Actor depends on, so switching this on stops the Actor working. It will say so plainly rather than return an empty result set. It is here so the choice is yours and is not hidden.

## `maxConcurrency` (type: `integer`):

Measured on the detail Actor: 10 parallel is clean, 15 doubles the block rate for no gain.

## `maxRequestsPerMinute` (type: `integer`):

Usually the setting that decides how long a run takes. 300 measured as the sweet spot.

## `proxyConfiguration` (type: `object`):

Zillow rejects datacenter IPs on search and listing pages. A measured A/B returned 60 of 60 properties on residential and nothing at all on datacenter. Residential US is the default and is what this Actor is priced around.

## Actor input object example

```json
{
  "locations": [
    "Austin, TX",
    "78731"
  ],
  "searchUrls": [],
  "listingType": "forSale",
  "maxItems": 100,
  "maxItemsPerLocation": 0,
  "daysOnZillow": "",
  "hasPool": false,
  "isWaterfront": false,
  "singleStoryOnly": false,
  "hasGarage": false,
  "hasAirConditioning": false,
  "hasBasement": false,
  "mustHaveOpenHouse": false,
  "mustHave3dTour": false,
  "sortBy": "",
  "splitLargeAreas": true,
  "respectRobotsTxt": false,
  "maxConcurrency": 10,
  "maxRequestsPerMinute": 300,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `listings` (type: `string`):

Every matching Zillow listing found by the search, one row each, de-duplicated by zpid. Opens on the Listings view; switch views in Console to see Price vs Zestimate, Coordinates, or Rentals & buildings.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "locations": [
        "Austin, TX"
    ],
    "searchUrls": [],
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("studioxl/zillow-search-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "locations": ["Austin, TX"],
    "searchUrls": [],
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("studioxl/zillow-search-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "locations": [
    "Austin, TX"
  ],
  "searchUrls": [],
  "maxItems": 100
}' |
apify call studioxl/zillow-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,studioxl/zillow-search-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/QgccYX0PYWOXmjVQH/builds/SnUz0TgNIh9F8wpqm/openapi.json
