Zillow Property Listings Scraper avatar

Zillow Property Listings Scraper

Pricing

from $1.00 / 1,000 results

Go to Apify Store
Zillow Property Listings Scraper

Zillow Property Listings Scraper

Scrape Zillow search results by city, region or ZIP: about 41 listings per location with zpid, numeric price plus the formatted priceText, beds, baths, areaSqft, address split into street, city, state and zipcode, status, brokerName, photo and exact coordinates. For sale, for rent or sold. No login.

Pricing

from $1.00 / 1,000 results

Rating

0.0

(0)

Developer

String

String

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

14 days ago

Last modified

Share

What does the Zillow Property Listings Scraper do?

Give this Actor a list of US locations — Austin, TX, Denver, CO, a ZIP code, a neighbourhood name — and it returns the listings on Zillow's search results for each one as flat rows. One Zillow request per location, around 41 listings back from each. Prices, bed and bath counts, floor area and coordinates come back as numbers rather than as display strings, because the Actor reads the __NEXT_DATA__ blob Zillow's own page hydrates from instead of parsing the rendered cards.

  • For-sale, for-rent and recently sold inventory from the same input, switched with listingType
  • Numeric price alongside Zillow's formatted priceText, so you can sort without cleaning first
  • latitude and longitude on every row, ready to plot without a geocoding step
  • zpid on every row, which is Zillow's stable listing key and the input the Zillow Property Detail Scraper takes
  • No Zillow account, cookie or API key anywhere in the run

This is the search-sweep half of a pair. It finds listings across a market but returns what a search result card carries. For one home's full record — year built, lot size, home type and the listing text — feed the zpid values it produces into the Zillow Property Detail Scraper.

What data does it extract?

FieldTypeDescription
zpidstringZillow's own listing ID, stable across runs. Use it to de-duplicate and as detail-scraper input
addressstringThe full one-line address as Zillow writes it
streetstringStreet line on its own
citystringCity
statestringTwo-letter state code
zipcodestringZIP code
pricenumber500000, not "$500,000"
priceTextstringThe formatted price exactly as the card shows it, including /mo on rentals
bedsnumberBedroom count
bathsnumberBathroom count
areaSqftnumberFloor area in square feet
statusstringZillow's own status type, e.g. FOR_SALE, FOR_RENT, SOLD
latitudenumberDecimal degrees
longitudenumberDecimal degrees
brokerNamestringListing broker, where the card names one
detailUrlstringLink to the property's Zillow page
imageUrlstringPrimary listing photo
searchLocationstringThe location string from your input that produced this row
listingTypestringfor_sale, for_rent or sold, echoed back from the input
sourceUrlstringThe Zillow search URL the row was read from
collectedAtstringISO timestamp of the fetch

Any value Zillow does not publish on a card arrives as null rather than as an empty string or a zero, so a missing bath count is never mistaken for a studio.

Why scrape Zillow?

  • Comps and pricing. Pull sold rows for a ZIP, keep price, beds, baths and areaSqft, and you have a price-per-square-foot distribution for the submarket in one run.
  • Rental yield. Run the same location twice, once as for_sale and once as for_rent, and compare asking rents against asking prices on comparable bed counts.
  • Agent and iBuyer lead lists. brokerName plus the address fields is enough to rank brokers by active inventory in a market.
  • Maps and dashboards. Coordinates are on the row already, so inventory goes onto a map without a geocoding bill.
  • Days-on-market and price-cut tracking. Schedule the Actor on a market, key rows by zpid, and diff price between runs.

How to use it

  1. Open the Actor and click Try for free, or add it to your Apify account.
  2. Put your markets in the Locations field, one per line. Austin, TX and 78704 both work.
  3. Pick a Listing type. Leave it at for_sale, or switch to for_rent or sold.
  4. Set Maximum results if you want a hard ceiling on the dataset, and raise Concurrency if you are running many locations and want them fetched in parallel.
  5. Click Start. Each location is one fetch, so a ten-location run finishes in seconds.
  6. Take the rows from the Dataset tab, or export them as JSON, CSV or Excel. The run's SUMMARY record in the key-value store lists any location that failed, with its error.

To keep a market file current, save the input as a task and schedule it. Rows carry collectedAt, so successive runs stack into a time series without any extra bookkeeping.

Input

FieldTypeDefaultDescription
locationsarray of stringsrequiredCities, regions or ZIP codes, e.g. "Austin, TX". Between 1 and 100 entries. Duplicates collapse to one fetch
listingTypestringfor_saleOne of for_sale, for_rent, sold
maxItemsinteger1000Ceiling on dataset rows, 1 to 50000
concurrencyinteger2Locations fetched in parallel, 1 to 5
{
"locations": ["Austin, TX", "Denver, CO", "78704"],
"listingType": "for_sale",
"maxItems": 1000,
"concurrency": 2
}

Output

One row per listing. The shape below is illustrative — it shows the fields and their types, not a captured run.

{
"zpid": "29502073",
"address": "6310 Needham Ln, Austin, TX 78739",
"street": "6310 Needham Ln",
"city": "Austin",
"state": "TX",
"zipcode": "78739",
"price": 1200000,
"priceText": "$1,200,000",
"beds": 5,
"baths": 4,
"areaSqft": 3496,
"status": "FOR_SALE",
"latitude": 30.192585,
"longitude": -97.89119,
"brokerName": "Compass RE Texas, LLC",
"detailUrl": "https://www.zillow.com/homedetails/6310-Needham-Ln-Austin-TX-78739/29502073_zpid/",
"imageUrl": "https://photos.zillowstatic.com/fp/a1b2c3d4e5f60718293a4b5c6d7e8f90-p_e.jpg",
"searchLocation": "Austin, TX",
"listingType": "for_sale",
"sourceUrl": "https://www.zillow.com/homes/for_sale/Austin,-TX_rb/",
"collectedAt": "2026-08-21T09:14:03.512Z"
}

Reliability

Measured across 30 US cities in one run: 30 of 30 locations succeeded and 1,230 listings came back, with every field in the schema populated by at least some rows. No column is always empty.

Reading the page's own state blob is what makes that possible. The rendered Zillow search page is around 650KB, well past what a model-backed extractor will accept, and the numbers it shows are formatted for display. The blob carries the same results with prices and areas already as integers.

A location that cannot be read is recorded in the run's SUMMARY under failures rather than quietly returning fewer rows, and a run in which every location failed exits with an error instead of reporting success on an empty dataset.

There are no retries by design. The String Unblocker behind this Actor owns proxy rotation, session reuse and anti-bot solver selection, so a retry loop out here would re-roll the same block and spend your budget doing it.

Limitations

One search page per location, so this samples a market rather than crawling it end to end. Widen coverage by listing more ZIP codes rather than expecting deeper paging from a single city. Detail pages are not opened, so tax history, price history, Zestimate detail, schools and photo galleries are out of scope. Only zillow.com is targeted, which means US listings only.

How much does it cost?

Pricing is per event: one charge for each result row written to the dataset. A location that fails produces no rows and costs you nothing, and the run never writes a row it could not charge for. The current rate is shown in the pricing panel at the top of this page.

Runs started from an Apify free plan stop at 250 requests and 250 results, and the run says so in its status message. Any paid plan runs the full input and whatever maxItems you set. The cap exists because this Actor fetches through String's own infrastructure, which Apify does not reimburse on free-plan runs; it binds on requests as well as rows so that a long input list cannot spend those fetches on rows the run will not return.

Using it with the Apify API

import { ApifyClient } from "apify-client";
const client = new ApifyClient({ token: "<YOUR_APIFY_TOKEN>" });
const run = await client.actor("usestring/zillow-listings").call({
locations: ["Austin, TX", "Denver, CO"],
listingType: "for_sale",
maxItems: 1000
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, items[0]);
const summary = await client.keyValueStore(run.defaultKeyValueStoreId).getRecord("SUMMARY");
console.log(summary?.value);

The Python client and the plain REST endpoints take the same input object; Apify's API documentation has the equivalents.

This Actor reads the public search results page a logged-out visitor sees. There is no account, no session cookie, no paywall and nothing behind a login. Scraping public web pages is broadly lawful in the US and the EU, and courts have repeatedly declined to treat reading a public page as unauthorised access.

A listing row is property data. The only person a row can name is the listing broker, which Zillow publishes as business contact information; no buyer, seller or tenant details are collected. What you do with the data afterwards is your responsibility, and republishing listing content verbatim raises copyright questions that reading the data for analysis does not. None of this is legal advice, so talk to a lawyer if your use goes beyond internal analysis.

FAQ

Do I need a Zillow account, API key or cookies? No. The Actor reads the public search page as a logged-out visitor and never signs in.

How many listings do I get per location? Around 41, which is one Zillow search page. Ten locations therefore return roughly 410 rows for ten requests. There is no deeper paging, so more coverage means more locations, not a bigger page.

Can I get rentals and sold homes? Yes. Set listingType to for_rent or sold. The default is for_sale.

Is price a number or a string? A number. priceText carries Zillow's formatted version next to it, and beds, baths, area and coordinates are numbers too.

How do I de-duplicate across runs? Key on zpid. It is Zillow's own identifier, it is stable, and it is also what the Zillow Property Detail Scraper accepts as input.

Does it work outside the US? No. It targets zillow.com, so US listings only. There is no country or language input.

What happens if one location fails? The rest of the batch still returns. The failed location and its error are written to the run's SUMMARY record, and only a run where every location failed exits with an error.

How fresh is the data? Every row is stamped with collectedAt and reflects what Zillow was showing at that moment. Schedule the run to build a series.

Feedback

Found a field parsed wrong, a market that fails, or something missing from the schema? Open an issue from the Issues tab on this Actor's Store page. A report that includes the input which reproduces the problem gets fixed fastest.