Zillow scraper: export every listing in a ZIP code to CSV with price and Zestimate
Investors, agents writing market reports and rent researchers need every listing of an area in one table, and zillow.com has no export.
arcadium/zillow-properties searches the places you give it (cities, ZIP codes, counties or Zillow search URLs) and returns one row per home: price, Zestimate, rent Zestimate, beds, baths, square feet, home type, days on market, the last price cut and the brokerage. Agents’ names, phones and emails are never returned.
Cost of this example on the Apify Free plan: 400 × $0.0021 = $0.84 ($0.48 on the Apify Gold plan and higher), plus $0.001 per GB of memory when the run starts.
What you need
- An Apify account and API token.
- The ZIP codes or places to cover, for example
78704.
Step 1: Run it in the Apify Store (no code)
- Open arcadium/zillow-properties on the Apify Store and sign in (a free Apify account works).
- Type the ZIP codes or places into Locations, one per line (for example
78704orAustin, TX). - Choose
for_sale,for_rentorsoldunder Listing type. - Set Max properties per search, for example 200 per ZIP code. Larger areas are split into map sections automatically.
- Optional: set a maximum cost per run in the run options as a hard cap.
- Click Start. Rows appear in the Output tab; export them as JSON, CSV or Excel.
The same input as JSON (paste it in the JSON tab of the input form):
{
"locations": [
"78704",
"78745"
],
"listingType": "for_sale",
"sortBy": "newest",
"maxResultsPerSearch": 200
}
Step 2: Call it over the Apify API
One HTTP call runs the actor and returns its rows. It suits runs up to about five minutes; for longer runs start the run asynchronously and read the dataset when it finishes.
curl -X POST "https://api.apify.com/v2/acts/arcadium~zillow-properties/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"locations":["78704","78745"],"listingType":"for_sale","sortBy":"newest","maxResultsPerSearch":200}'
Step 3: Run it from Python
With apify-client 3.x, call() returns a Run object: read the dataset id as run.default_dataset_id (older examples use run["defaultDatasetId"], which no longer works).
# pip install "apify-client>=3"
import csv, os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("arcadium/zillow-properties").call(run_input={
"locations": ["78704", "78745"], "listingType": "for_sale",
"sortBy": "newest", "maxResultsPerSearch": 200,
})
rows = list(client.dataset(run.default_dataset_id).iterate_items()) # apify-client 3.x
keys = ["propertyId", "address", "zipcode", "price", "zestimate", "rentZestimate", "beds", "baths",
"livingArea", "homeType", "daysOnMarket", "priceChange", "priceChangeDate", "brokerName", "url"]
with open("zillow-listings.csv", "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=keys, extrasaction="ignore")
w.writeheader()
w.writerows(r for r in rows if r.get("rowType") == "property")
for e in (r for r in rows if r.get("rowType") == "error"):
print("could not read:", e.get("input"), e.get("code"), e.get("message"))
Step 4: Run it from JavaScript
// npm install apify-client
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('arcadium/zillow-properties').call({
"locations": [
"78704",
"78745"
],
"listingType": "for_sale",
"sortBy": "newest",
"maxResultsPerSearch": 200
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
Step 5: Ask an AI assistant (MCP)
Apify’s hosted MCP server turns the actor into one tool. Add it to Claude, Cursor, VS Code or any MCP client:
{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com/?tools=arcadium/zillow-properties",
"headers": {
"Authorization": "Bearer <APIFY_TOKEN>"
}
}
}
}Then ask, for example:
Use arcadium/zillow-properties to list up to 100 homes for sale in ZIP code 78704. Give me the median price, the median price per square foot and the homes with a price cut.
More clients and the local setup on the MCP page.
Price drops every day
Save the input as a Task and add a daily Schedule. propertyId (the zpid) is the stable key across runs: compare price and priceChange per propertyId to list new price cuts, and new ids to list new listings. Add "daysOnZillow": "1" to read only the homes listed in the last day.
Sold prices, price history and taxes
For recently sold homes set "listingType": "sold" and "includeDetails": true: each row then also has lastSoldPrice, yearBuilt, priceHistory, taxHistory and schools. Detail rows are charged as Property with details instead of the search row, never both.
{
"locations": [
"78704"
],
"listingType": "sold",
"includeDetails": true,
"maxResultsPerSearch": 100
}
Output fields
Every row has a rowType; filter on it first. Failed inputs come back as free rows with a code and message. The fields this tutorial uses, from the actor’s dataset schema:
| Field | What it holds |
|---|---|
rowType | Kind of row. Filter on this first. 'property': one Zillow property (charged as 'property', or 'property-details' when detailed is true). 'error': an input could not be read; see code and message (free). |
propertyId | Zillow property id (zpid), the number before _zpid in the property URL. Unique per run. |
address | Full address line, e.g. '3207 Wickham Ln, Austin, TX 78725'. |
zipcode | ZIP code of the property address. |
price | List price in USD; monthly rent for rentals; null for sold homes in search rows (see lastSoldPrice on detail rows). |
zestimate | Zillow's estimated market value in USD (Zestimate). |
rentZestimate | Zillow's estimated monthly rent in USD. |
beds | Number of bedrooms. |
baths | Bathrooms (half baths count 0.5). |
livingArea | Living area in square feet. |
homeType | SINGLE_FAMILY, CONDO, TOWNHOUSE, MULTI_FAMILY, APARTMENT, LOT, MANUFACTURED, ... |
daysOnMarket | Days on Zillow. |
priceChange | Last price change in USD (negative = price cut). |
brokerName | Listing brokerage (company). Agent names, phones and emails are never included. |
searchInput | The location or search URL this row was found by (null for property URLs). |
All fields: Zillow Scraper guide.
Cost per run
Pay per event: you pay for the rows delivered, at the actor’s live Apify price on 2026-11-02. Error, coverage and summary rows are free.
| What | Rows | Price per row (Free plan) | Subtotal |
|---|---|---|---|
| Property: 2 ZIP codes x up to 200 homes | 400 | $0.0021 | $0.84 |
Cost of this example on the Apify Free plan: 400 × $0.0021 = $0.84 ($0.48 on the Apify Gold plan and higher), plus $0.001 per GB of memory when the run starts. Higher Apify plans pay less per row. The Store page always has the current price.
Limitations
maxResultsPerSearchis a cap: a ZIP code with fewer listings returns fewer rows, and you pay only for the rows you get. A home found by two of your searches in one run is charged once.zestimateandrentZestimateare empty when Zillow shows no estimate for the home.- Zillow lists at most 820 homes per query; larger areas are split into map sections automatically.
- Sold homes often have
price: nullin search rows; useincludeDetailsforlastSoldPrice.
Links
- Zillow Scraper: fields, price table and sample output
- How to use Zillow Scraper: every input field, n8n, LangChain
- arcadium/zillow-properties on the Apify Store
- All tutorials