Real Estate Data Scraping: Zillow, Redfin & Realtor

Extract property listings, price histories, and market data from major real estate platforms. Build investment analysis tools and market intelligence systems with comprehensive property data.

Why Scrape Real Estate Data?

Real estate data drives billions in investment decisions. Access to comprehensive property data enables:

Market Opportunity

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

Financial Data

Neighborhood Data

Scraping Zillow

Zillow is the largest real estate marketplace with over 100 million listings. Their data includes Zestimates, price history, and comprehensive property details:

Python
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:

Python
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:

Python
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

Python
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 →