In this Article
Real estate data scraping is how property platforms, investors, and proptech teams turn scattered online listings into a structured dataset — prices, addresses, beds, baths, square footage, and trends across a whole market. This guide shows how to scrape property listings with Python, the robust way (parsing the structured data sites already embed), plus the anti-bot and proxy setup that keeps collection running across regions.
I’m Andrii Byzov, an AI-Native Fractional CMO who builds web-data pipelines. Below: what real estate data you can collect, a clear legal line, and working code for search results and individual listings — with residential proxies for geo-accurate, unblocked data.
Key Facts
- Most listing facts are public and lower-risk — price, beds/baths, sqft, type — but addresses, photos, descriptions and agent names can be personal or copyrighted, so assess before reuse.
- Parse embedded JSON where present, not fragile CSS. Many real estate sites expose listing data as JSON-LD (schema.org); parse that when available and fall back to the HTML cards when it’s not.
- Some sites personalize by location — results, availability, language, or currency — so geo-targeted proxies help match a specific market.
- Big portals defend hard. Zillow, Realtor.com, Rightmove and similar rate-limit and flag datacenter IPs fast; rotating residential proxies help, but no tool guarantees access or compliance.
- Agent and owner contact details are personal data. Keep them out of the pipeline; collect property facts, not people.
What Real Estate Data Can You Scrape?
A property listing is rich, structured data. The fields generally worth collecting — and the ones to handle carefully:
- Lower-risk property facts: price, bedrooms, bathrooms, square footage, lot size, property type, year built, listing status, days on market, and price history.
- Handle with care or avoid: exact addresses, photos and full descriptions (often copyrighted), and agents’/owners’ contact details (personal data). Public availability doesn’t make this data automatically reusable.
Is Real Estate Scraping Legal?
Collecting public property facts is broadly defensible, and it’s how price-intelligence and proptech work — but public availability doesn’t automatically make data non-personal or reusable: assess privacy, copyright, database rights, contracts, and local law. The practical line: don’t scrape behind a login, keep personal data (agent/owner details) out, don’t republish copyrighted photos or descriptions, respect each site’s terms and robots.txt, and don’t evade access controls or CAPTCHAs. Big portals restrict automated access, so for those prefer an official API, a licensed data feed, or written permission. For the full framework, see our guide on whether web scraping is legal. This is general information, not legal advice.
Step 1 — Set Up Your Environment
Install the libraries the real estate scraper needs. Static, JSON-LD-friendly sites work with requests; JavaScript-heavy portals like Zillow need a headless browser.
# Core libraries for real estate data scraping
# requests -> fetch pages | beautifulsoup4 + lxml -> parse HTML/JSON
pip install requests beautifulsoup4 lxml
# Many listing sites are JavaScript-heavy and well defended (e.g. Zillow).
# For those, add a headless browser:
pip install playwright && playwright install chromium
Step 2 — Fetch a Listings Page
Pull a search-results page (a city’s homes-for-sale URL) with proper headers, routed through a residential proxy so the listings match the target market.
import requests
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9"}
# Route through residential proxies for geo-accurate listings (see Step 5)
proxies = {"http": "http://LOGIN:[email protected]:823",
"https": "http://LOGIN:[email protected]:823"}
def get_html(url):
r = requests.get(url, headers=HEADERS, proxies=proxies, timeout=30)
r.raise_for_status()
return r.text
html = get_html("https://example-realestate.com/homes/for_sale/CityName/")
Step 3 — Extract Listings from Embedded JSON
Where it’s available, the most reliable way to scrape property listings is to read the structured data the site already embeds. Many real estate platforms include application/ld+json (schema.org) with a listing’s price, address, and URL — stable across redesigns, unlike CSS class names. Not every site exposes it (some keep data in JS app state), so check each one and fall back to parsing the cards in Step 4.
import json
from bs4 import BeautifulSoup
LISTING_TYPES = ("Residence", "Product", "RealEstateListing", "Offer",
"SingleFamilyResidence", "Apartment", "House")
def listings_from_jsonld(html):
"""Where available, sites embed listing data as JSON-LD (schema.org).
Parsing it is more stable than CSS classes — but not every site has it,
so fall back to the cards in Step 4 when this returns nothing."""
soup = BeautifulSoup(html, "lxml")
nodes, out = [], []
for tag in soup.find_all("script", type="application/ld+json"):
try:
data = json.loads(tag.string or "{}")
except json.JSONDecodeError:
continue
stack = data if isinstance(data, list) else [data]
while stack: # flatten @graph / ItemList / nested
n = stack.pop()
if not isinstance(n, dict):
continue
if "@graph" in n:
stack.extend(n["@graph"])
if n.get("@type") == "ItemList":
stack.extend(x.get("item", x) for x in n.get("itemListElement", []))
nodes.append(n)
for n in nodes:
types = n.get("@type", "")
types = types if isinstance(types, list) else [types]
if any(t in LISTING_TYPES for t in types):
offers = n.get("offers") or {}
if isinstance(offers, list):
offers = offers[0] if offers else {}
out.append({"name": n.get("name"),
"price": offers.get("price") or n.get("price"),
"address": n.get("address"),
"url": n.get("url")})
return out
print(listings_from_jsonld(html)[:3])
Step 4 — Parse the Listing Cards (Fallback)
When a site doesn’t expose clean JSON-LD, parse the search-result cards directly: price, address, beds, baths, sqft, and the listing link. Selectors vary by site and change often, so confirm them in DevTools.
def parse_listing_cards(html):
"""Fallback: parse the search-results cards directly. Selectors differ by
site and change often — confirm the current ones in DevTools."""
soup = BeautifulSoup(html, "lxml")
cards = []
for card in soup.select("[data-test='property-card']"):
def t(sel):
el = card.select_one(sel)
return el.get_text(strip=True) if el else None
cards.append({
"price": t("[data-test='property-price']"),
"address": t("[data-test='property-address']"),
"beds": t("[data-test='property-beds']"),
"baths": t("[data-test='property-baths']"),
"sqft": t("[data-test='property-sqft']"),
"url": (card.select_one("a[href]") or {}).get("href"),
})
return cards
Step 5 — Handle Pagination
One page of results isn’t a dataset. Loop through the pages until results run out or you hit a cap, pacing requests so you don’t trip rate limits.
import time, random
def scrape_all_pages(base_url, max_pages=10):
"""Page through search results until empty or max_pages."""
all_listings = []
for page in range(1, max_pages + 1):
html = get_html(f"{base_url}?page={page}")
cards = parse_listing_cards(html)
if not cards: # no more results -> stop
break
all_listings.extend(cards)
time.sleep(random.uniform(1, 3)) # pace requests
return all_listings
Step 6 — Use Proxies for Geo-Accurate, Unblocked Data
Two things make real estate web scraping harder without proxies. First, location: some sites personalize results, availability, language, or currency by region, so to match a specific market you query from an IP there. Second, blocking: big portals flag datacenter IPs fast and rate-limit aggressively. DataImpulse residential proxies ($1/GB, rotating, 195 countries with city/state targeting) help with both — real-user IPs in the right market that reduce IP-based blocks — though access isn’t guaranteed and you still have to follow each site’s rules. Route requests through them and export the data you collected.
import time, random, json, csv
# Listings differ by region: route through a proxy in the target market so
# prices, availability, and currency match what a local user sees.
proxies = {
"http": "http://LOGIN__cr.us;sid.re1:[email protected]:823",
"https": "http://LOGIN__cr.us;sid.re1:[email protected]:823",
}
listings = scrape_all_pages("https://example-realestate.com/homes/for_sale/CityName/")
with open("listings.json", "w", encoding="utf-8") as f:
json.dump(listings, f, indent=2, ensure_ascii=False)
if listings:
with open("listings.csv", "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=listings[0].keys())
w.writeheader(); w.writerows(listings)
Frequently Asked Questions
Is real estate data scraping legal?
Collecting public, non-personal property data (price, address, beds, sqft) is broadly defensible and standard in proptech. Avoid personal data (agent/owner contacts), don’t scrape behind logins, don’t republish copyrighted photos or descriptions, and respect site terms and robots.txt. Big portals restrict automated access, so get permission or counsel for commercial use. This is general information, not legal advice — see our web scraping legality guide.
What’s the best way to scrape property listings?
Parse the structured data sites already embed — most real estate platforms ship listings as JSON-LD (schema.org) in the page HTML, which is far more stable than chasing CSS class names that change with every redesign. Fall back to parsing the listing cards only when no clean JSON is available.
Do I need proxies to scrape real estate sites?
For volume, usually. Big portals (Zillow, Realtor.com, Rightmove) rate-limit and flag datacenter IPs quickly, and some listings are location-dependent. Rotating residential proxies in the target market help return location-accurate data and reduce IP-based blocks — though they don’t guarantee access or make scraping compliant.
Can I scrape Zillow or Realtor.com directly?
They’re public but heavily defended, JavaScript-rendered, and their terms restrict automated access. For those portals, prefer an official API, a licensed data feed, or written permission rather than evading their defenses. If you do collect public data, take only public property facts, stay logged out, don’t bypass access controls, and pace requests. See our best proxies for Zillow guide.
What real estate data can I collect?
Mostly public property facts — price, beds, baths, square footage, lot size, type, status, days on market, price history. But public availability doesn’t automatically make data non-personal or reusable: exact addresses, photos, descriptions, and agent/owner details can carry privacy or copyright weight, so assess before storing or republishing.
Conclusion
Real estate data scraping turns scattered listings into a usable dataset — and the reliable recipe is: read the embedded JSON-LD where you can, fall back to parsing cards where you can’t, page through results, and route everything through geo-targeted residential proxies so the data is location-accurate and unblocked. Keep to public, non-personal property facts and you stay in the defensible lane. For the infrastructure, see best proxies for real estate data and the broader best proxies for web scraping guide.
Last updated: June 25, 2026.

State/City/Zip/ASN Targeting 



