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).

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

These are the selectors the cards use:
| Data Field | CSS Selector |
|---|---|
| Property Card | div.HomeCardContainer |
| Link and Address | a.bp-Homecard__Address |
| Price | span.bp-Homecard__Price—value |
| Beds | span.bp-Homecard__Stats—beds |
| Baths | span.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 beautifulsoup4If 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:

The form has three settings:

- 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.

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).


