Zillow Scraper: Sale, Rent and Sold Property Listings
Pricing
from $1.00 / 1,000 results
Zillow Scraper: Sale, Rent and Sold Property Listings
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.
Pricing
from $1.00 / 1,000 results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
6 days ago
Last modified
Categories
Share
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 |
heroImage / photos | property photo URL(s) |
description | listing description text (property page only) |
yearBuilt | 1995 (property page only) |
monthlyHoaFee / propertyTaxRate | 60 / 1.9 (property page only) |
mlsId / mlsName | "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" |
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
- Pick a mode:
search(build a search from a location and filters) orurl(paste links you already have). - In search mode, add one or more locations and set the channel and any filters. In URL mode, paste search or property links.
- Turn on Read the property page for each result if you need the description, year built, HOA fee, MLS id, agent and brokerage.
- 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:
{"mode": "search","locations": ["Austin, TX"],"listingType": "FOR_SALE","maxItems": 200}
Search several places for rentals under a price, with filters:
{"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:
{"mode": "search","locations": ["78759"],"listingType": "SOLD","daysOnZillow": "12_MONTHS","fetchDetails": true,"maxItems": 100}
Paste links you already have:
{"mode": "url","startUrls": ["https://www.zillow.com/homes/for_sale/Austin,-TX/","https://www.zillow.com/homedetails/00000001_zpid/"],"maxItems": 50}
Run it from your code
Python:
from apify_client import ApifyClientclient = 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:
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 classifiedNEW,UPDATED(withchangedFields),UNCHANGED(suppressed and not billed unlessemitUnchangedis on),REAPPEAREDorEXPIRED(only after a run scanned the whole search end to end, and only withemitExpired).stateKeynames or shares the stored state. With incremental mode off, output is exactly as before. - 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 asUPDATED(with every field inchangedFields) 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 to0, or comfortably above the area's normal listing count, wheneverincrementalModeis on; keep a tight cap for one-off searches. - Tip: an
emitExpiredbatch is not capped by Max results. Once a run has scanned its tracked search end to end, every property that is no longer found is pushed and billed asEXPIREDin that same run, regardless of the Max results setting. On a large tracked search this can be a bigger batch than you expect the first time a lot of inventory turns over at once; turnemitExpiredoff 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.
- Authorize a connector once under Apify, Settings, Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto 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 | sample | 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. |
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.
{"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
Runs on any Apify plan. The prefilled Connection setting works out of the box, and the actor rotates connections automatically when one is refused. That prefilled setting is billed by traffic on top of the run itself; you can supply your own connection URLs under Connection instead.
FAQ
How much does it cost?
You pay per result returned, with the property page read billed only when you switch it on. 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, every previously tracked property no longer found is returned as EXPIRED in that same run, and this batch is not limited by Max results. A large one-time swing in local inventory can therefore produce a bigger EXPIRED batch 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 a clear message so "no listings matched" is never confused with "nothing could be read". Run it again in a few minutes. 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:
| ๐ Redfin Scraper Scrape redfin.com home listings: price, beds, baths, sqft, lot, year built, coordinates... | ๐ Realtor.com From $0.9/1K. Extract property listings from realtor.com. Get comprehensive data... |
| ๐งฉ Homes.com Scraper Scrape US property records from Homes.com. Pick a city, state or ZIP and a channel (for... | ๐ Trulia Scraper Scrape Trulia.com property listings by location, filters, or URL. Extract prices, beds... |
| ๐งฉ Propwire Scraper Scrape Propwire.com: 157M+ US MLS & off-market properties with owner names, mailing... | ๐ Homes.co.nz Scraper Scrape Homes.co.nz listings by location or URL with pagination. Extract property details... |
๐ Browse all abotapi scrapers
๐ฌ Support & custom scrapers
- ๐ Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- ๐ ๏ธ Need another site, extra fields or a private build? Email abotapi@proton.me or message Telegram @abotapi.
- โญ Enjoying it? A quick review on the actor page helps other users find it.