# Zillow Scraper: Sale, Rent and Sold Property Listings (`abotapi/zillow-scraper`) Actor

Scrape zillow.com properties for sale, for rent and recently sold across the US: price, beds, baths, area, address, GPS, photos, home value and rent estimates. Optional property page read adds HOA fee, tax rate, MLS id, agent, brokerage and the fact sheet. 25 filters, or paste links.

- **URL**: https://apify.com/abotapi/zillow-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 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?

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: Sale, Rent and Sold Property Listings

Zillow Scraper turns zillow.com into structured property data. Search any US place by name or ZIP for homes for sale, homes for rent or recently sold homes, or paste links you already have. Every record carries price, beds, baths, area, full address, GPS, photos and the site's own home value and rent estimates; switch on the property page read and it also carries the description, year built, HOA fee, property tax rate, MLS id, listing agent, brokerage and the full fact sheet. Export to JSON, CSV or Excel, or pull results straight into your app through the API.

### Why This Scraper?

- **Coverage past the site's own ceiling.** One read of a map area returns at most 500 records however many properties exist inside it. This actor splits a busy area into quarters and reads each one, so a city with thousands of homes is genuinely covered instead of truncated at the first few hundred.
- **Honest completeness.** Every run reports whether it scanned an area end to end. A refused read, an unresolved place or a run cap all mark the scan partial, and expiry tracking refuses to act on a partial scan.
- **Three channels, one shape.** For sale, for rent and recently sold return the same columns, so a rental feed and a sold-comparables feed line up without post-processing.
- **25 search filters.** Channel, price, beds, baths, area, lot size, year built, HOA fee, property type, keyword, time on market, sort order and eight feature toggles.
- **Recurring monitoring built in.** Run the same search on a schedule and get only what changed, marked NEW, UPDATED, REAPPEARED or EXPIRED, with the exact list of fields that moved.
- **Pay for results, not for pages.** The search-level record costs one result event. Reading each property's own page is optional and billed separately, so you never pay for detail you did not ask for.
- **No fields quietly dropped.** Agent name, phone, brokerage and MLS id are returned whenever the property page publishes them, instead of being silently omitted.

### Use Cases

- **Property investors and flippers:** screen a whole metro for price, days on market and lot size, then compare against the home value estimate.
- **Rental market research:** track asking rent by bedroom count and neighborhood across several cities at once.
- **Comparable sales analysis:** pull recently sold homes near a subject property to support a valuation or an offer.
- **Lead generation for agents and lenders:** monitor a farm area on a schedule and get only the new listings and price changes.
- **Market dashboards and reports:** build a standing dataset of active inventory, price trends and property mix for a region.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live listing.

| Field | Example |
| --- | --- |
| `id` / `zpid` | `"00000001"` |
| `url` | `"https://www.zillow.com/homedetails/00000001_zpid/"` |
| `listingType` | `"FOR_SALE"` (also `"FOR_RENT"`, `"SOLD"`) |
| `homeStatus` / `statusText` | `"FOR_SALE"` / `"Active"` |
| `propertyType` | `"SINGLE_FAMILY"` |
| `price` / `formattedPrice` | `612000` / `"$612,000"` |
| `zestimate` / `rentZestimate` | `604800` / `2875` |
| `taxAssessedValue` | `498000` |
| `priceChange` / `priceChangeDate` | `-9000` / `"2026-02-01T00:00:00.000Z"` |
| `streetAddress` / `city` / `state` / `zipCode` | `"142 Sample Ridge Dr"` / `"Austin"` / `"TX"` / `"78701"` |
| `latitude` / `longitude` | `30.30` / `-97.70` |
| `bedrooms` / `bathrooms` | `4` / `3` |
| `livingArea` | `2100` |
| `lotAreaValue` / `lotAreaUnit` | `8000` / `"sqft"` |
| `daysOnZillow` | `14` |
| `priceReduction` / `marketingTagline` / `cardHighlight` | `"$9,000 (Feb 1)"` / `"Showcase"` / short card badge text |
| `cardType` / `units` / `dateSold` / `listingSoldPrice` / `availabilityDate` / `sourceSearchUrl` | card type, apartment-building units, sold date and price (sold searches), rental availability, originating search link |
| `listingSubType` / `openHouse` / `openHouseSchedule` | `{"isFSBA": true, "isOpenHouse": true}` (listing-kind flags: for sale by agent or owner, coming soon, pending, auction, foreclosure, bank owned, open house, new home; only the flags the card carries) / `"Sat. 11am-2pm"` / `[{"startDate": "2026-09-05T11:00:00", "endDate": "2026-09-05T14:00:00"}]` (open-house windows, property-local wall-clock time with no time-zone suffix) |
| `heroImage` / `photos` | property photo URL(s) |
| `description` / `yearBuilt` / `monthlyHoaFee` / `propertyTaxRate` / `mlsId` / `mlsName` | `"..."` / `1995` / `60` / `1.9` / `"1234567"` / `"Sample MLS"` (property page only) |
| `listingAgentName` / `listingAgentPhone` / `brokerageName` | `"Jordan Example"` / `"(555) 010-0100"` / `"Sample Realty Group"` (property page only) |
| `resoFacts` | parking, sub-type and other fact-sheet fields (property page only) |
| `changeType` / `changedFields` | `"UPDATED"` / `["price"]` (incremental mode only) |
| `detailFetched` / `scrapedAt` | `true` / `"2026-01-01T00:00:00.000Z"` |

**Dataset views.** The output tab offers three views: **Results** (the main card fields), **Property page details** (description, year built, HOA fee, MLS, agent and brokerage, filled when you switch on the property page read) and **Changes** (`changeType` and `changedFields`, filled in incremental mode).

**Added card fields.** Newer rows also carry `cardType`, `units`, `priceReduction`, `marketingTagline`, `cardHighlight`, `dateSold`, `listingSoldPrice`, `availabilityDate`, `priceIncludesMonthlyFees`, `isRentalWithBasePrice`, `marketingTreatments`, `sourceSearchUrl`, `listingSubType` and `openHouseSchedule`. They are additions only; no existing field was renamed or removed. In incremental mode they are ignored by default when deciding `UPDATED`, so an existing saved baseline does not suddenly bill a wave of changes.

Rental results also include building and community cards, which carry a price range instead of one price and are returned with the fields the site publishes for them.

### How to Use

1. Pick a **mode**: `search` (build a search from a location and filters) or `url` (paste links you already have).
2. In search mode, add one or more **locations** and set the channel and any filters. In URL mode, paste search or property links.
3. Turn on **Read the property page for each result** if you need the description, year built, HOA fee, MLS id, agent and brokerage.
4. Set **Max results** to control run size and cost, then click **Start**. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Search one city for homes for sale:**

```json
{
  "mode": "search",
  "locations": ["Austin, TX"],
  "listingType": "FOR_SALE",
  "maxItems": 200
}
```

**Search several places for rentals under a price, with filters:**

```json
{
  "mode": "search",
  "locations": ["Brooklyn, NY", "Jersey City, NJ"],
  "listingType": "FOR_RENT",
  "maxPrice": 3500,
  "minBeds": 2,
  "homeTypes": ["APARTMENT", "CONDO"],
  "daysOnZillow": "30_DAYS",
  "maxItems": 500
}
```

**Recently sold comparables with the property page read switched on:**

```json
{
  "mode": "search",
  "locations": ["78759"],
  "listingType": "SOLD",
  "daysOnZillow": "12_MONTHS",
  "fetchDetails": true,
  "maxItems": 100
}
```

**Paste links you already have:**

```json
{
  "mode": "url",
  "startUrls": [
    "https://www.zillow.com/homes/for_sale/Austin,-TX/",
    "https://www.zillow.com/homedetails/00000001_zpid/"
  ],
  "maxItems": 50
}
```

**URL mode defaults:** if you switch to URL mode and leave the links field untouched (or omit `startUrls` from an API call), the run scrapes one example of each supported link shape: a for-sale search, a city page, a page-numbered city page (`/austin-tx/2_p/` starts at page 2, it does not restart at page 1), a for-rent search and one property page (returned as a single record). Replace the list, or pass an empty list, to scrape only your own links. Max results still caps the whole run.

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/zillow-scraper").call(
    run_input={"mode": "search", "locations": ["Austin, TX"], "maxItems": 50}
)
for record in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(record["address"], record["price"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/zillow-scraper').call({
  mode: 'search', locations: ['Austin, TX'], maxItems: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### How coverage and Max results interact

One read of a map area returns at most 500 records, however many exist inside it. This actor covers a busy area by splitting it into quarters and reading each one, up to a run-level cap on total reads (**Max reads per location**). When an area still holds more than one read can return and can no longer be split, the run reports that area as not fully covered rather than reporting a silent partial count.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one interrupted run: paste its run or dataset id and the actor skips every row already collected there, so you don't pay twice.
- **Incremental mode** (`incrementalMode`) is for scheduled runs over the same search. Each property is classified `NEW`, `UPDATED` (with `changedFields`), `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on), `REAPPEARED` or `EXPIRED` (only after a run scanned the whole search end to end, and only with `emitExpired`). `stateKey` names or shares the stored state. With incremental mode off, output is exactly as before.
- **Ignoring noisy fields (incremental mode).** These fields are always ignored when deciding `UPDATED`: the scrape time, days on Zillow, favourite count and page view count (they move on their own), and the added card fields `sourceSearchUrl`, `cardType`, `units`, `isValid`, `hasImage`, `marketingTagline`, `cardHighlight`, `priceReduction`, `priceIncludesMonthlyFees`, `isRentalWithBasePrice`, `marketingTreatments`, `availabilityDate`, `dateSold`, `listingSoldPrice`, `listingSubType` and `openHouseSchedule`. A change to only one of them does not, on its own, make a row `UPDATED` (a changed `price` or `openHouse` label still does), and `changedFields` leaves them out; they are still returned in every row. Add more with `ignoreFieldsForChanges`; Zillow's own `zestimate` and `rentZestimate` are recomputed regularly, so list them there if you only care about listing changes. The saved baseline is compared again with your current list on every run, so editing the list never turns the whole baseline into `UPDATED`.
- **Resume plus incremental mode.** You can combine the two only on a campaign with no saved state yet; with saved state the run stops and asks you to remove the resume id or pick a different `stateKey`. On that first combined run the resumed properties are skipped, and they carry no saved values into the campaign. So the next incremental run returns each resumed property that is still listed once as `UPDATED` (with every field in `changedFields`) and bills it. From the run after that, classification is normal. To avoid that one extra batch, start a campaign with a plain incremental run instead of a resume.
- **Tip: keep Max results high (or 0) on incremental runs.** Max results limits how many properties each read checks, not only how many are returned. This is deliberate: a property the run never checked is never counted as seen, so the run is correctly reported as partial and nothing is wrongly marked `EXPIRED`. The side effect is that unchanged properties use up the cap even though they are not returned. With a small cap, a new or changed property that sits later in the scan order can go unchecked for that run, and the run can end with fewer rows, or none, and a status saying nothing changed, even though changes exist. Those properties are not lost: any later run whose cap reaches them reports them. Set **Max results** to `0`, or comfortably above the area's normal listing count, whenever `incrementalMode` is on; keep a tight cap for one-off searches.
- **Tip: `emitExpired` rows count toward Max results.** Once a run has scanned its tracked search end to end, properties that are no longer found are pushed and billed as `EXPIRED` in that same run, but only while the run is still under **Max results** (the cap covers the whole run, every location or link together, minus the rows already returned). Properties left over when the cap is reached stay tracked and are reported by a later run; **Max results** `0` means no cap, so every one is reported at once. A run that stopped because it reached **Max results** did not scan the search end to end, so it reports no `EXPIRED` rows at all. Turn `emitExpired` off if you only want the `NEW`/`UPDATED` delta.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify, Settings, Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` builds the search from the location and filters below. `url` takes zillow.com links you already have. |
| `locations` | array | `["Austin, TX"]` | Search mode only. One or more US places, written the way you would type them into the site. The run cap is shared evenly between them. |
| `listingType` | string | `FOR_SALE` | Search mode only. `FOR_SALE`, `FOR_RENT` or `SOLD`. |
| `homeTypes` | array | `[]` | Search mode only. Empty means every property type. |
| `minPrice` | integer | none | Search mode only. Lowest price in USD (lowest monthly rent on a for-rent search). |
| `maxPrice` | integer | none | Search mode only. Highest price in USD (highest monthly rent on a for-rent search). |
| `minBeds` | integer | none | Search mode only. Fewest bedrooms. |
| `maxBeds` | integer | none | Search mode only. Most bedrooms. |
| `minBaths` | integer | none | Search mode only. Fewest bathrooms. |
| `minSqft` | integer | none | Search mode only. Smallest interior area in square feet. |
| `maxSqft` | integer | none | Search mode only. Largest interior area in square feet. |
| `minLotSize` | integer | none | Search mode only. Smallest lot in square feet. |
| `maxLotSize` | integer | none | Search mode only. Largest lot in square feet. |
| `minYearBuilt` | integer | none | Search mode only. Earliest year built. |
| `maxYearBuilt` | integer | none | Search mode only. Latest year built. |
| `maxHoaFee` | integer | none | Search mode only. Highest monthly HOA fee in USD. `0` returns only properties with no HOA fee. |
| `keywords` | string | none | Search mode only. Narrows to properties whose listing text mentions this word. |
| `daysOnZillow` | string | `ANY` | Search mode only. How recently the property was listed, or sold on a recently-sold search. |
| `sortBy` | string | `RECOMMENDED` | Search mode only. Result ordering; changes the order results arrive in, not which properties match. |
| `hasPool` | boolean | `false` | Search mode only. Must have a pool. |
| `hasGarage` | boolean | `false` | Search mode only. Must have a garage. |
| `hasAirConditioning` | boolean | `false` | Search mode only. Must have air conditioning. |
| `isWaterfront` | boolean | `false` | Search mode only. Waterfront only. |
| `singleStoryOnly` | boolean | `false` | Search mode only. Single story only. |
| `has3dTour` | boolean | `false` | Search mode only. Must have a 3D tour. |
| `openHouseOnly` | boolean | `false` | Search mode only. Open house only. |
| `priceReducedOnly` | boolean | `false` | Search mode only. Price reduced only. |
| `startUrls` | array | one example of each link shape | URL mode only. zillow.com search links (walked automatically) or property links ending in `_zpid`. Search-mode filters are ignored in this mode. |
| `fetchDetails` | boolean | `false` | Read the property page for each result. Adds description, year built, HOA fee, property tax rate, MLS id and name, listing agent and brokerage, full photo gallery, parking and the fact sheet. Billed per enriched result. |
| `maxItems` | integer | `20` | Maximum number of results across every location or link. `0` means unlimited. |
| `maxPages` | integer | `0` | Optional safety cap on search reads per location or link. `0` means no read limit; the run still stops at Max results. |
| `resumeFromRunId` | string | none | Continue one interrupted run without returning or billing for rows already collected there. For recurring monitoring, use Incremental mode instead. |
| `incrementalMode` | boolean | `false` | Return only new and changed results on scheduled runs against the same search. |
| `stateKey` | string | none | Name a monitoring campaign to keep its state stable, or share state across differently configured runs. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) results unchanged since the last run. |
| `emitExpired` | boolean | `false` | Also return (and bill) results that were present before and are no longer found, once a run has scanned the search end to end. |
| `ignoreFieldsForChanges` | array | none | Incremental mode only. Field names that never, on their own, make a property `UPDATED`, for example `zestimate`. Use a dotted name such as `resoFacts.hoaFee` for a nested field. Ignored fields are still returned in every row. |
| `proxy` | object | Apify Proxy, US | How this run reaches the site. The prefilled option is billed by traffic on top of the run itself. You can also supply your own connection URLs instead. |
| `mcpConnectors` | array | none | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | none | Notion connector only: page under which item pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. Does not affect the dataset. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live listing.

```json
{
  "id": "00000001",
  "zpid": "00000001",
  "url": "https://www.zillow.com/homedetails/00000001_zpid/",
  "listingType": "FOR_SALE",
  "homeStatus": "FOR_SALE",
  "statusText": "Active",
  "propertyType": "SINGLE_FAMILY",
  "price": 612000,
  "formattedPrice": "$612,000",
  "currency": "USD",
  "zestimate": 604800,
  "rentZestimate": 2875,
  "taxAssessedValue": 498000,
  "priceChange": -9000,
  "priceChangeDate": "2026-02-01T00:00:00.000Z",
  "streetAddress": "142 Sample Ridge Dr",
  "city": "Austin",
  "state": "TX",
  "zipCode": "78701",
  "country": "USA",
  "address": "142 Sample Ridge Dr, Austin, TX 78701",
  "latitude": 30.30,
  "longitude": -97.70,
  "bedrooms": 4,
  "bathrooms": 3,
  "livingArea": 2100,
  "lotAreaValue": 8000,
  "lotAreaUnit": "sqft",
  "daysOnZillow": 14,
  "heroImage": "https://photos.zillowstatic.com/fp/00000000-p_e.jpg",
  "photos": ["https://photos.zillowstatic.com/fp/00000000-p_e.jpg"],
  "description": "Single story home with an open kitchen and a covered patio.",
  "yearBuilt": 1995,
  "monthlyHoaFee": 60,
  "propertyTaxRate": 1.9,
  "pricePerSquareFoot": 291,
  "mlsId": "1234567",
  "mlsName": "Sample MLS",
  "listingAgentName": "Jordan Example",
  "listingAgentPhone": "(555) 010-0100",
  "brokerageName": "Sample Realty Group",
  "resoFacts": {
    "parkingCapacity": 2,
    "propertySubType": ["Single Family Residence"]
  },
  "detailFetched": true,
  "scrapedAt": "2026-01-01T00:00:00.000Z"
}
```

### Plan Requirement

Zillow guards its pages with a bot-protection layer that refuses datacenter connections outright. Only residential connections work, so this actor needs a residential connection. The prefilled **Proxy & connection** setting (Apify Proxy, US) is residential and works out of the box; it is billed by traffic on top of the run itself. A plan without access to residential connections cannot use it: the run logs that the selected connection is unavailable (Apify free plans do not include residential proxies) and then ends as failed with a clear message, so upgrade your plan or supply your own residential connection URLs under **Proxy & connection**. A datacenter-only setup returns nothing. The actor rotates connections automatically when one is refused. When the residential connection is open on your account but the site keeps refusing its exits, the last attempt of each read goes over a backup connection, and the log says once when the backup served the data. Your own connection URLs never get a backup.

### FAQ

#### How much does it cost?

You pay per result returned, plus a start charge per run. The property page read is a separate charge, applied once per enriched result and only when you switch it on and the record is actually returned. The **Pricing** tab shows the current rates. Use **Max results** to cap the cost of any run.

#### Is it legal to scrape Zillow?

This actor collects only publicly available listing data. You are responsible for how you use it: follow Zillow's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listing facts (price, beds, baths, address) are generally usable, but photos, descriptions and estimates such as the home value figure may carry additional rights.

#### Can I get only new or changed listings on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new and updated properties, and unchanged ones are not billed. Keep **Max results** high (or `0`) on these runs; a small cap can cause the run to stop scanning before it reaches a genuinely new or changed listing, and can return fewer rows, or none, even though changes exist. See the tips under How to Use for the exact mechanics.

#### Why did a scheduled run suddenly return a lot of `EXPIRED` rows?

Once a run scans its tracked search completely, previously tracked properties that are no longer found are returned as `EXPIRED` in that same run. These rows count toward **Max results** (the whole run, minus the rows already returned), so with a cap the leftover properties stay tracked and a later run reports them; with **Max results** `0` the whole batch comes at once, and a large swing in local inventory can make it bigger than you expect. Turn `emitExpired` off if you only want the new and updated delta.

#### What does it not return?

Coverage is US only. Price history, tax history and school assignments are not returned, because those tables are not published on the pages this actor reads.

#### Why did my run fail instead of returning an empty dataset?

If the site refuses every request, the run stops with the message "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later." (or, when the account is refused proxy access, "The proxy connection was refused for this account, so no page could be read ..."), so "no listings matched" is never confused with "nothing could be read". Run it again in a few minutes, and check that **Proxy & connection** is a residential one. A search that genuinely matches nothing ends with "No results matched. Widen the filters, or in URL mode check the link points at a zillow.com search or property page." Property page reads that keep failing stop early instead of being retried for the whole run. If only some locations or links could be read, you still get what was found, and the run says the scan was partial.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 More scrapers you might like

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🏠 <a href="https://apify.com/abotapi/redfin-scraper"><b>Redfin Scraper</b></a><br>Scrape redfin.com home listings: price, beds, baths, sqft, lot, year built, coordinates...</td><td>🧩 <a href="https://apify.com/abotapi/homes-com-scraper"><b>Homes.com Scraper</b></a><br>Scrape US property records from Homes.com. Pick a city, state or ZIP and a channel (for...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/realtor-com-scraper"><b>Realtor.com</b></a><br>Extract property listings from realtor.com. Get comprehensive data, including prices...</td><td>🏠 <a href="https://apify.com/abotapi/view-com-au-scraper"><b>View.com.au Scraper</b></a><br>Scrape View.com.au properties for sale, rent and recently sold across Australia. Search...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/homes-co-nz-scraper"><b>Homes.co.nz Scraper</b></a><br>Scrape Homes.co.nz listings by location or URL with pagination. Extract property details...</td><td>🏠 <a href="https://apify.com/abotapi/crexi-commercial-listings-scraper"><b>Crexi Scraper</b></a><br>Scrape crexi.com commercial listings: asking price, cap rate, square footage, units, year...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/zillow-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

How to start. 'Search by location' builds the search from the location and filters below. 'Direct URLs' takes zillow.com search or property links you already have.

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

Search mode only. One or more US places, written the way you would type them into the site: 'Austin, TX', 'Brooklyn, NY', '90210'. Each one is walked separately, and the run cap below is shared evenly between them.

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

Search mode only. For sale, for rent, or recently sold properties.

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

Search mode only. Leave empty for every type. Selecting one or more returns only those types.

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

Search mode only. Lowest price in USD. On a for-rent search this is the lowest monthly rent.

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

Search mode only. Highest price in USD. On a for-rent search this is the highest monthly rent.

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

Search mode only. Fewest bedrooms.

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

Search mode only. Most bedrooms.

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

Search mode only. Fewest bathrooms.

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

Search mode only. Smallest interior area in square feet.

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

Search mode only. Largest interior area in square feet.

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

Search mode only. Smallest lot in square feet. One acre is 43560 square feet.

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

Search mode only. Largest lot in square feet.

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

Search mode only. Earliest year built.

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

Search mode only. Latest year built.

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

Search mode only. Highest monthly HOA fee in USD. Set 0 to return only properties with no HOA fee.

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

Search mode only. Narrows to properties whose listing text mentions this word, for example pool, casita, cul-de-sac. Leave empty for no keyword filter.

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

Search mode only. How recently the property was listed. On a recently-sold search this is how recently it sold.

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

Search mode only. Result ordering. This changes the order results arrive in, it does not change which properties match.

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

Search mode only.

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

Search mode only.

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

Search mode only.

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

Search mode only.

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

Search mode only.

## `has3dTour` (type: `boolean`):

Search mode only.

## `openHouseOnly` (type: `boolean`):

Search mode only.

## `priceReducedOnly` (type: `boolean`):

Search mode only.

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

URL mode only. Paste zillow.com search links (walked automatically) or property links ending in \_zpid (returned as one record each). Multiple links supported (search pages, city pages, page-numbered pages, for-rent searches and property pages). Left untouched, the field scrapes one example of each shape. The filters above are ignored in this mode, because a pasted link already carries its own.

## `fetchDetails` (type: `boolean`):

Adds description, year built, HOA fee, property tax rate, MLS id and name, listing agent and brokerage, full photo gallery, parking and the property fact sheet. Costs one extra read per result and is billed per enriched result under the surcharge named in this section. Leave off for the search-level record, which already carries price, beds, baths, address, GPS, home value estimate, rent estimate and a photo.

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

Maximum number of results to return across every location or link. This is the run cap. Set 0 for unlimited.

## `maxPages` (type: `integer`):

Optional safety cap on how many search reads to spend on each location or link. A large area is covered by splitting it into smaller areas, so a big city needs more than one read. 0 means no read limit; the run still stops at Max results.

## `proxy` (type: `object`):

How this run reaches the site. The prefilled option is billed by traffic on top of the run itself; see https://apify.com/pricing for rates. You can also supply your own connection URLs instead.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID (or dataset ID) from this actor to continue a large crawl without returning or billing for results already collected there. Use this after an interrupted run. For recurring monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for recurring monitoring. The first run returns every match as NEW. Later runs return only NEW, UPDATED and REAPPEARED results. State is kept separately per mode, locations or links, every filter and the property-page setting, so two different searches never mix. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently configured runs. Leave empty to let the actor derive a key from the mode, locations or links, every filter and the property-page setting.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return results that have not changed since the last run, marked UNCHANGED. This returns, and bills for, extra rows you already have, so leave it off unless you want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return results that were present in a previous run and are no longer found, marked EXPIRED, once a run has scanned the whole search end to end. Adds, and bills for, extra rows, so leave it off unless you need expiry tracking.

## `ignoreFieldsForChanges` (type: `array`):

Optional. Field names that should never, on their own, turn a property into UPDATED. Always ignored, so you do not need to list them: the scrape time and the counters that move by themselves (days on Zillow, favourite count, page view count), and the added card fields sourceSearchUrl, cardType, units, isValid, hasImage, marketingTagline, cardHighlight, priceReduction, priceIncludesMonthlyFees, isRentalWithBasePrice, marketingTreatments, availabilityDate, dateSold, listingSoldPrice, listingSubType and openHouseSchedule (a change to only one of these does not, on its own, make a property UPDATED). Add more if a field is noisy for your search, for example zestimate or rentZestimate (Zillow's own estimates, which are recomputed regularly). Use a dotted name such as resoFacts.hoaFee for a field inside a nested object. Ignored fields are still returned in every row. Changing this list never marks the whole saved baseline as updated.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify, Settings, Integrations, then select it here. The connector receives a condensed, human-readable summary per item (address plus key fields), not the full JSON; the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "locations": [
    "Austin, TX"
  ],
  "listingType": "FOR_SALE",
  "homeTypes": [],
  "daysOnZillow": "ANY",
  "sortBy": "RECOMMENDED",
  "hasPool": false,
  "hasGarage": false,
  "hasAirConditioning": false,
  "isWaterfront": false,
  "singleStoryOnly": false,
  "has3dTour": false,
  "openHouseOnly": false,
  "priceReducedOnly": false,
  "startUrls": [
    "https://www.zillow.com/homes/for_sale/Austin,-TX/",
    "https://www.zillow.com/austin-tx/",
    "https://www.zillow.com/austin-tx/2_p/",
    "https://www.zillow.com/homes/for_rent/Austin,-TX/",
    "https://www.zillow.com/homedetails/1600-Pennsylvania-Ave-NW-Washington-DC-20500/84074482_zpid/"
  ],
  "fetchDetails": false,
  "maxItems": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `changes` (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 = {
    "mode": "search",
    "locations": [
        "Austin, TX"
    ],
    "listingType": "FOR_SALE",
    "startUrls": [
        "https://www.zillow.com/homes/for_sale/Austin,-TX/",
        "https://www.zillow.com/austin-tx/",
        "https://www.zillow.com/austin-tx/2_p/",
        "https://www.zillow.com/homes/for_rent/Austin,-TX/",
        "https://www.zillow.com/homedetails/1600-Pennsylvania-Ave-NW-Washington-DC-20500/84074482_zpid/"
    ],
    "fetchDetails": false,
    "maxItems": 20,
    "maxPages": 0,
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/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 = {
    "mode": "search",
    "locations": ["Austin, TX"],
    "listingType": "FOR_SALE",
    "startUrls": [
        "https://www.zillow.com/homes/for_sale/Austin,-TX/",
        "https://www.zillow.com/austin-tx/",
        "https://www.zillow.com/austin-tx/2_p/",
        "https://www.zillow.com/homes/for_rent/Austin,-TX/",
        "https://www.zillow.com/homedetails/1600-Pennsylvania-Ave-NW-Washington-DC-20500/84074482_zpid/",
    ],
    "fetchDetails": False,
    "maxItems": 20,
    "maxPages": 0,
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/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 '{
  "mode": "search",
  "locations": [
    "Austin, TX"
  ],
  "listingType": "FOR_SALE",
  "startUrls": [
    "https://www.zillow.com/homes/for_sale/Austin,-TX/",
    "https://www.zillow.com/austin-tx/",
    "https://www.zillow.com/austin-tx/2_p/",
    "https://www.zillow.com/homes/for_rent/Austin,-TX/",
    "https://www.zillow.com/homedetails/1600-Pennsylvania-Ave-NW-Washington-DC-20500/84074482_zpid/"
  ],
  "fetchDetails": false,
  "maxItems": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/zillow-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/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/73zI6vYOBAkXWHzeK/builds/5oa9hI0JZcHOQ5L1y/openapi.json
