# Zillow Listing Scraper (`searchshark/zillow-listing-scraper`) Actor

Scrape Zillow property listings by location, search URL, property URL, or ZPID. Track prices, status changes, lifecycle history, coverage quality, conflicts, and source provenance with false-removal protection.

- **URL**: https://apify.com/searchshark/zillow-listing-scraper.md
- **Developed by:** [SearchShark Tools](https://apify.com/searchshark) (community)
- **Stats:** 2 total users, 0 monthly users, 73.3% runs succeeded, 0 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.

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 - Listing Change Ledger & Coverage Monitor

**Zillow Scraper - Listing Change Ledger & Coverage Monitor** extracts **structured Zillow property data** and helps you track **what changed, where it was observed, and whether the scan was complete enough to trust**.

Use it for property research, listing monitoring, CRM automation, PropTech workflows, ETL pipelines, and change detection without manually revisiting Zillow pages.

- 🏠 **Scrape Zillow listings:** collect ZPIDs, addresses, prices, beds, baths, square footage, property type, coordinates, and more
- 🔎 **Search by location or Zillow URL:** use cities, ZIP codes, neighborhoods, direct Zillow search URLs, property URLs, or ZPIDs
- 🧬 **Deduplicate by ZPID:** reconcile overlapping observations into one canonical property record
- 🧭 **Preserve provenance:** keep the searches and search URLs that observed each property
- 📉 **Track listing changes:** detect new listings, price cuts, price increases, status changes, reactivations, and absence signals
- 🛡 **Avoid false removals:** blocked, capped, or incomplete scans are not silently treated as confirmed removals
- 📊 **Measure scan quality:** receive coverage diagnostics showing caps, failures, blocks, unique ZPIDs, duplicates, and detail success
- ⚡ **Automate workflows:** export structured results through Apify Dataset, API, integrations, or downstream applications

***

### 📦 What data does Zillow Scraper extract?

| | |
| --- | --- |
| 🆔 **ZPID** | 🔗 **Canonical Zillow URL** |
| 🏠 **Address** | 🏷 **Listing status** |
| 💵 **Price** | 🛏 **Beds** |
| 🛁 **Baths** | 📐 **Square feet** |
| 🏘 **Property type** | 📈 **Zestimate** when available |
| 📍 **Latitude & longitude** | 🕒 **Scrape timestamp** |
| 🔎 **Source query** | 🌐 **Source URL** |
| 🧭 **Matched queries** | 🔗 **Matched search URLs** |
| 🥇 **First discovery query** | 🧹 **Duplicate observation count** |
| ✅ **Data consistency status** | ⚠️ **Conflict evidence** |
| 🔬 **Detail enrichment status** | 📊 **Coverage diagnostics** |
| 🔄 **Change events** | 🧬 **Lifecycle summaries** |

> **Missing fields may be returned as `null`.** The Actor does **not invent property data** that Zillow does not expose.

***

## 🚀 Features

Zillow Scraper is designed for **auditable property extraction and change monitoring**.

- **Multiple discovery modes:** locations, Zillow search URLs, property URLs, and ZPIDs
- **Canonical ZPID reconciliation:** use stable Zillow property IDs as the primary identity
- **Overlapping-query provenance:** preserve every query and search URL that observed a property
- **Conflict detection:** retain material disagreements instead of silently hiding conflicting values
- **Coverage diagnostics:** report whether each discovery scope was complete, partial, blocked, capped, or unknown
- **False-removal protection:** incomplete scans do not create fake confirmed-removal events
- **Listing lifecycle tracking:** retain first seen, last seen, observation counts, price-cut totals, pending transitions, and reactivations
- **Change-event output:** emit explicit machine-readable events with previous/current values and evidence
- **Selective detail enrichment:** fetch property details for all listings or only when they are new, changed, or insufficient
- **Per-search and global limits:** control maximum accepted listing observations
- **Retry handling:** retry temporary request failures according to your configuration
- **Concurrent discovery:** process independent search targets in parallel
- **API-friendly output:** every Dataset row uses a predictable `recordType`
- **Browser-backed Zillow transport:** designed for live Zillow pages and current Zillow response structures

***

## ⬇️ Input

You can configure Zillow Scraper directly from the **Apify Console** or send input programmatically through the **Apify API**.

You can combine multiple input types in one run.

***

### 📍 Locations

Use `locations` for Zillow location searches.

Examples:

```text
Austin, TX
Miami, FL
90210
Downtown Los Angeles, CA
```

Example:

```json
{
  "locations": [
    "Austin, TX",
    "Miami, FL"
  ]
}
```

Each location becomes its own discovery scope with its own coverage evidence.

***

### 🌐 Zillow search URLs

Use `searchUrls` when you already have a Zillow search page.

Example:

```json
{
  "searchUrls": [
    "https://www.zillow.com/austin-tx/"
  ]
}
```

The Actor keeps the search URL as provenance and applies supported Actor filters when configured.

***

### 🏠 Direct property URLs

Use `propertyUrls` to scrape specific public Zillow property pages.

Example:

```json
{
  "propertyUrls": [
    "https://www.zillow.com/homedetails/example-property/123456789_zpid/"
  ]
}
```

Direct property inputs are validated against the embedded ZPID when available.

***

### 🆔 ZPIDs

Use `zpids` when you already know the Zillow property IDs.

Example:

```json
{
  "zpids": [
    "123456789",
    "987654321"
  ]
}
```

ZPIDs are treated as stable property identities for reconciliation and monitoring.

***

### 🔢 Maximum items per search

`maxItemsPerLocation` controls the maximum accepted listing observations from each location or Zillow search URL.

```text
Minimum: 1
Default: 1000
Maximum: 5000
```

If the limit is reached before completeness can be proven, the search is reported as:

```text
coverageStatus: CAPPED
```

with a transparent reason such as:

```text
USER_TOTAL_LIMIT_REACHED
```

***

### 🌍 Maximum total items

`maxTotalItems` limits accepted observations across all direct targets and searches in the run.

```text
Minimum: 1
Default: 5000
Maximum: 50000
```

This is a maximum, not a guarantee.

If Zillow exposes fewer usable listings, the Actor returns the listings it can verify instead of fabricating rows.

***

## 🎛 Listing filters

### 🏷 Listing status

Supported `listingStatus` values:

```text
for_sale
for_rent
sold
pending
any
```

`any` preserves the URL/default status state instead of forcing a specific supported status filter.

Default:

```text
for_sale
```

***

### 🏘 Property types

Supported `propertyTypes` values:

```text
SINGLE_FAMILY
CONDO
TOWNHOUSE
MULTI_FAMILY
APARTMENT
MANUFACTURED
LOT
```

Example:

```json
{
  "propertyTypes": [
    "SINGLE_FAMILY",
    "CONDO"
  ]
}
```

***

### 💵 Price filters

Optional inputs:

```text
minPrice
maxPrice
```

Example:

```json
{
  "minPrice": 300000,
  "maxPrice": 900000
}
```

***

### 🛏 Bed and bath filters

Optional inputs:

```text
minBeds
maxBeds
minBaths
```

Example:

```json
{
  "minBeds": 3,
  "maxBeds": 5,
  "minBaths": 2
}
```

***

## 🔬 Detail enrichment

Set:

```text
includeDetails: true
```

to allow Zillow property-detail requests.

Supported `detailStrategy` values:

```text
none
all
new_or_changed_only
```

Default:

```text
new_or_changed_only
```

#### `none`

Skip detail enrichment.

#### `all`

Attempt detail enrichment for every canonical property, subject to `maxDetailFetches`.

#### `new_or_changed_only`

Use detail requests selectively for records that are new, changed, conflicting, or missing useful information.

This can reduce unnecessary requests during recurring monitor runs.

***

### 🔢 Maximum detail fetches

`maxDetailFetches` limits the number of property-detail requests in one run.

```text
Minimum: 0
Default: 250
Maximum: 5000
```

Coverage output reports:

```text
detailRequested
detailSucceeded
detailSuccessRate
```

so downstream systems can see how much enrichment actually succeeded.

***

## 🧹 ZPID deduplication

By default:

```text
deduplicateByZpid: true
```

The Actor reconciles multiple observations of the same ZPID into one canonical `CURRENT` record.

This is useful when the same property appears in:

```text
Austin, TX
+
78747
+
a Zillow search URL
```

Instead of blindly returning duplicate properties, the Actor can preserve the overlap as provenance.

***

### 🧭 Matched-query provenance

When:

```text
preserveMatchedQueries: true
```

the canonical property can include:

```json
{
  "matchedQueries": [
    "Austin, TX",
    "78747"
  ],
  "matchedSearchUrls": [
    "https://www.zillow.com/austin-tx/",
    "https://www.zillow.com/..."
  ]
}
```

This answers an important question:

> **Which discovery scopes actually observed this ZPID?**

That provenance is especially useful for ETL, CRM routing, data QA, and monitoring systems.

***

## ⚠️ Cross-source conflicts

If material observations disagree, the Actor can preserve the disagreement instead of silently choosing a value.

Example:

```json
{
  "dataStatus": "CONFLICT",
  "conflicts": [
    {
      "field": "price",
      "values": [
        500000,
        525000
      ]
    }
  ]
}
```

Possible `dataStatus` values:

```text
CONSISTENT
CONFLICT
PARTIAL
```

This makes conflicting source evidence visible to downstream automation.

***

## 📊 Coverage diagnostics

Enable:

```text
includeCoverageReport: true
```

to receive `COVERAGE` records for discovery scopes.

Coverage can include:

```text
rawRows
uniqueZpids
duplicatesRemoved
reportedMatches
pagesScanned
partitionsScanned
requestsSucceeded
requestsFailed
detailRequested
detailSucceeded
detailSuccessRate
capHit
blockedRequests
coverageStatus
coverageReasons
checkedAt
```

Supported `coverageStatus` values:

```text
COMPLETE
PARTIAL
BLOCKED
CAPPED
UNKNOWN
```

***

### 🛡 Why coverage matters

A scraper should not treat:

```text
0 results
```

as automatically meaning:

```text
0 properties exist
```

A scan may instead be:

```text
blocked
partially parsed
capped by a user limit
affected by a request failure
missing detail enrichment
```

Zillow Scraper exposes these conditions so automated systems can decide whether the output is safe to trust.

***

## 🔄 Monitoring changes across runs

Set:

```text
monitorMode: true
```

and provide a stable:

```text
monitorId
```

to compare the current run with persistent monitor state.

Example:

```json
{
  "monitorMode": true,
  "monitorId": "austin-investment-watch"
}
```

Use the **same `monitorId`** on future runs to continue the same history.

***

### 🆕 First monitor run

When a property is seen for the first time, the Actor can emit:

```text
changeType: NEW
```

The lifecycle state begins with:

```text
firstSeenAt
lastSeenAt
observationsCount
lifecycleStage
```

***

### 🔁 Later monitor runs

If a property is observed again with no material change:

```text
No new CHANGE_EVENT is required
```

but its lifecycle state can still update:

```text
lastSeenAt
observationsCount
```

If something materially changes, the Actor can emit an evidence-backed change event.

***

## 🔔 Change-event types

Supported `changeType` values include:

```text
NEW
PRICE_CUT
PRICE_INCREASE
STATUS_CHANGED
ACTIVE_AGAIN
NOT_SEEN_CONFIRMED
NOT_SEEN_PARTIAL_SCAN
DATA_CONFLICT
```

Each `CHANGE_EVENT` can contain:

```text
changedFields
previousValues
currentValues
detectedAt
previousObservedAt
currentObservedAt
priceChange
priceChangePercent
observationConfidence
evidence
reasonCodes
```

***

### 📉 Price changes

Example:

```json
{
  "recordType": "CHANGE_EVENT",
  "zpid": "123456789",
  "changeType": "PRICE_CUT",
  "changedFields": [
    "price"
  ],
  "previousValues": {
    "price": 650000
  },
  "currentValues": {
    "price": 625000
  },
  "priceChange": -25000
}
```

Lifecycle output can retain:

```text
numberOfPriceCuts
cumulativePriceCut
cumulativePriceCutPercent
```

***

### 🏷 Status changes

The Actor can track status transitions such as:

```text
FOR_SALE → PENDING
PENDING → FOR_SALE
```

Lifecycle output can retain:

```text
wentPendingCount
backOnMarketCount
statusTransitions
```

when event-history retention is enabled.

***

## 🛡 False-removal protection

One of the most important monitoring rules is:

> **An incomplete Zillow scan must not be treated as proof that a property disappeared.**

If a previously observed property is missing from a run, the Actor evaluates the scan evidence first.

#### ✅ Confirmed absence

When the required discovery coverage is reliable enough, the Actor can emit:

```text
NOT_SEEN_CONFIRMED
```

#### ⚠️ Incomplete scan

When the search was blocked, capped, failed, or otherwise incomplete, the Actor can emit:

```text
NOT_SEEN_PARTIAL_SCAN
```

instead of silently declaring the property removed.

The retained monitor state is protected from fake disappearance signals caused by bad scans.

***

## 🧬 Lifecycle records

In monitor mode, the Actor can emit `LIFECYCLE` records such as:

```json
{
  "recordType": "LIFECYCLE",
  "zpid": "123456789",
  "firstSeenAt": "2026-09-01T00:00:00.000Z",
  "lastSeenAt": "2026-09-11T00:00:00.000Z",
  "observationsCount": 6,
  "numberOfPriceCuts": 2,
  "cumulativePriceCut": -35000,
  "wentPendingCount": 1,
  "backOnMarketCount": 1,
  "lifecycleStage": "FOR_SALE",
  "statusTransitions": []
}
```

This provides a compact history summary without requiring you to reconstruct every state transition manually.

***

### 📜 Retain lifecycle history

Set:

```text
retainEventHistory: true
```

to retain supported lifecycle status-transition history in persistent monitor state.

The current version does **not** create a separate historical-event Dataset for every previous observation.

***

### 🎯 Emit only changes

Set:

```text
emitOnlyChanges: true
```

when you primarily want change events during monitoring.

In this mode, normal `CURRENT` and `LIFECYCLE` rows can be omitted from public output while requested coverage diagnostics remain available.

This is useful for:

```text
webhooks
CRM triggers
alerts
scheduled monitors
change-only ETL jobs
```

***

## 🌐 Proxy configuration

Zillow can return temporary blocks or interactive challenges.

You can provide an Apify `proxyConfiguration` when needed.

Example:

```json
{
  "proxyConfiguration": {
    "groups": [
      "UNBLOCKER"
    ]
  }
}
```

The Actor does **not** pretend that a blocked page is a successful empty result.

Blocked requests are reported through coverage and error diagnostics such as:

```text
blockedRequests
coverageStatus: BLOCKED
errorCode: BLOCKED
```

> The Actor is designed to work with public Zillow pages. It does **not** bypass interactive CAPTCHA or human-verification challenges.

***

## ⚙️ Reliability controls

### 🚀 Maximum concurrency

`maxConcurrency` controls how many independent search targets can be collected concurrently.

```text
Minimum: 1
Default: 8
Maximum: 50
```

***

### 🔁 Maximum retries

`maxRetries` controls retry attempts for retryable Zillow failures.

```text
Minimum: 0
Default: 3
Maximum: 10
```

***

### ⏱ Request delay

`requestDelayMs` controls the base delay used around throttled/retry requests.

```text
Minimum: 0 ms
Default: 250 ms
Maximum: 30000 ms
```

***

## 🧪 Example input

```json
{
  "locations": [
    "Austin, TX"
  ],
  "searchUrls": [],
  "propertyUrls": [],
  "zpids": [],
  "maxItemsPerLocation": 100,
  "maxTotalItems": 500,
  "listingStatus": "for_sale",
  "propertyTypes": [
    "SINGLE_FAMILY",
    "CONDO"
  ],
  "minPrice": 300000,
  "maxPrice": 900000,
  "minBeds": 3,
  "minBaths": 2,
  "includeDetails": true,
  "detailStrategy": "new_or_changed_only",
  "maxDetailFetches": 100,
  "deduplicateByZpid": true,
  "preserveMatchedQueries": true,
  "includeCoverageReport": true,
  "monitorMode": true,
  "monitorId": "austin-homes-watch",
  "emitOnlyChanges": false,
  "retainEventHistory": true,
  "proxyConfiguration": {
    "groups": [
      "UNBLOCKER"
    ]
  },
  "maxConcurrency": 4,
  "maxRetries": 3,
  "requestDelayMs": 250
}
```

***

## ⬆️ Output

Results are stored in the Actor's default **Apify Dataset**.

You can access them through:

```text
Output
Storage
Apify API
Integrations
```

The Dataset can contain five record types:

```text
CURRENT
CHANGE_EVENT
LIFECYCLE
COVERAGE
ERROR
```

Use the `recordType` field to route each row in downstream workflows.

***

## 🏠 Example CURRENT record

```json
{
  "recordType": "CURRENT",
  "zpid": "123456789",
  "canonicalUrl": "https://www.zillow.com/homedetails/123456789_zpid/",
  "address": "123 Example St, Austin, TX 78701",
  "status": "FOR_SALE",
  "price": 625000,
  "beds": 3,
  "baths": 2,
  "sqft": 1800,
  "propertyType": "SINGLE_FAMILY",
  "zestimate": null,
  "latitude": 30.2672,
  "longitude": -97.7431,
  "scrapedAt": "2026-09-11T00:00:00.000Z",
  "sourceQuery": "Austin, TX",
  "sourceUrl": "https://www.zillow.com/austin-tx/",
  "matchedQueries": [
    "Austin, TX"
  ],
  "matchedSearchUrls": [
    "https://www.zillow.com/austin-tx/"
  ],
  "firstDiscoveredByQuery": "Austin, TX",
  "duplicateObservationCount": 0,
  "dataStatus": "CONSISTENT",
  "conflicts": [],
  "detailFetched": true,
  "detailStatus": "SUCCESS"
}
```

***

## 📊 Example COVERAGE record

```json
{
  "recordType": "COVERAGE",
  "queryKey": "location:austin-tx",
  "query": "Austin, TX",
  "searchUrl": "https://www.zillow.com/austin-tx/",
  "rawRows": 41,
  "uniqueZpids": 41,
  "duplicatesRemoved": 0,
  "reportedMatches": null,
  "pagesScanned": 1,
  "partitionsScanned": 1,
  "requestsSucceeded": 2,
  "requestsFailed": 0,
  "detailRequested": 0,
  "detailSucceeded": 0,
  "detailSuccessRate": null,
  "capHit": true,
  "blockedRequests": 0,
  "coverageStatus": "CAPPED",
  "coverageReasons": [
    "USER_TOTAL_LIMIT_REACHED"
  ],
  "checkedAt": "2026-09-11T00:00:00.000Z"
}
```

A `CAPPED` scan is not treated as secretly complete.

***

## 🔄 Example CHANGE\_EVENT record

```json
{
  "recordType": "CHANGE_EVENT",
  "zpid": "123456789",
  "changeType": "STATUS_CHANGED",
  "changedFields": [
    "status"
  ],
  "previousValues": {
    "status": "FOR_SALE"
  },
  "currentValues": {
    "status": "PENDING"
  },
  "detectedAt": "2026-09-11T00:00:00.000Z",
  "previousObservedAt": "2026-09-10T00:00:00.000Z",
  "currentObservedAt": "2026-09-11T00:00:00.000Z",
  "priceChange": null,
  "priceChangePercent": null,
  "observationConfidence": "HIGH",
  "evidence": {
    "searchSucceeded": true,
    "detailSucceeded": true,
    "coverageStatus": "COMPLETE"
  },
  "reasonCodes": []
}
```

***

## ❌ Example ERROR record

Errors are explicit Dataset records rather than silent empty results.

Example:

```json
{
  "recordType": "ERROR",
  "errorCode": "PARTIAL_COVERAGE",
  "message": "Discovery scope could not be proven complete.",
  "queryKey": "location:austin-tx",
  "sourceUrl": "https://www.zillow.com/austin-tx/",
  "retryable": true,
  "createdAt": "2026-09-11T00:00:00.000Z"
}
```

Supported diagnostic codes include:

```text
INVALID_INPUT
REQUEST_FAILED
BLOCKED
PARSE_FAILED
DETAIL_FAILED
SEARCH_CAPPED
PARTIAL_COVERAGE
DATA_CONFLICT
```

***

## 👀 Dataset views

The Dataset schema includes dedicated views for different workflows.

#### 🏠 Listing ledger overview

Focuses on:

```text
record type
ZPID
address
status
price
change type
data status
coverage status
confidence
source
timestamps
```

#### 🔄 Monitoring evidence

Focuses on:

```text
change events
previous/current values
price deltas
first/last seen
price-cut totals
pending/reactivation counts
lifecycle stage
```

#### 🛡 Coverage & quality

Focuses on:

```text
coverage status
coverage reasons
raw rows
unique ZPIDs
duplicates
request success/failure
blocks
detail success
conflicts
errors
```

***

## ⚡ Performance

The Actor uses browser-backed Zillow requests designed for current public Zillow page structures.

Performance depends on:

```text
Number of search targets
Result limits
Detail strategy
Maximum detail fetches
Proxy configuration
Zillow response time
Concurrency
Retry settings
```

For lower cost and faster recurring monitors, consider:

```text
detailStrategy: new_or_changed_only
```

instead of fetching full details for every unchanged property on every run.

***

## 🛡 Reliability

Zillow Scraper is designed to make failure states visible.

If one discovery scope fails while others succeed, successful data can still remain available with explicit coverage diagnostics for the affected scope.

The Actor distinguishes conditions such as:

```text
COMPLETE
PARTIAL
BLOCKED
CAPPED
UNKNOWN
```

instead of collapsing every condition into a successful empty Dataset.

This is especially important when the output is consumed automatically.

***

## 🔌 API & integrations

Zillow Scraper can be used programmatically through the **Apify API**.

You can connect Dataset results to workflows such as:

- Google Sheets
- Make
- Zapier
- CRMs
- Internal APIs
- Data warehouses
- Property-analysis pipelines
- Lead and acquisition systems
- AI agents
- MCP workflows

Every run stores structured output in the default Dataset, making the results easy to consume from another application.

***

## 💡 Example use cases

### 🏠 Property listing research

Search one or more markets and collect:

```text
ZPID
Address
Status
Price
Beds
Baths
Square feet
Property type
Coordinates
Source query
```

Use the results for property analysis, market research, or internal data pipelines.

***

### 📉 Price-change monitoring

Schedule the Actor with:

```text
monitorMode: true
monitorId: your-stable-monitor-id
```

to identify:

```text
New listings
Price cuts
Price increases
Status changes
Back-on-market events
```

without manually comparing Zillow pages.

***

### 📊 Data-quality monitoring

Use `COVERAGE` records to determine:

```text
Was the search complete?
Was the run capped?
Were requests blocked?
How many unique ZPIDs were found?
How many detail requests succeeded?
```

This is useful before feeding Zillow data into automated decisions.

***

### 🧭 Multi-query reconciliation

Run overlapping searches such as:

```text
Austin, TX
78747
South Austin, TX
```

and use ZPID reconciliation plus matched-query provenance to see which properties overlap across scopes.

***

### ⚙️ CRM and automation triggers

With:

```text
emitOnlyChanges: true
```

monitor runs can focus on machine-readable change events suitable for downstream alerts and workflows.

***

## ⚠️ Current limitations

The current version focuses on **public Zillow listing discovery, reconciliation, coverage diagnostics, and monitoring**.

Important limitations:

```text
Zillow may expose fewer results than requested
Some public fields may be null
Search completeness depends on Zillow response behavior and configured limits
Interactive challenges can still block a request
A blocked or partial scan is not automatically treated as a confirmed removal
V1 normally uses one partition per input search
Lifecycle state does not create a separate historical Dataset for every prior observation
```

The Actor does **not**:

```text
Log into private Zillow accounts
Scrape private/account-only property data
Bypass interactive CAPTCHA or human-verification challenges
Invent missing fields
Pretend incomplete scans are complete
```

***

## ❓ FAQ

### How does Zillow Scraper work?

The Actor accepts locations, search URLs, property URLs, or ZPIDs.

It then:

```text
Builds discovery targets
Fetches public Zillow pages
Parses Zillow search/property data
Normalizes property observations
Reconciles by ZPID
Preserves matched-query provenance
Optionally enriches property details
Calculates coverage diagnostics
Compares persistent monitor state when enabled
Emits Dataset records
```

***

### Can I scrape multiple locations in one run?

**Yes.**

Add multiple values to:

```text
locations
```

Each location is treated as its own discovery scope.

***

### Can I provide Zillow search URLs directly?

**Yes.**

Use:

```text
searchUrls
```

This is useful when you already have a specific Zillow search page you want to process.

***

### Can I scrape one property directly?

**Yes.**

Use either:

```text
propertyUrls
```

or:

```text
zpids
```

Direct targets receive their own coverage evidence.

***

### Why is the canonical URL sometimes shorter than the original Zillow URL?

The Actor reconciles property identity around the ZPID.

A normalized canonical URL can therefore look like:

```text
https://www.zillow.com/homedetails/123456789_zpid/
```

while `sourceUrl` preserves the actual page used as evidence.

***

### Why are some fields null?

Zillow does not expose every field consistently for every property or response type.

The Actor deliberately uses:

```text
null
```

when a value is unavailable instead of fabricating data.

***

### What does `CAPPED` mean?

`CAPPED` means the configured item limit was reached before the Actor could prove that the discovery scope was fully exhausted.

Example:

```text
Zillow exposed 41 unique ZPIDs
maxTotalItems was 25
```

The Actor can return 25 records while clearly reporting:

```text
coverageStatus: CAPPED
```

This is intentional.

***

### What does `PARTIAL` mean?

`PARTIAL` means the Actor obtained some evidence but could not prove complete coverage.

Possible causes can include:

```text
Request failure
Parse failure
Incomplete detail enrichment
Other incomplete discovery evidence
```

Check:

```text
coverageReasons
```

for the exact reason.

***

### What happens if Zillow blocks a request?

The Actor records the block explicitly.

Depending on the affected scope, you may see:

```text
blockedRequests > 0
coverageStatus: BLOCKED
errorCode: BLOCKED
```

The Actor does not silently convert a blocked page into zero listings.

***

### How does monitoring avoid false removals?

The Actor checks coverage evidence before treating a missing property as confirmed absent.

Reliable coverage can produce:

```text
NOT_SEEN_CONFIRMED
```

Incomplete coverage can produce:

```text
NOT_SEEN_PARTIAL_SCAN
```

This prevents a blocked, capped, or failed scan from automatically becoming a fake removal event.

***

### What should I use as `monitorId`?

Use a stable name that identifies the monitoring job.

Examples:

```text
austin-homes-watch
phoenix-rentals-monitor
client-42-acquisition-list
```

Reuse the same ID on future runs to continue the same monitor history.

***

### Can I output only changes?

**Yes.**

Use:

```text
emitOnlyChanges: true
```

This is useful for recurring automations where unchanged `CURRENT` rows are not needed.

***

### Can I export the results?

Yes.

Apify Dataset results can be exported in supported formats such as:

```text
JSON
CSV
Excel
XML
HTML
```

***

### Can I use the Actor through an API?

Yes.

Every Actor run can be started through the **Apify API**, and Dataset results can be retrieved programmatically.

This makes Zillow Scraper suitable for ETL, CRM, monitoring, and automation pipelines.

***

## 🧭 Recommended workflow

For normal one-time property discovery:

```text
1. Add locations or Zillow search URLs
2. Set your item limits
3. Choose listing filters
4. Enable coverage diagnostics
5. Use selective detail enrichment when needed
6. Run the Actor
7. Review CURRENT and COVERAGE records
```

For recurring monitoring:

```text
1. Create a stable monitorId
2. Keep the same discovery inputs across runs
3. Enable monitorMode
4. Keep coverage diagnostics enabled
5. Use new_or_changed_only detail strategy
6. Schedule recurring Actor runs
7. Route CHANGE_EVENT records to alerts or automation
```

For stronger trust:

```text
Do not rely only on listing rows
+
Check COVERAGE records
+
Review DATA_CONFLICT events
+
Treat CAPPED/PARTIAL/BLOCKED scopes differently from COMPLETE scopes
```

***

## 💬 Feedback & support

Found a bug or have a feature request?

Use the Actor's **Issues** tab on Apify.

Useful future additions may include:

```text
More advanced partitioning for very large markets
Additional lifecycle analytics
More selective enrichment strategies
Dedicated historical event storage
Additional Zillow public fields
More market-level monitoring summaries
```

***

### 🚀 Zillow listing data you can audit

Enter locations, Zillow URLs, or ZPIDs, run the Actor, and receive **structured Zillow property data** with **ZPID reconciliation, listing-change events, lifecycle tracking, provenance, and coverage evidence** ready for **research, APIs, CRM workflows, ETL, and automation**.

# Actor input Schema

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

Cities, ZIP codes, neighborhoods, or other Zillow search locations.

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

Zillow search URLs. Supported Actor filters override equivalent URL filters.

## `propertyUrls` (type: `array`):

Direct public Zillow property URLs.

## `zpids` (type: `array`):

Direct Zillow property IDs.

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

Maximum accepted listing observations from each location or search URL before that search is marked capped by the user limit.

## `maxTotalItems` (type: `integer`):

Global maximum accepted listing observations across direct targets and all searches in this run.

## `listingStatus` (type: `string`):

Status filter applied to supported Zillow search state. Choose Preserve URL/default status to avoid overriding the URL's status state.

## `propertyTypes` (type: `array`):

Optional Zillow property-type filters. Multiple types can be selected.

## `minPrice` (type: `number`):

Optional minimum listing price filter.

## `maxPrice` (type: `number`):

Optional maximum listing price filter.

## `minBeds` (type: `number`):

Optional minimum bedroom count filter.

## `maxBeds` (type: `number`):

Optional maximum bedroom count filter.

## `minBaths` (type: `number`):

Optional minimum bathroom count filter.

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

Allow Zillow property-detail requests after search-card reconciliation.

## `detailStrategy` (type: `string`):

Choose whether details are skipped, fetched for all canonical properties, or fetched only for new/changed/insufficient records.

## `maxDetailFetches` (type: `integer`):

Maximum Zillow property-detail requests allowed in one run.

## `deduplicateByZpid` (type: `boolean`):

Return one canonical CURRENT row per ZPID. Monitoring state is always canonical even when this is disabled for public output.

## `preserveMatchedQueries` (type: `boolean`):

Preserve the searches and search URLs that observed each canonical ZPID.

## `includeCoverageReport` (type: `boolean`):

Emit COVERAGE records describing caps, blocks, request failures, detail success, and scan completeness.

## `monitorMode` (type: `boolean`):

Compare this run with persistent monitor state and emit evidence-backed change/lifecycle records.

## `monitorId` (type: `string`):

Stable ID used to load and update the same listing-history state across scheduled runs.

## `emitOnlyChanges` (type: `boolean`):

In monitor mode, omit CURRENT and LIFECYCLE rows and emit change events plus requested coverage diagnostics.

## `retainEventHistory` (type: `boolean`):

Retain lifecycle status transitions in persistent monitor state. This does not create a separate historical event Dataset in V1.

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

Optional Apify proxy configuration. Blocks are reported as partial coverage; the Actor does not bypass interactive challenges.

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

Maximum number of search-target collectors allowed to run concurrently.

## `maxRetries` (type: `integer`):

Maximum retries for retryable Zillow HTTP failures.

## `requestDelayMs` (type: `integer`):

Base delay used between retry attempts and throttled requests.

## Actor input object example

```json
{
  "locations": [
    "New York, NY"
  ],
  "searchUrls": [],
  "propertyUrls": [],
  "zpids": [],
  "maxItemsPerLocation": 5,
  "maxTotalItems": 5,
  "listingStatus": "for_sale",
  "propertyTypes": [],
  "includeDetails": true,
  "detailStrategy": "none",
  "maxDetailFetches": 250,
  "deduplicateByZpid": true,
  "preserveMatchedQueries": true,
  "includeCoverageReport": true,
  "monitorMode": false,
  "emitOnlyChanges": false,
  "retainEventHistory": true,
  "maxConcurrency": 8,
  "maxRetries": 3,
  "requestDelayMs": 250
}
```

# Actor output Schema

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

Default Dataset containing CURRENT, CHANGE\_EVENT, LIFECYCLE, COVERAGE, and ERROR records.

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {
    "locations": [
        "New York, NY"
    ],
    "maxItemsPerLocation": 5,
    "maxTotalItems": 5,
    "includeDetails": false,
    "detailStrategy": "none",
    "maxDetailFetches": 0,
    "monitorMode": false
};

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

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

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

```

## Python example

```python
from apify_client import ApifyClient

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

# Prepare the Actor input
run_input = {
    "locations": ["New York, NY"],
    "maxItemsPerLocation": 5,
    "maxTotalItems": 5,
    "includeDetails": False,
    "detailStrategy": "none",
    "maxDetailFetches": 0,
    "monitorMode": False,
}

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

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

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

```

## CLI example

```bash
echo '{
  "locations": [
    "New York, NY"
  ],
  "maxItemsPerLocation": 5,
  "maxTotalItems": 5,
  "includeDetails": false,
  "detailStrategy": "none",
  "maxDetailFetches": 0,
  "monitorMode": false
}' |
apify call searchshark/zillow-listing-scraper --silent --output-dataset

```

## MCP server setup

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