# Zillow Listings Scraper (price & new-listing alerts) (`datahamster/zillow-listings`) Actor

Get for-sale or for-rent listings from Zillow city search pages: price, beds, baths, area, status and coordinates. No login. Monitor mode alerts on new listings and price or status changes for a saved city search.

- **URL**: https://apify.com/datahamster/zillow-listings.md
- **Developed by:** [Viktor Dubnytskiy](https://apify.com/datahamster) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 result items

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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?

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 Listings Scraper (price & new-listing alerts)

Get Zillow for-sale or for-rent listings for one or more US cities, one flat row per listing, straight from the city search page. No login, no account, no session.

### What you get

Real rows from the example dataset (`cities: ["austin-tx"]`):

| address | price | beds/baths | area | status |
|---|---|---|---|---|
| 500 Denson Dr, Austin, TX 78752 | $395,000 | 3 / 2 | 1110 sqft | FOR\_SALE (Active) |
| ... | ... | ... | ... | ... |

Full row: `id` (zpid), `url`, `address`, `city`, `state`, `zip`, `price`, `priceText`, `beds`, `baths`, `areaSqft`, `lotAreaText`, `homeType`, `statusType`, `statusText`, `daysOnZillow`, `latitude`, `longitude`, `imageUrl`, `query`.

### Use cases

- **Buyer watch** — put a city (and optional price/bed filters) into monitor mode; each run returns only listings that are new since the previous run, so you get an alert instead of re-checking Zillow yourself.
- **Price-drop tracking** — monitor mode fingerprints each listing on price and status, so a run flags a listing the moment its price or status (for sale / pending / sold) changes.
- **One-off local market snapshot** — a single run for one or a few cities when you just need today's list of active listings and nothing recurring.

### Try it in 10 seconds

**One-off list** — hit **Start**/**Try it**, the input already works: `cities: ["austin-tx"]`, `forRent: false`, `maxListingsPerCity: 200`, `maxItems: 200`, nothing required.

**Monitor mode** — save the task, then set:

```json
{
  "mode": "monitor",
  "monitorStateId": "austin-tx-watch",
  "cities": ["austin-tx"],
  "minBeds": 3,
  "webhookUrl": "https://your-endpoint.example.com/hook",
  "telegramBotToken": "",
  "telegramChatId": ""
}
```

and put it on a schedule (Apify → Schedules). Each monitor run charges one `monitor-check` event ($0.006) and returns only listings that are new or whose price/status changed since the previous run, each billed as one `change` event ($0.003) — a run with nothing new charges only the check.

### Related actors

- [Amazon Price Tracker: Product Scraper by ASIN, Buy Box, Stock](https://apify.com/datahamster/amazon-product-monitor) — the same price-alert pattern for retail products.
- [Walmart Product Scraper & Price Monitor (stock, seller, store)](https://apify.com/datahamster/walmart-product-monitor) — same monitor pattern for a different retail marketplace.
- [Etsy Listings Scraper (search, shops, listing details, prices)](https://apify.com/datahamster/etsy-listings) — same alerting approach for handmade and vintage goods.

### How it works

1. Each city slug (or full Zillow city URL) is turned into the city's search page (`zillow.com/<city-slug>/` or `.../rentals/`) and paged (`<n>_p/`) until `maxListingsPerCity` is reached, a page repeats listings already seen, or Zillow signals the last page (a distinct "page past the end" response, not an empty page) — verified on real fetched pages for both the for-sale and the rentals path.
2. Each listing card is read from the page's own embedded JSON (`__NEXT_DATA__`) — the same data Zillow's own search results render from, not a scrape of visible HTML text.
3. Building-level rental cards (an apartment complex shown as one card with a price range across units, no single listing id) are skipped: they are not one listing.
4. A wall (bot-verification page) is retried through Apify's Web Unblocker proxy and reported as a block, so an empty dataset never hides a wall as "no listings".

### Input

| Field | Meaning | Default |
|---|---|---|
| `cities` | City slugs or Zillow city URLs | `["austin-tx"]` |
| `forRent` | false = for-sale, true = for-rent | `false` |
| `maxListingsPerCity` | Stop a city after this many matching listings | `200` |
| `minPrice` / `maxPrice` | Client-side price filter (USD) | none |
| `minBeds` | Client-side minimum-bedrooms filter | none |
| `maxItems` | Stop the whole run after this many rows | `200` |
| `mode` | `scrape` or `monitor` (only new/changed since last run) | `scrape` |
| `monitorStateId`, `webhookUrl`, `telegramBotToken`, `telegramChatId` | Monitor-mode state key and alert targets | empty |

### Pricing

| Event | Price |
|---|---|
| result | $0.003 per listing ($3 per 1,000) |
| monitor-check | $0.006 per monitor run |
| change | $0.003 per new/changed listing |

Charged only for listings actually pushed. Web Unblocker proxy usage is billed by Apify on top of the actor's own events.

**Found it useful?** A short review on the Store page helps other people find this actor and tells us what to improve. If a listing or alert looks wrong, open an issue on the actor page — issues are answered within a day.

### Why this actor

- No login and no cookies to supply.
- Client-side price/bed filters so you do not need to build Zillow's own filter URL.
- Monitor mode with webhook and Telegram alerts on new listings and price/status changes — built for a recurring watch, not just a one-off pull.
- A run that finds nothing pushes nothing and charges no result events; the run summary explains why instead of leaving you guessing.

### Limits

- US listings only (Zillow's own coverage).
- Filters (`minPrice`, `maxPrice`, `minBeds`) are applied after the fetch, not via Zillow's own filtered search URL, so a very narrow filter on a small city can still page through the whole unfiltered list.
- Building-level rental cards (apartment complexes shown with a price range instead of one listing) are not returned — only individually-listed units, which on a typical rentals page are a small minority of the cards (about 1 in 41 on the pages checked), so a rentals run over several pages returns few rows per page fetched.
- No agent, broker or owner names, and no phone numbers — only listing-level facts.

### FAQ

**Does it need a Zillow login or cookies?** No. There is no account field at all; every request is made as a logged-out visitor through Apify's Web Unblocker proxy.

**What happens when a city slug does not exist?** No rows are pushed for that city and no result events are charged; the `RUN_SUMMARY` record in the run's key-value store notes it separately from a real block.

**Are agent or broker names collected?** No. Listing rows carry only the property's own facts (address, price, beds/baths, area, status) — no agent, broker or owner name or phone number.

**What does monitor mode actually save me?** It keeps state per `monitorStateId` (or per saved task) across runs, so a schedule returns only listings that are new or whose price/status changed instead of the whole list again.

### Changelog

- 0.1: initial release — city-page listings (for-sale/for-rent), client-side price/bed filters, monitor mode; a wall is retried through Web Unblocker and reported as a block instead of an empty dataset.

***

If this actor saved you time, a short review on its Store page genuinely helps other people find it. Found a bug or need a field that is missing? Open a ticket on the **Issues** tab.

# Actor input Schema

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

Stop after this many results (you are charged only for pushed items)

## `mode` (type: `string`):

scrape = full results; monitor = only new/changed items since the previous run of this task

## `monitorStateId` (type: `string`):

Optional state id when not running as a saved task (monitor mode)

## `webhookUrl` (type: `string`):

POST a change summary here in monitor mode

## `telegramBotToken` (type: `string`):

Optional: bot token for monitor-mode change summaries

## `telegramChatId` (type: `string`):

Optional: chat id that receives monitor-mode summaries

## `cities` (type: `array`):

Zillow city slugs (e.g. "austin-tx", "marfa-tx") or full zillow.com city-page URLs, one per line.

## `forRent` (type: `boolean`):

false = for-sale listings, true = for-rent listings. Example: false.

## `maxListingsPerCity` (type: `integer`):

Stop paging a city after this many matching listings. Example: 200.

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

Drop listings priced below this (client-side filter, applied after fetch). Example: 200000.

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

Drop listings priced above this (client-side filter, applied after fetch). Example: 800000.

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

Drop listings with fewer bedrooms than this (client-side filter, applied after fetch). Example: 2.

## Actor input object example

```json
{
  "maxItems": 200,
  "mode": "scrape",
  "cities": [
    "austin-tx"
  ],
  "forRent": false,
  "maxListingsPerCity": 200
}
```

# Actor output Schema

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

All pushed rows (dataset, JSON)

## `resultsTable` (type: `string`):

Dataset in the Console viewer

## `runSummary` (type: `string`):

RUN\_SUMMARY record

# 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 = {
    "cities": [
        "austin-tx"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datahamster/zillow-listings").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 = { "cities": ["austin-tx"] }

# Run the Actor and wait for it to finish
run = client.actor("datahamster/zillow-listings").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 '{
  "cities": [
    "austin-tx"
  ]
}' |
apify call datahamster/zillow-listings --silent --output-dataset

```

## MCP server setup

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

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/tohdgZ3gIWITvTzXc/builds/ky2MihWFgYHkIUZbk/openapi.json
