Zillow scraper tutorial: extract property data with Python

Zillow sits on one of the richest real estate datasets on the internet. Prices, Zestimates, days on market, price history, agent contacts. If you work in real estate analytics, lead generation, маркетинговое исследование, or just want to track home values in your neighborhood, that data is worth having in a spreadsheet instead of a browser tab.
The problem is that Zillow does not want you taking it programmatically. The site is built to detect automated traffic, and it blocks aggressively. In this guide you will build a working Python scraper, understand exactly why Zillow fights back, and learn what it actually takes to keep a scraper running past the first few requests.
By the end you will have a working Zillow scraper Python script that pulls property title, address, price, beds, baths, square footage, and listing URL, and writes everything to a CSV file you can open in Excel or load into pandas. If you’ve been searching for how to scrape Zillow data or a straightforward Zillow web scraper example, this is built to be that starting point.
What is a Zillow scraper?
A Zillow scraper is a script or program that automatically visits Zillow pages, reads the listing data embedded in the page, and saves it somewhere useful, usually a CSV file, a database, or a spreadsheet.
Instead of manually copying addresses and prices from search results, a scraper does it in seconds and can repeat the process daily, hourly, or on whatever schedule you set.
People build Zillow scrapers for a lot of reasons:
- Real estate investors tracking price drops and new listings in specific ZIP codes
- Data analysts building housing market models or trend reports
- Lead generation agencies compiling contact lists from agent listings
- Proptech startups feeding listing data into their own apps
- Researchers and students studying housing affordability or price trends
- Разработчики building rental or price-comparison tools
None of these use cases require hacking anything. It is public listing data. The challenge is technical, not conceptual: getting the page content reliably without Zillow’s anti-bot systems shutting you out.
Can you scrape Zillow?
Can you scrape Zillow, and is it legal to scrape Zillow? These are the questions everyone asks before writing a single line of code, so let’s cover them honestly.
Zillow’s Terms of Use prohibit automated data collection from their site without permission. Their robots.txt file also restricts crawling on large portions of the site. That means scraping Zillow is against their platform rules, even though the underlying listing data (address, price, square footage) is generally considered public information under US law.
Web scraping law depends on jurisdiction, what data you collect, how you use it, and whether you access anything behind a login. If you are scraping for a business use case, especially one involving personal data like agent contact details, talk to a lawyer before you scale it up.
What tends to separate a reasonable, low-risk scraping project from a reckless one:
- Check robots.txt before you start, and understand which paths are disallowed for crawlers
- Only collect public data visible without logging in
- Don’t hammer the server. Add delays between requests instead of firing hundreds per minute
- Don’t republish Zillow’s proprietary data like Zestimate as your own product
- Respect personal data rules if you’re collecting agent names, emails, or phone numbers, especially under GDPR or CCPA
- Use the data for personal analysis or internal research rather than building a competing public listing site
Plenty of people scrape Zillow for personal projects, coursework, and internal market research every day.
What data can you scrape from Zillow?
Zillow listing pages and search results carry a lot of structured data. Here’s what’s typically available on the front end:
| Data field | Where it appears | Заметки |
| Адрес | Search results, listing page | Usually full street address |
| Цена | Search results, listing page | Current asking price |
| Zestimate | Listing page | Zillow’s own valuation, not the same as list price |
| Bedrooms / bathrooms | Search results, listing page | Numeric values |
| Square footage | Search results, listing page | Living area, not lot size |
| Lot size | Listing page | Sometimes missing for condos/apartments |
| Listing status | Search results | For sale, pending, sold, off market |
| Property images | Listing page | Image URLs, subject to copyright |
| Listing URL | Search results | Direct link to the property page |
| История цен | Listing page | Not always present, depends on listing |
| Agent name and brokerage | Listing page | Contact details count as personal data |
| Days on Zillow | Search results, listing page | Useful for tracking stale listings |
For this tutorial, we’ll focus on the fields most people actually need: title, address, price, beds, baths, area, and listing URL. Once the scraper works for those, adding a couple more fields is a small change, not a rewrite. A Zillow property data scraper that handles this core set can usually be extended into a full Zillow listings scraper just by adding new keys to the parsing step.
Build a Zillow scraper in Python
This is the core of the guide. We’ll go from an empty folder to a working scraper that exports a CSV, step by step.
Prerequisites
You’ll need:
- Python 3.9 or newer
- Basic familiarity with the terminal
- A code editor (VS Code works fine)
- About 20 minutes
Installing dependencies
We’re keeping the stack lightweight: requests for HTTP calls and beautifulsoup4 for parsing. Install both in a virtual environment so they don’t collide with other projects.
lxml is optional but it parses HTML faster than Python’s built-in parser, and Zillow’s pages are large.
Project setup
Create a single file to start with. Once it works, you can split it into modules.
Inspecting Zillow pages
Open a Zillow search results page in your browser, right-click, and choose “View Page Source”(or Ctrl+U for Windows). Search for __NEXT_DATA__. You’ll find a large <script> tag containing a JSON blob with the same listing data rendered on the page.

This matters because it means you don’t need to hunt through dozens of nested <div> tags with fragile CSS classes. You can pull the JSON directly and read clean, structured fields from it. This is a common pattern on modern React-based sites, and it’s far more stable than scraping visible HTML, since visual layout changes more often than the underlying data structure.
Zillow updates its page structure periodically. The exact script tag ID or JSON shape shown here may shift over time. If the scraper stops finding data, re-inspect the page source and adjust the parsing logic to match what’s currently there.
Complete Python scraper
Here’s the full script. We’ll break down each part afterward.
Code explanation
Here’s what each piece is actually doing.
Headers. Зона build_headers() function rotates between a small pool of realistic browser user agents and sets standard headers like Accept-Language. A request with no user agent, or an obviously fake one like python-requests/2.31, is one of the fastest ways to get flagged.
Retries with backoff. fetch_page() retries failed requests with exponential backoff plus a small random jitter. This avoids hammering Zillow’s servers with retries at the exact same interval, which itself is a detectable pattern.
Pulling the JSON payload. extract_next_data() looks for the <script id=”__NEXT_DATA__”> tag and parses its contents as JSON. This is far more reliable than scraping visible text out of styled <div> elements, because CSS class names change constantly while the underlying data shape is more stable.
Recursive search for listings. parse_listings() uses a recursive helper, find_results(), to walk the JSON tree and find the listResults array wherever it happens to sit. Zillow’s JSON structure has nested search state a few levels deep, and hardcoding one exact path tends to break the first time they change anything upstream.
Dataclass for structure. Зона Listing dataclass keeps each row’s shape consistent and makes it trivial to convert to a dictionary for CSV export with asdict().
CSV export. export_to_csv() writes every listing out using csv.DictWriter, with the field names pulled automatically from the first listing’s dataclass keys.
Exporting data
Running the script produces a zillow_listings.csv file in your project folder. Each row represents one property, ready to open in Excel, Google Sheets, or load into a pandas DataFrame with pd.read_csv(“zillow_listings.csv”) для дальнейшего анализа.
Example output
Improvements
The script above is intentionally minimal so you can see how every piece works. Once it’s running, here’s where to take it next:
- Pagination. Loop through search result pages by adjusting the URL’s page parameter and stop when listResults comes back empty.
- Individual listing pages. Extend the scraper to visit each listing_url and pull Zestimate, price history, and agent details from the full property page’s own __NEXT_DATA__ block.
- Proxy rotation. Pass a fresh proxy into fetch_page() on every request instead of reusing one IP for the whole run. We’ll cover exactly why in the next sections.
- A real browser engine. If Zillow starts serving a JavaScript challenge before the JSON payload loads, swap requests for Playwright, which renders the page like a real browser before you extract the data.
- Structured logging. Log every failed request with its status code and URL so you can spot patterns, like a specific ZIP code or page range that triggers more blocks than others.
Common errors and what they mean
- 403 Запрещено
Zillow flagged the request as automated. Usually a missing or suspicious user agent, or too many requests from the same IP.
- 429 Слишком много запросов
You’ve hit a rate limit. Slow down and add longer delays between requests.
- __NEXT_DATA__ not found
Zillow served a CAPTCHA or challenge page instead of the real listing page. Check the raw HTML you received.
- JSONDecodeError
The script tag exists but its content changed shape. Print the raw string and inspect it manually.
Why Zillow blocks scrapers
Zillow invests heavily in anti-bot infrastructure, and it’s worth understanding what you’re actually up against.
Ограничение скорости
Send too many requests from one IP in a short window, and Zillow starts throttling or blocking that IP outright. Real users don’t load 50 property pages in 10 seconds, so that pattern stands out immediately.
Репутация IP-адреса
Zillow checks the reputation of the IP address making the request. Datacenter IPs, especially ones from well-known hosting providers like AWS or DigitalOcean, are heavily associated with bots and get blocked far more aggressively than residential IPs.
Отпечаток браузера
Beyond the IP, Zillow’s anti-bot layer looks at TLS handshake details, header order, and JavaScript execution behavior. A plain requests call has a different fingerprint than an actual Chrome browser, and sophisticated bot detection can pick up on that gap.
CAPTCHA-задачи
When traffic looks suspicious, Zillow serves a CAPTCHA page instead of the real content. A Zillow scrape CAPTCHA usually shows up after a burst of rapid requests or an obvious bot fingerprint. If your scraper isn’t handling that case, it will silently save the CAPTCHA page instead of listing data, which is why checking for the expected JSON structure matters.
Request pattern analysis
Consistent, robotic timing between requests, like exactly one request every two seconds, is its own signal. Human browsing is messier: pauses, backtracking, variable scroll speed. Randomized delays help, but they’re not a complete fix on their own.
How proxies improve Zillow scraping
Everything above points to the same root problem: your IP address is the first thing Zillow evaluates, before it even looks at your headers or behavior.
Резидентские прокси route your requests through real IP addresses assigned by internet service providers to actual households. To Zillow’s systems, that traffic looks like an ordinary visitor, not a scraper running out of a cloud server.
NodeMaven’s residential proxies pull from a pool of 30M+ real, filtered IPs across 190+ countries, with a 95% clean IP rate.
A few ways this helps a Zillow scraper in practice:
- Rotating IPs spread your requests across thousands of different addresses, so no single IP absorbs enough traffic to get flagged
- Fewer CAPTCHA challenges since clean residential IPs trigger far less suspicion than datacenter ranges
- Sticky sessions let you hold the same IP for a multi-step flow, like paginating through one search’s results, without switching identity mid-task
- геотаргетинг на уровне ZIP-кода is useful if you’re scraping a specific local market and want IPs that match that region
- Higher overall success rate per request, which means fewer retries and a faster scrape end to end
For scraping tasks that also need to look like mobile traffic, or that hit endpoints more sensitive to device type, мобильные прокси. are worth testing too.

Before you plug proxies into the scraper above, it’s worth verifying the connection works as expected. NodeMaven’s free bandwidth checker confirms a proxy connects properly and performs well enough for a scraping workload, and the free IP lookup tool shows exactly what IP, location, and ISP a site like Zillow would see on the other end. Both are free and don’t require an account.
Scaling your Zillow scraper
A script that works for 50 listings behaves very differently at 50,000. Here’s what changes as you scale up.
Retries and delays
Add randomized delays between requests, not fixed ones. Something like time.sleep(random.uniform(2, 5)) between page fetches looks far less robotic than a flat two-second gap every time
ротационные IP-адреса
At scale, manually swapping proxy credentials doesn’t hold up. A rotating proxy setup, where a new IP is assigned automatically per request or per session, keeps traffic distributed without you managing it by hand
Параллелизм
Sequential requests are slow. Using concurrent.futures.ThreadPoolExecutor or an async library like httpx lets you fetch multiple pages in parallel. Keep concurrency modest, 5 to 10 workers is usually a safer starting point than 50
Data validation
Not every parsed listing will be complete. Add checks for missing price or address fields before you write a row, so a partially blocked response doesn’t quietly corrupt your dataset
Логирование
Log every request’s URL, status code, and timestamp. When something breaks at 3am on a scheduled run, this is what tells you whether it was a block, a parsing change, or a network issue
Deduplication
Zillow listings can appear in multiple search pages, especially if new listings shift pagination while you’re scraping. Deduplicate on the zpid (Zillow’s internal property ID, visible in the listing URL) rather than address text, which can vary in formatting
Alternatives to building your own scraper
Writing your own scraper isn’t the only path. Depending on your timeline and technical comfort, one of these might fit better.
| Approach | Лучшее для | Tradeoffs |
| Custom Python scraper | Full control, custom fields, ongoing internal use | You maintain it when Zillow changes its page structure |
| Apify Zillow scraper | Fast setup, no code needed | Usage-based pricing, less flexibility over output format |
| Real estate data APIs | Reliable, structured data feeds | Often paid, may not cover every field you want |
| Open-source GitHub projects | Learning, quick prototypes | Often outdated, break when Zillow updates its site |
| Browser extensions | One-off, small manual exports | Not built for scale or automation |
Zillow does not offer a public API for general listing data. Their official API access is limited to approved partners, which is why most developers end up scraping the public site instead.
Заключение
Building a Zillow scraper in Python is a manageable weekend project once you understand the pieces: fetch the page, pull the embedded JSON, parse it into a clean structure, and export it. The harder part is keeping that scraper running reliably, and that comes down to how your requests look to Zillow’s anti-bot systems.
Realistic headers, sensible delays, and clean residential IPs solve most of the reliability problems you’ll run into. Scrape responsibly, respect rate limits, and keep your usage aligned with what the data is actually for.
If IP blocks are the thing slowing you down, NodeMaven’s резидентский и мобильные прокси. are worth testing against your own scraper before you scale anything up.




