Table of Contents
Why Scrape Real Estate Data?
Real estate data drives billions in investment decisions. Access to comprehensive property data enables:
- Investment analysis — Identify undervalued properties and market opportunities
- Market research — Track pricing trends across neighborhoods and cities
- Comparable analysis — Find similar properties for accurate valuations
- Lead generation — Identify motivated sellers and expired listings
- Portfolio management — Monitor property values and rental yields
- Development planning — Analyze land prices and zoning data
Real estate investors using data-driven approaches report 20-40% better returns than traditional methods. Automated market analysis can identify opportunities hours or days before competitors.
Available Data Points
Property Details
- Address — Full property location
- Price — List price, Zestimate, previous prices
- Bedrooms/Bathrooms — Room counts
- Square footage — Living area and lot size
- Year built — Construction date
- Property type — Single family, condo, multi-family
- Parking — Garage, driveway details
Financial Data
- Price history — All historical sale prices
- Tax assessment — Property tax values
- HOA fees — Monthly association costs
- Rental estimates — Expected rental income
- Days on market — Time listed for sale
Neighborhood Data
- School ratings — Nearby school scores
- Walk score — Walkability index
- Crime statistics — Safety metrics
- Median home values — Area averages
Scraping Zillow
Zillow is the largest real estate marketplace with over 100 million listings. Their data includes Zestimates, price history, and comprehensive property details:
from playwright.sync_api import sync_playwright
import json
import re
class ZillowScraper:
def __init__(self):
self.playwright = sync_playwright().start()
self.browser = self.playwright.chromium.launch(headless=True)
def search_properties(self, city, state, max_pages=5):
properties = []
page = self.browser.new_page()
for page_num in range(1, max_pages + 1):
url = f"https://www.zillow.com/{city}-{state}/?searchQueryState=%7B%22pagination%22%3A%7B%22currentPage%22%3A{page_num}%7D%7D"
page.goto(url, wait_until='networkidle')
page.wait_for_selector('[data-test="property-card"]', timeout=15000)
# Extract property cards
cards = page.locator('[data-test="property-card"]').all()
for card in cards:
try:
prop = {
'address': card.locator('address').text_content().strip(),
'price': self._parse_price(card.locator('[data-test="property-card-price"]').text_content()),
'beds': self._extract_beds(card),
'baths': self._extract_baths(card),
'sqft': self._extract_sqft(card),
'link': card.locator('a').first.get_attribute('href'),
}
properties.append(prop)
except:
continue
page.close()
return properties
def get_property_details(self, zpid):
"""Get detailed data for a specific property."""
page = self.browser.new_page()
url = f"https://www.zillow.com/homedetails/{zpid}_zpid/"
page.goto(url, wait_until='networkidle')
details = {
'zpid': zpid,
'price': self._get_text(page, '[data-testid="price"]'),
'zestimate': self._get_text(page, '[data-testid="zestimate-text"]'),
'address': self._get_text(page, 'h1'),
'beds': self._get_text(page, '[data-testid="bed-bath-item"]:has-text("bd")'),
'baths': self._get_text(page, '[data-testid="bed-bath-item"]:has-text("ba")'),
'sqft': self._get_text(page, '[data-testid="bed-bath-item"]:has-text("sqft")'),
'year_built': self._extract_fact(page, 'Year built'),
'lot_size': self._extract_fact(page, 'Lot'),
'price_per_sqft': self._extract_fact(page, 'Price/sqft'),
}
page.close()
return details
def _parse_price(self, text):
if not text:
return None
numbers = re.findall(r'[\d,]+', text)
return int(numbers[0].replace(',', '')) if numbers else None
def _get_text(self, page, selector):
elem = page.locator(selector).first
return elem.text_content().strip() if elem.count() > 0 else None
def _extract_fact(self, page, label):
elem = page.locator(f'[data-testid="facts-table"] >> text="{label}"').first
if elem.count() > 0:
return elem.locator('..').locator('span').last.text_content()
return None
def close(self):
self.browser.close()
self.playwright.stop()
Scraping Redfin
Redfin offers detailed listing data and is often more up-to-date than other sources:
class RedfinScraper:
def __init__(self):
self.base_url = "https://www.redfin.com"
self.playwright = sync_playwright().start()
self.browser = self.playwright.chromium.launch(headless=True)
def search_properties(self, location):
page = self.browser.new_page()
properties = []
# Redfin search URL format
url = f"{self.base_url}/city/{location}"
page.goto(url, wait_until='networkidle')
# Wait for listings to load
page.wait_for_selector('.HomeCardContainer', timeout=15000)
cards = page.locator('.HomeCardContainer').all()
for card in cards:
try:
prop = {
'price': card.locator('.homecardV2Price').text_content().strip(),
'address': card.locator('.homeAddressV2').text_content().strip(),
'beds': card.locator('.HomeStatsV2 >> nth=0').text_content(),
'baths': card.locator('.HomeStatsV2 >> nth=1').text_content(),
'sqft': card.locator('.HomeStatsV2 >> nth=2').text_content(),
'link': self.base_url + card.locator('a').first.get_attribute('href'),
}
properties.append(prop)
except:
continue
page.close()
return properties
def get_price_history(self, property_url):
"""Extract price history from property page."""
page = self.browser.new_page()
page.goto(property_url, wait_until='networkidle')
history = []
rows = page.locator('.PropertyHistoryEventRow').all()
for row in rows:
try:
event = {
'date': row.locator('.date-col').text_content().strip(),
'event': row.locator('.event-col').text_content().strip(),
'price': row.locator('.price-col').text_content().strip(),
}
history.append(event)
except:
continue
page.close()
return history
Scraping Realtor.com
Realtor.com has direct MLS connections, providing highly accurate listing data:
class RealtorScraper:
def search_properties(self, city, state_code, property_type='for-sale'):
page = self.browser.new_page()
url = f"https://www.realtor.com/realestateandhomes-search/{city}_{state_code}"
page.goto(url, wait_until='networkidle')
properties = []
cards = page.locator('[data-testid="card-content"]').all()
for card in cards:
try:
prop = {
'price': card.locator('[data-testid="card-price"]').text_content(),
'address': card.locator('[data-testid="card-address-1"]').text_content(),
'city': card.locator('[data-testid="card-address-2"]').text_content(),
'beds': card.locator('[data-testid="property-meta-beds"]').text_content(),
'baths': card.locator('[data-testid="property-meta-baths"]').text_content(),
'sqft': card.locator('[data-testid="property-meta-sqft"]').text_content(),
'status': card.locator('[data-testid="card-description"]').text_content(),
}
properties.append(prop)
except:
continue
page.close()
return properties
Analyzing Property Data
import pandas as pd
def analyze_market(properties):
"""Analyze scraped property data for market insights."""
df = pd.DataFrame(properties)
# Clean price data
df['price'] = df['price'].str.replace(r'[^\d]', '', regex=True).astype(float)
df['sqft'] = df['sqft'].str.replace(r'[^\d]', '', regex=True).astype(float)
# Calculate metrics
df['price_per_sqft'] = df['price'] / df['sqft']
analysis = {
'median_price': df['price'].median(),
'avg_price': df['price'].mean(),
'median_sqft': df['sqft'].median(),
'avg_price_per_sqft': df['price_per_sqft'].mean(),
'total_listings': len(df),
'price_range': {
'min': df['price'].min(),
'max': df['price'].max()
}
}
return analysis
def find_deals(properties, threshold=0.8):
"""Find properties priced below market average."""
df = pd.DataFrame(properties)
df['price_per_sqft'] = df['price'] / df['sqft']
avg_ppsf = df['price_per_sqft'].mean()
deals = df[df['price_per_sqft'] < avg_ppsf * threshold]
return deals.to_dict('records')
Investment Use Cases
1. Automated Deal Finding
Set up scrapers to monitor new listings and alert when properties match your investment criteria (price per sqft, cap rate, location).
2. Rental Yield Analysis
Combine listing prices with rental data to calculate potential yields and identify high-ROI investment opportunities.
3. Market Trend Reports
Track price movements, days on market, and inventory levels to produce market intelligence reports.
4. Comparable Sales (Comps)
Build automated comp analysis tools that find similar recently sold properties for accurate valuations.
Need Real Estate Data at Scale?
Crawlix provides comprehensive property data from all major platforms with daily updates and historical tracking.
Get Property Data →