# Zillow Scraper (`s-r/zillow-scraper`) Actor

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

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Zillow Scraper

Search Zillow and get properties back as rows: address, price, beds, baths, square feet, **price per square foot**, coordinates, home type, days on market and Zillow's own valuation estimate.

No login, no cookie, no browser, no solver.

### What you get

- **`price_per_sqft` on every priced row.** Zillow does not publish it on the card, and it is the number every property comparison actually turns on. Computed from price and living area, and null when either is missing rather than guessed
- **Coordinates on every row**, so results drop straight into a map or a spatial join
- **The full record, not just the card.** Home type, lot size, tax assessed value, Zestimate, rent Zestimate, days on market and the listing brokerage all come from the nested payload behind each result
- **Zillow's own match count.** `totalMatches` says 5.573 properties exist for a search that returned 100, so a sample never looks like the whole answer
- **For sale, recently sold and rentals** from one actor
- **An access route written down, with its date.** This is a measurement, not a property of the site, and it is stated as such below

### The access route, stated plainly

Zillow refuses almost every automated request, which is why most scrapers for it either need a browser or stop working.

It answers **200 with about 1,3 MB to `Google-Extended`**, which is Google's AI-training crawler token.

That is worth spelling out because no amount of reasoning would have produced it: Zillow refuses the crawler that indexes it and admits the one whose documented purpose is model training. A refusal from one crawler tells you nothing about another, which is why the working identity has to be found by enumeration rather than deduction.

The consequence for you: this needs no browser and no CAPTCHA solver, so it is fast and cheap. The risk: a single-identity route can close, and if it does this actor reports `forbidden` with that explanation rather than returning an empty result. Re-check monthly.

### Input

| Field | Type | Required | Default | What it does |
|---|---|---|---|---|
| `location` | string | one of the two | `Austin, TX` | City and state |
| `listing_type` | select | no | `for_sale` | For sale, sold, or rentals |
| `url` | string | one of the two | – | A Zillow search URL, filters included. Takes precedence |
| `limit` | integer | no | `100` | Properties to return, 1 to 800. A page carries 41 |
| `retries` | integer | no | `3` | Retry attempts per page |

### Output

```json
{
  "position": 1,
  "zpid": "29369311",
  "url": "https://www.zillow.com/homedetails/6305-Leatherwood-Cv-Austin-TX-78759/29369311_zpid/",
  "address": "6305 Leatherwood Cv, Austin, TX 78759",
  "city": "Austin",
  "state": "TX",
  "zipcode": "78759",
  "price": 750000.0,
  "beds": 4.0,
  "baths": 3.0,
  "living_area_sqft": 2164.0,
  "price_per_sqft": 346.58,
  "lot_area": 9017.0,
  "home_type": "SINGLE_FAMILY",
  "status": "FOR_SALE",
  "days_on_zillow": 5,
  "tax_assessed_value": 558793.0,
  "latitude": 30.422058,
  "longitude": -97.75903,
  "broker": "Keller Williams Realty",
  "listing_type": "for_sale"
}
```

### Use cases

**Comparable-sales analysis.** Pull a ZIP code, group by `home_type` and take the median `price_per_sqft`. That single column is what an appraisal argument is built on, and computing it yourself from a scraped price and a scraped area is where most pipelines introduce their errors.

**Finding mispriced listings.** Compare `price` against `zestimate` per row. A listing well under Zillow's own estimate is either a bargain or has a problem, and either way it is worth a look. `days_on_zillow` tells you whether the market already noticed.

**Investor screening at scale.** `tax_assessed_value` beside `price` shows where asking prices have run ahead of assessments. Combine with `rent_zestimate` for a gross yield estimate without a second data source.

**Mapping and territory work.** Every row carries latitude and longitude, so a run drops into a map, a drive-time analysis or a spatial join with no geocoding step and no geocoding bill.

**Market monitoring.** Schedule a location and diff on `zpid`. New ids are new listings, disappeared ids are off-market, and `days_on_zillow` on the survivors shows how fast the market is moving.

### How it compares

| | this actor | `maxcopell/zillow-detail-scraper` | `igolaizola/zillow-scraper-ppe` |
|---|---|---|---|
| Per 1.000 properties | **$1,50** | **no per-item rate** | $0,90 |
| Actor-start fee | $0,002 | – | – |
| `price_per_sqft` computed | **yes** | not stated | not stated |
| Coordinates on every row | **yes** | not stated | not stated |
| Zillow's own match count returned | **yes** | no | no |
| Access route documented | **yes, with its date** | no | no |
| Monthly users | new | **1.030** | 126 |

Honest about the other side, and it is the important column: **`maxcopell` holds 1.030 monthly users on this actor alone, 1.930 across three, and charges nothing per row.** No price competes with that. The case for paying here is the derived fields and knowing exactly how the data is being fetched.

### Pricing

Two events. `run_start` costs $0,0020 per run. `property` costs $0,0015 per property written to the dataset, which is $1,50 per 1.000.

### Limits and gotchas

- **Sold listings carry no sale price.** Zillow returns `soldPrice` as an empty string in its sold search payload and no price field exists anywhere in the record; verified across a full page of 41. Those rows carry `date_sold` and `zestimate` instead, and the run summary says so. If you need actual sale prices, this surface does not have them.
- **Rentals are keyed differently.** Rental results are often buildings rather than individual homes, so `zpid` can be absent and the URL is the stable key.
- **41 properties per page, paginated by path.** `/2_p/` moves; there is no page parameter. A URL you paste should be the plain one.
- **`beds` is missing on some sold rows** because Zillow omits it there, about one row in five in testing.
- **`zestimate` is Zillow's model, not a valuation.** It is returned because it is useful as a reference point, not because it is authoritative.
- **Pages are fetched one at a time on purpose.** The site tolerates a single identity, and hammering it in parallel is the fastest way to lose the only route that works. A 800-row run takes minutes.
- **US only.**

### FAQ

**Does this need a browser or a CAPTCHA solver?**
No. It is a plain HTTP fetch, which is why it is fast.

**Why does my sold search have no prices?**
Because Zillow does not put them in that payload. It is absent at the source, not dropped by the actor.

**How many properties can I get in one run?**
Up to 800, which is about 20 pages. `totalMatches` tells you how many exist so you can decide whether to narrow the area.

**Can I use my own filtered search?**
Yes. Apply the filters on Zillow, then paste the URL into `url`.

**Will this keep working?**
It is a measurement taken on 2 September 2026, not a guarantee. Single-identity routes close. If it does, the run says `forbidden` and explains why rather than returning nothing.

### Related Actors

- [Redfin Scraper](https://apify.com/s-r/redfin-scraper) — the same job on Redfin
- [Marktplaats Scraper](https://apify.com/s-r/marktplaats-scraper) — Dutch classifieds including property
- [Willhaben Scraper](https://apify.com/s-r/willhaben-scraper) — Austrian classifieds across four verticals

# Actor input Schema

## `location` (type: `string`):

City and state, for example "Austin, TX". Optional if you pass a URL.

## `listing_type` (type: `string`):

For sale, recently sold, or rentals.

## `url` (type: `string`):

A zillow.com search URL to read instead. Paste it from your browser after applying filters. Takes precedence.

## `limit` (type: `integer`):

How many to return, 1 to 800. A page carries 41.

## `retries` (type: `integer`):

Retry attempts per page, each with a rotated TLS fingerprint.

## Actor input object example

```json
{
  "location": "Austin, TX",
  "listing_type": "for_sale",
  "url": "https://www.zillow.com/homes/Austin,-TX_rb/",
  "limit": 100,
  "retries": 3
}
```

# Actor output Schema

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

One row per property listing.

## `summary` (type: `string`):

Properties returned against Zillow's own match count, plus price, coordinate and zestimate coverage.

## `errors` (type: `string`):

Per-page failures with a code and a redacted message.

# 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 = {
    "location": "Austin, TX",
    "listing_type": "for_sale",
    "limit": 100,
    "retries": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/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 = {
    "location": "Austin, TX",
    "listing_type": "for_sale",
    "limit": 100,
    "retries": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/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 '{
  "location": "Austin, TX",
  "listing_type": "for_sale",
  "limit": 100,
  "retries": 3
}' |
apify call s-r/zillow-scraper --silent --output-dataset

```

## MCP server setup

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