HasData
Back to all posts

How to Scrape Redfin Real Estate Property Data

Redfin is one of the largest online real estate platforms in the U.S., ranking fifth among top brokers by revenue. It offers data ranging from property listings for sale and rent to market insights and convenient search tools.

In this article, we’ll guide you through various methods to gather data from Redfin. We’ll explore building a custom Python scraper with Beautiful Soup, introduce a quick trick for retrieving data directly from Redfin’s JSON using Selenium, and show you how to create a scraper using the Redfin API.

Redfin’s Official Data Comes as Downloads

What Redfin publishes officially is the Data Center, downloadable market data by region covering prices, inventory, and sales over time. A public listings API is not part of the offer. For market research that is the cleanest source there is, and it costs nothing. What the Data Center does not carry is individual listings, so a task that needs addresses, prices, and links to specific homes still comes down to reading the site, which is what the rest of this article covers.

Scrape Redfin Property Data with Beautiful Soup

Redfin decides who gets the page before any parsing starts. A plain requests call redirects to a rate-limit host and ends on HTTP 429, the same call with a current browser User-Agent gets HTTP 405, headless Chromium lands on a page titled “Are You a Robot?”, and even a signed-in browser profile drew “Human Verification”. The route that returned the page was the Web Scraping API with JavaScript rendering on a residential exit, so that is what the script fetches with, and BeautifulSoup does the rest.

Inspect Redfin Page

Before scraping data, let’s research the specific data we can extract from Redfin. To do this, we’ll navigate to the website and open DevTools (F12 or right-click and Inspect).

Home card structure

Home card structure

The properties sit in containers with the class HomeCardContainer, and one card carries everything the listing shows:

Home card blocks

Home card blocks

These are the selectors the cards use:

Data FieldCSS Selector
Property Carddiv.HomeCardContainer
Link and Addressa.bp-Homecard__Address
Pricespan.bp-Homecard__Price—value
Bedsspan.bp-Homecard__Stats—beds
Bathsspan.bp-Homecard__Stats—baths

a.link-and-anchor matches nothing on the current page. The address element is the link itself, so one selector returns both.

Setting Up Your Development Environment

Install the two libraries the script needs:

pip install requests beautifulsoup4

If Python itself is the new part, our Python scraping guide covers the setup from zero.

Full Redfin Scraper Using Beautiful Soup

The script asks the API for the rendered page, then parses the cards. It writes 41 properties for Tampa:

import csv
import requests
from bs4 import BeautifulSoup

api_key = "YOUR-API-KEY"
target = "https://www.redfin.com/city/18142/FL/Tampa"

response = requests.post(
    "https://api.hasdata.com/scrape/web",
    json={"url": target, "outputFormat": ["html"],
          "jsRendering": True, "proxyType": "residential"},
    headers={"x-api-key": api_key},
    timeout=240,
)
response.raise_for_status()

soup = BeautifulSoup(response.text, "html.parser")
properties = []
for card in soup.find_all("div", class_="HomeCardContainer"):
    link_tag = card.find("a", class_="bp-Homecard__Address")
    price = card.find("span", class_="bp-Homecard__Price--value")
    if not (link_tag and price):
        continue  # ad slots and empty containers
    beds = card.find("span", class_="bp-Homecard__Stats--beds")
    baths = card.find("span", class_="bp-Homecard__Stats--baths")
    properties.append({
        "Price": price.text.strip(),
        "Beds": beds.text.strip() if beds else "",
        "Baths": baths.text.strip() if baths else "",
        "Address": link_tag.text.strip(),
        "Link": "https://www.redfin.com" + (link_tag.get("href") or ""),
    })

with open("redfin_properties.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["Price", "Beds", "Baths", "Address", "Link"])
    writer.writeheader()
    writer.writerows(properties)

print(f"{len(properties)} properties saved")

The guard in the loop matters. The page carries 43 HomeCardContainer divs, two of them with no listing inside, so a version that indexes fields without checking crashes on real pages.

Parse Redfin JSON using Selenium

Let’s do the same thing in an easier way using Selenium.

Full Redfin Scraper using Selenium

Every card also carries its data as JSON inside a script type="application/ld+json" tag, and reading that beats scraping text out of markup, since the structure survives redesigns that rename CSS classes. The script below collects those blocks. If Selenium itself is unfamiliar, the Selenium with Python guide starts from zero. The access notes above apply here too. The script below drives a local Chrome, which works while the page still opens for you; from an exit Redfin rate-limits, keep the fetch on the API and hand the rendered HTML to the same parsing. The rendered Tampa page carried 41 ld+json property blocks, so the parse works on it unchanged:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import json

chrome_options = Options()
driver = webdriver.Chrome(options=chrome_options)

url = "https://www.redfin.com/city/18142/FL/Tampa"

try:
    driver.get(url)
    WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, "HomeCardContainer")))
    homecards = driver.find_elements(By.CLASS_NAME, "HomeCardContainer")

    properties = []

    for card in homecards:
        try:
            script_elements = card.find_elements(By.XPATH, ".//script[@type='application/ld+json']")
            
            for script in script_elements:
                json_data = json.loads(script.get_attribute("innerHTML"))
                
                if isinstance(json_data, list):
                    properties.extend(json_data)
                else:
                    properties.append(json_data)

        except Exception as e:
            print(f"Error: {e}")

    with open("properties.json", "w") as json_file:
        json.dump(properties, json_file, indent=4)

finally:
    driver.quit()

The script also sits on the Colab Research page, but it only runs on your own machine, since Google Colaboratory allows no web drivers.

The Same Harvest in Playwright

Playwright runs the identical ld+json collection, and it reads a saved page as easily as a live one. That makes the split explicit. The API gets the page, and Playwright parses it. This ran against the rendered Tampa page and returned the same 41 properties:

import json
import pathlib
from playwright.sync_api import sync_playwright

# HTML obtained through the Web Scraping API, saved to disk
source = pathlib.Path("tampa.html").resolve().as_uri()

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto(source)

    properties = []
    for block in page.locator('script[type="application/ld+json"]').all():
        try:
            data = json.loads(block.text_content() or "")
        except json.JSONDecodeError:
            continue
        items = data if isinstance(data, list) else [data]
        properties += [i for i in items if isinstance(i, dict)
                       and i.get("@type") == "SingleFamilyResidence"]
    browser.close()

print(f"{len(properties)} properties")

Swap source for the live URL and the same script covers the case where a page renders for you. Each entry arrives as schema.org SingleFamilyResidence, with the address split into fields and the URL absolute, so there is no markup left to clean.

Scrape Redfin with HasData’s API

Alright, if you’re like me and don’t want to deal with buying proxies or setting up captcha-solving services, here’s a simple solution. Redfin doesn’t offer an official API, so we’ll use HasData’s Redfin API instead. It returns all the data in a clean JSON format. Check out the full details in the docs.

The Listing Endpoint

The Redfin API has two endpoints, and the listing one covers the search-results job from the sections above. It takes a ZIP code and a listing type and returns parsed objects, 40 per page:

import requests
import json

api_key = "YOUR-API-KEY"

response = requests.get(
    "https://api.hasdata.com/scrape/redfin/listing",
    params={"keyword": "33321", "type": "forSale"},
    headers={"x-api-key": api_key},
    timeout=180,
)
response.raise_for_status()
data = response.json()

properties = data.get("properties", [])
for p in properties[:5]:
    print(p.get("price"), "|", p.get("beds"), "bd", p.get("baths"), "ba |",
          p.get("addressRaw"))

with open("listings.json", "w", encoding="utf-8") as f:
    json.dump(properties, f, indent=4, ensure_ascii=False)

Each property carries the fields the cards show plus what they do not: daysOnSite, homeType, atAGlanceFacts, the full description, and pagination sits next to properties in the response for walking deeper.

The Property Endpoint

The second endpoint takes one listing URL and returns everything its page holds:

import requests
import json

api_key = "YOUR-API-KEY"

response = requests.get(
    "https://api.hasdata.com/scrape/redfin/property",
    params={"url": "https://www.redfin.com/IL/Chicago/1322-S-Prairie-Ave-60605/unit-1106/home/12694628"},
    headers={"x-api-key": api_key},
    timeout=180,
)
response.raise_for_status()

prop = response.json().get("property", {})
print(prop["address"]["addressRaw"], "|", prop.get("homeType"), "|", prop.get("area"), "sq ft")

with open("property.json", "w", encoding="utf-8") as f:
    json.dump(prop, f, indent=4, ensure_ascii=False)

The property object holds the address as parts, agent info, geo coordinates, at-a-glance facts, price and tax history, schools and the nearby comparables, which is the per-listing depth the listing endpoint trades away for volume. Lot size is not a field of its own here: it appears as a labelled row inside propertyDetails, and a condo returns a dash for it.

Scrape Data from Redfin Without Code

The no-code route runs entirely in the browser, and the fetching happens on the service’s side rather than your machine. The proxy pool and the rendering are part of the run, and the result arrives as rows you download.

Let’s take a closer look at how to use such tools using the example of HasData’s Redfin no-code scraper. Sign up, open the No-Code Scrapers section, and find the Redfin Property card:

No-Code Scrapers catalog with the Redfin Property card outlined

The form has three settings:

Redfin Property scraper form with the rows limit at ten, two ZIP codes, and For Sale as the listing type

  • Result rows limit caps the rows the run returns. The banner above the form prices a plan in rows, and the Basic plan’s million credits come to about 100 thousand rows here, since a Redfin row costs 10 credits.
  • Zip Codes take one code per line.
  • Type switches between For Sale, For Rent, and Sold.

Click Run Scraper and the job appears in the history on the left. A ten-row test finishes in under two minutes and spends 100 credits, and the chips above the results carry the row count, columns, credits, duration, and job id. The download control returns CSV, JSON, or XLSX.

Finished Redfin run showing ten rows with URL, home type and price, and the download control outlined

The result file carries 25 columns per property, the price, type and status among them, and the Redfin Scraper page shows a full example of one.

Conclusion

This article covered the working methods for gathering real estate data from Redfin, from Beautiful Soup and browser parsing to the API and the no-code scraper, and each one extracts details like prices, addresses, and property features. You can also check out our Zillow scraping tutorial for more real estate insights.

All scripts are available in Google Colaboratory, so you can run them without installing Python (except the browser examples, which need a local machine).

Valentina Skakun
Valentina Skakun
Valentina is a software engineer who builds data extraction tools before writing about them. With a strong background in Python, she also leverages her experience in JavaScript, PHP, R, and Ruby to reverse-engineer complex web architectures.If data renders in a browser, she will find a way to script its extraction.
Articles

Might Be Interesting