# Zillow Scraper — Homes for Sale, Rent, Sold & Zestimates (`tortuga/zillow-scraper`) Actor

Scrape Zillow homes for sale, for rent and recently sold from search URLs or any US city, ZIP or neighborhood: price, Zestimate, beds, baths, sqft, photos, agent, open houses, price cuts.

- **URL**: https://apify.com/tortuga/zillow-scraper.md
- **Developed by:** [Trevor Ortega](https://apify.com/tortuga) (community)
- **Categories:** Real estate
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 listing scrapeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Zillow Scraper — Homes for Sale, Rent, Sold & Zestimates

Scrape **Zillow** listings for any US city, ZIP code, neighborhood, county or state: homes **for sale**, **for rent** and **recently sold**. Paste a Zillow search URL straight from your browser (any filters, sort, zoomed or drawn map area), or type locations and pick filters here (price, beds, baths, home type, living area, lot size, year built, HOA, days on Zillow, keywords). Every listing comes with the zpid and URL, address, coordinates, price or rent, **Zestimate and Rent Zestimate as Zillow shows them**, beds, baths, living area, lot size, home type, status, days on Zillow, **price cuts** (amount and date), sold price and date, open houses, the brokerage named on the listing and all photos. Turn on **details** to add the full description, year built, HOA fee, listing agent, MLS number, facts and features, and, where Zillow shows them on the page, price history, tax history and nearby schools.

No login and no browser: the actor reads the same search data Zillow's own map loads, 41 listings per request, and splits big searches automatically past Zillow's 820-result limit.

### What data does Zillow Scraper extract?

| Field | Example | Notes |
|---|---|---|
| `id`, `zpid`, `url` | `29502952` | `id` = zpid; apartment communities have no zpid (`id` like `lot1001414165`) |
| `status`, `listingStatus`, `statusText`, `listingType` | `FOR_SALE`, `for_sale`, `Active`, `For Sale by Agent` | `listingStatus`: for_sale, pending, coming_soon, for_rent, sold, ... |
| `price`, `priceText`, `pricePerSqft` | `630000`, `$630,000`, `323` | Rentals: monthly rent. Sold: sale price |
| `zestimate` (= `estimate`), `rentZestimate`, `taxAssessedValue` | `616600`, `3066`, `495011` | As shown in the results; Zillow hides the Zestimate on many active listings |
| `priceReducedAmount`, `priceReducedDate`, `priceChange`, `priceReductionText` | `20000`, `2026-09-29`, `-20000`, `$20,000 (Sep 29)` | Latest price change |
| `soldPrice`, `soldDate`, `salePriceHidden` | `345000`, `2026-10-09`, `false` | Texas, Utah and other non-disclosure states: Zillow shows no sale price (`salePriceHidden: true`) |
| `daysOnZillow` (= `daysOnMarket`), `listDate`, `comingSoonDate` | `31`, `2026-09-09` | `listDate` = scrape time minus Zillow's time on site |
| `propertyType`, `homeType` | `single_family`, `SINGLE_FAMILY` | single_family, condo, townhouse, multi_family, land, manufactured, apartment |
| `beds`, `baths`, `sqft`, `lotSqft`, `lotSize` | `3`, `2`, `1950`, `44605`, `1.024 acres` | `lotSqft` converts acres to sq ft |
| `address`, `unit`, `city`, `state`, `zip`, `fullAddress`, `latitude`, `longitude` | `11306 Zyle Ln`, `Austin`, `TX`, `78737` | |
| `isNewConstruction`, `isForSaleByOwner`, `isForeclosure`, `isAuction`, `isComingSoon`, `isPending`, `isShowcase`, `has3DTour`, `hasVideo` | `false` | |
| `openHouses`, `openHouseText` | `[{"start": "2026-10-11T14:00:00", "end": "2026-10-11T16:00:00"}]`, `Sun. 2-4pm` | Local time at the property |
| `listingOffice`, `builder` | `JBGoodwin REALTORS WL` | Brokerage as shown on the card |
| `primaryPhoto`, `photos`, `photoCount` | large photo URLs | |
| Rentals: `buildingName`, `isBuilding`, `units`, `priceMin`, `priceMax`, `availableUnits`, `availableFrom` | `The Morgan`, `[{"beds": 1, "price": 825}]` | Apartment communities with their unit prices |
| **With details:** `description`, `yearBuilt`, `hoaFee`, `listingAgent`, `listingAgentPhone`, `listingContactPhone`, `mlsName`, `mlsListingId`, `datePosted`, `pageViews`, `favorites`, `propertyTaxRate`, `taxAnnualAmount`, `parcelNumber`, `county`, `keyFacts`, `facts`, `highlights`, `virtualTourUrl` | | Extra `detail` event |
| **With details, where Zillow shows them:** `priceHistory`, `taxHistory`, `schools` | | On rental pages (and price history on Showcase listings); see below |
| `changeType`, `previousValues` | `changed`, `{"price": 650000}` | Only with monitoring on |

### How to scrape Zillow listings

1. Open [zillow.com](https://www.zillow.com), search a place, set filters (and zoom or draw on the map if you like), and copy the address bar into **Start URLs**. Plain URLs (`/austin-tx/`, `/austin-tx/rentals/`, `/phoenix-az/sold/`, `/austin-tx-78704/`) and URLs with `?searchQueryState=...` both work. Property URLs (`.../homedetails/..._zpid/`) return that home with its details.
2. Or leave Start URLs empty and type **Locations** such as `Austin, TX`, `78704`, `Williamsburg, Brooklyn, NY`, `Travis County, TX` or `TX`, then pick **For sale / For rent / Recently sold** and filters.
3. Set **Max items** and run. Download the results as JSON, CSV or Excel, or read them through the API.

### Input example

```json
{
  "locations": ["Austin, TX", "78704"],
  "listingType": "for_sale",
  "homeTypes": ["houses", "townhomes"],
  "minBeds": 3,
  "maxPrice": 700000,
  "daysOnZillow": "30",
  "keywords": "pool",
  "sort": "newest",
  "maxItems": 500,
  "includeDetails": false
}
```

Start URL example: `{"startUrls": [{"url": "https://www.zillow.com/austin-tx/sold/"}], "maxItems": 200}`. Filters set in the input are added on top of the URL's own filters.

### Output example

```json
{
  "id": "29502952",
  "zpid": 29502952,
  "url": "https://www.zillow.com/homedetails/11306-Zyle-Ln-Austin-TX-78737/29502952_zpid/",
  "status": "FOR_SALE",
  "listingStatus": "for_sale",
  "statusText": "Active",
  "listingType": "For Sale by Agent",
  "price": 630000,
  "priceText": "$630,000",
  "pricePerSqft": 323,
  "zestimate": 616600,
  "rentZestimate": 3066,
  "estimate": 616600,
  "taxAssessedValue": 495011,
  "priceReducedAmount": 20000,
  "priceReducedDate": "2026-09-29",
  "priceChange": -20000,
  "priceReductionText": "$20,000 (Sep 29)",
  "daysOnZillow": 31,
  "daysOnMarket": 31,
  "listDate": "2026-09-09",
  "propertyType": "single_family",
  "homeType": "SINGLE_FAMILY",
  "beds": 3,
  "baths": 2,
  "sqft": 1950,
  "lotSqft": 44605,
  "lotSize": "1.024 acres",
  "address": "11306 Zyle Ln",
  "city": "Austin",
  "state": "TX",
  "zip": "78737",
  "fullAddress": "11306 Zyle Ln, Austin, TX 78737",
  "latitude": 30.20049,
  "longitude": -97.92846,
  "openHouses": [{"start": "2026-10-11T14:00:00", "end": "2026-10-11T16:00:00"}],
  "openHouseText": "Sun. 2-4pm",
  "listingOffice": "JBGoodwin REALTORS WL",
  "primaryPhoto": "https://photos.zillowstatic.com/fp/6da83d301e6234ec06efbf07043f741f-uncropped_scaled_within_1536_1152.jpg",
  "photoCount": 40,
  "detailsFetched": false,
  "search": "Austin, TX (for sale)",
  "scrapedAt": "2026-10-11T06:08:45Z"
}
```

With details (a rental): `"description": "Modern home with pool in the Estates section of Cardinal Hills ..."`, `"yearBuilt": 2015`, `"listingAgent": "Tabitha LeJeune"`, `"listingOffice": "Kuper Sotheby's International Realty"`, `"taxAnnualAmount": 21914`, `"priceHistory": [{"date": "2026-10-11", "event": "Listed for rent", "price": 4200, "source": "Zillow Rentals"}]`, `"taxHistory": [{"year": 2025, "taxPaid": 21913.83, "assessedValue": 1304420}]`, `"schools": [{"name": "Lake Travis Elementary School", "rating": 6, "level": "Primary", "grades": "PK-5", "distanceMiles": 1.7}]`.

### How many listings can I get per search?

Zillow shows at most 820 listings per search (20 pages of 41), whatever the total says. When a search has more and **Max items** asks for more, the actor cuts the map area into smaller tiles with tiny count requests until each tile fits, then loads the tiles one by one, so every listing is reachable (Austin, 5,648 homes for sale: 1,000 listings in 33 seconds). Results are de-duplicated by zpid across tiles and searches. With splitting, the sort order applies within each tile. Turn **Split searches over 820 listings** off to get only the first 820 in Zillow's order.

### Can I scrape rentals and sold homes?

Yes. Choose **For rent** or paste a `/rentals/` URL: single homes come with the monthly rent, apartment communities with their unit prices (`units`, `priceMin`, `priceMax`). Zillow mixes featured apartment communities into every rental search; when you pick home types without "Apartments", the actor drops them (not charged). Choose **Recently sold** or paste a `/sold/` URL: sold homes come with the sale date and, where the state discloses it, the sale price; use **Days on Zillow** to limit how far back (e.g. sold in the last 90 days).

### What do the property details include?

With **Include property details** the actor opens each home's Zillow page and adds what that page renders: description, year built, HOA fee, listing agent with the phone shown for inquiries, brokerage, MLS name and number, all photos, key facts and features, page views and saves, property tax rate. Zillow loads part of the page later in the browser, and that part differs by page type: **rental pages include price history, tax history and nearby schools**, Showcase listings include price history, but regular for-sale and sold pages do not carry price/tax history, schools or climate risk in the page itself, so those fields stay empty there. Apartment communities have no property details.

### Do I need a proxy? Does it work without login?

No login is needed. Zillow is protected by PerimeterX: Apify datacenter IPs were blocked after a few dozen requests in our tests, so the default is Apify **RESIDENTIAL** proxy (US). It is billed to your Apify account per GB: searches transfer about 0.9 MB per 1,000 listings (about $0.01), property pages about 0.1 MB each for homes for sale and 0.2 MB for rentals (about $0.8-1.5 per 1,000 details). Blocked answers are retried on a new IP with another browser fingerprint. The Unblocker is not needed.

### Monitor changes on a schedule

Turn on **Only new or changed items** to watch a search. Each run remembers what earlier runs returned and outputs only new listings and listings whose price, rent range or status changed (for example for sale to pending, a price cut, or sold). Unchanged listings are dropped before property pages are loaded, so they are not charged. **Max items** then sets how many listings are checked per run; keep the "Newest" sort, e.g. 100-300 for a city. Save the input as a task in Apify and add a Schedule (for example daily); give each watch its own **Monitoring key** if several schedules use the same search. Every output item gets `changeType` (`new` or `changed`), and changed items get `previousValues` with the old values, e.g. `{"price": 650000, "listingStatus": "for_sale"}`.

### Pricing

You pay per listing saved ($1.60 per 1,000), plus $0.80 per 1,000 for the optional property details. There is no start fee. Apify platform usage, including the residential proxy traffic, is billed to you separately: about $0.013 per 1,000 listings from searches in our tests, and about $0.8-1.5 per 1,000 property pages.

### Integrations and API

Run it from the Apify API, the Python or JavaScript client, Make, Zapier, n8n, or schedule it in Apify Console. Results can go to Google Sheets, a webhook or your database.

### Is it legal to scrape Zillow?

The actor collects listing data that anyone can see on Zillow without logging in: property facts, prices, Zestimates, photos and the agent and brokerage named on the listing with the phone number published for inquiries. It does not log in and leaves out what Zillow hides (for example sale prices in non-disclosure states). Agent names are personal data in some jurisdictions; you are responsible for having a legitimate purpose for processing them and for respecting Zillow's terms of use. If in doubt, ask a lawyer.

### Support

Found a bug or need a field that is not there? Open an issue on the Issues tab with the input you used and the run link.

# Actor input Schema

## `startUrls` (type: `array`):

Zillow search URLs copied from the browser (any filters, sort, drawn or zoomed map area: the URL's searchQueryState is used as is), e.g. https://www.zillow.com/austin-tx/, .../austin-tx/rentals/, .../austin-tx/sold/ or .../homes/for_sale/?searchQueryState=..., or property URLs (.../homedetails/...\_zpid/), which return that home with its details. Leave empty if you use Locations below.

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

US cities, ZIP codes, neighborhoods, counties or states, one per line, e.g. 'Austin, TX', '78704', 'Williamsburg, Brooklyn, NY', 'Travis County, TX', 'TX'. Each is searched separately (Zillow's own place lookup picks the region).

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

Which listings the Locations searches return (start URLs keep their own: /rentals/ = for rent, /sold/ = recently sold).

## `homeTypes` (type: `array`):

Only these home types. Leave empty for all.

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

Minimum list price, or minimum monthly rent for rentals, in US dollars.

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

Maximum list price, or maximum monthly rent for rentals, in US dollars.

## `minBeds` (type: `integer`):

Minimum number of bedrooms (0 = studio).

## `maxBeds` (type: `integer`):

Maximum number of bedrooms.

## `minBaths` (type: `integer`):

Minimum number of bathrooms.

## `minSqft` (type: `integer`):

Minimum interior living area in square feet.

## `maxSqft` (type: `integer`):

Maximum interior living area in square feet.

## `minLotSqft` (type: `integer`):

Minimum lot size in square feet (1 acre = 43,560 sq ft).

## `maxLotSqft` (type: `integer`):

Maximum lot size in square feet (1 acre = 43,560 sq ft).

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

Only homes built in or after this year.

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

Only homes built in or before this year.

## `maxHoaFee` (type: `integer`):

Maximum monthly HOA fee (0 = no HOA fee).

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

Only listings that came on Zillow within this period. For recently sold searches: sold within this period.

## `keywords` (type: `string`):

Words the listing must mention, as in Zillow's keyword filter, e.g. 'pool', 'waterfront', 'ADU'.

## `sort` (type: `string`):

Order of the results (what you get first when Max items is lower than the number of listings). 'Newest' = newest listings first; for sold searches, most recently sold first. A start URL with its own searchQueryState keeps the sort chosen on the site.

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

Stop after this many listings in total (all searches together). Each listing is one charged result. Zillow shows at most 820 listings per search; larger searches are split automatically (see below).

## `includeDetails` (type: `boolean`):

Also open each home's Zillow page: full description, year built, HOA fee, listing agent and brokerage, MLS id, all photos, facts and features, page views and saves, and, where Zillow shows them on the page, price history, tax history and nearby schools (rentals; price history also on Showcase listings). Charged as an extra 'detail' event per listing.

## `splitLargeSearches` (type: `boolean`):

Zillow serves at most 820 listings per search (20 pages). When a search has more and Max items asks for more, the map area is cut into smaller tiles so every listing is reachable (tiny count requests; results then come tile by tile).

## `onlyNewOrChanged` (type: `boolean`):

Remember what earlier runs of this search returned and output only new items or items whose price/status changed. Use with a Schedule; unchanged items are not charged. With this on, Max items is the number of listings checked per run (e.g. the newest 200).

## `monitoringKey` (type: `string`):

Name for this watch, so several schedules on the same search stay separate; defaults to a key derived from the search input

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

Zillow blocks datacenter IPs (PerimeterX), so the default is Apify RESIDENTIAL proxy in the US, billed per GB to your account: about $0.01 per 1,000 listings from searches and about $0.8-1.5 per 1,000 property pages (Include property details). Blocked answers are retried on a new IP. Unblocker is not needed.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.zillow.com/austin-tx/"
    }
  ],
  "listingType": "for_sale",
  "sort": "newest",
  "maxItems": 100,
  "includeDetails": false,
  "splitLargeSearches": true,
  "onlyNewOrChanged": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# 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 = {
    "startUrls": [
        {
            "url": "https://www.zillow.com/austin-tx/"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("tortuga/zillow-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 = {
    "startUrls": [{ "url": "https://www.zillow.com/austin-tx/" }],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("tortuga/zillow-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 '{
  "startUrls": [
    {
      "url": "https://www.zillow.com/austin-tx/"
    }
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call tortuga/zillow-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tortuga/zillow-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/CUo0TtwgsQpDVoVoT/builds/dwGQJhB5cZ1MtaDOn/openapi.json
