Scrape Realtor.com property listings
Search Realtor.com for homes and extract prices, addresses, bed/bath counts, and square footage from the results.
- A Browserless API token from your account dashboard
Steps
Realtor.com renders listings with JavaScript and uses bot-detection measures. The examples below search for homes in San Francisco and route through stealth mode with a residential proxy.
Realtor.com updates its markup periodically. If selectors stop returning results, inspect the live page with browser DevTools to find the current element and attribute names.
- AI Agent
- REST API
- Frameworks
- BQL
Use the Browserless MCP server to scrape property listings from Realtor.com from any MCP-compatible AI agent (Claude Desktop, Cursor, Windsurf, ChatGPT, etc.).
1. Connect the MCP server
Send this prompt to your AI agent to install the Browserless MCP server:
Go to https://github.com/browserless/browserless-mcp/blob/main/install.md
and follow the instructions to install the Browserless MCP server
for my client.
2. Scrape Realtor.com
Use browserless_smartscraper. It handles Realtor.com's dynamic content and bot protection automatically.
Use the browserless_smartscraper tool to scrape property listings
from https://www.realtor.com/realestateandhomes-search/San-Francisco_CA
and return the results as markdown
Send the BQL mutation over HTTP to the stealth endpoint. No browser library or BQL IDE required.
- cURL
- JavaScript
- Python
- Java
- C#
1. Send the request
curl -X POST \
"https://production-sfo.browserless.io/stealth/bql?token=YOUR_API_TOKEN_HERE&proxy=residential&proxyCountry=us" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation ScrapeRealtor { goto(url: \"https://www.realtor.com/realestateandhomes-search/San-Francisco_CA\", waitUntil: networkIdle) { status } waitForSelector(selector: \"[data-testid=card-content]\", timeout: 15000) { time } properties: mapSelector(selector: \"[data-testid=card-content]\") { price: mapSelector(selector: \"[data-testid=card-price]\") { innerText } address: mapSelector(selector: \"[data-testid=card-address]\") { innerText } beds: mapSelector(selector: \"[data-testid=property-meta-beds] span\") { innerText } baths: mapSelector(selector: \"[data-testid=property-meta-baths] span\") { innerText } sqft: mapSelector(selector: \"[data-testid=property-meta-sqft] span\") { innerText } status: mapSelector(selector: \"[data-testid=card-description]\") { innerText } } }",
"variables": {}
}'
2. Check the output
{
"data": {
"goto": { "status": 200 },
"waitForSelector": { "time": 3654 },
"properties": [
{
"price": [{ "innerText": "$1,295,000" }],
"address": [{ "innerText": "123 Valencia St, San Francisco, CA 94103" }],
"beds": [{ "innerText": "3" }],
"baths": [{ "innerText": "2" }],
"sqft": [{ "innerText": "1,450" }],
"status": [{ "innerText": "For Sale" }]
},
{
"price": [{ "innerText": "$875,000" }],
"address": [{ "innerText": "456 Guerrero St, San Francisco, CA 94110" }],
"beds": [{ "innerText": "2" }],
"baths": [{ "innerText": "1" }],
"sqft": [{ "innerText": "980" }],
"status": [{ "innerText": "For Sale" }]
}
]
}
}
1. Send the request
const query = `mutation ScrapeRealtor {
goto(url: "https://www.realtor.com/realestateandhomes-search/San-Francisco_CA", waitUntil: networkIdle) {
status
}
waitForSelector(selector: "[data-testid=card-content]", timeout: 15000) {
time
}
properties: mapSelector(selector: "[data-testid=card-content]") {
price: mapSelector(selector: "[data-testid=card-price]") { innerText }
address: mapSelector(selector: "[data-testid=card-address]") { innerText }
beds: mapSelector(selector: "[data-testid=property-meta-beds] span") { innerText }
baths: mapSelector(selector: "[data-testid=property-meta-baths] span") { innerText }
sqft: mapSelector(selector: "[data-testid=property-meta-sqft] span") { innerText }
}
}`;
const response = await fetch(
'https://production-sfo.browserless.io/stealth/bql?token=YOUR_API_TOKEN_HERE&proxy=residential&proxyCountry=us',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables: {} }),
}
);
const { data } = await response.json();
console.log(JSON.stringify(data.properties, null, 2));
2. Check the output
[
{
"price": [{ "innerText": "$1,295,000" }],
"address": [{ "innerText": "123 Valencia St, San Francisco, CA 94103" }],
"beds": [{ "innerText": "3" }],
"baths": [{ "innerText": "2" }],
"sqft": [{ "innerText": "1,450" }]
},
{
"price": [{ "innerText": "$875,000" }],
"address": [{ "innerText": "456 Mission St, San Francisco, CA 94105" }],
"beds": [{ "innerText": "2" }],
"baths": [{ "innerText": "1" }],
"sqft": [{ "innerText": "1,100" }]
}
]
1. Install dependencies
pip install requests
2. Send the request
import requests
query = """
mutation ScrapeRealtor {
goto(url: "https://www.realtor.com/realestateandhomes-search/San-Francisco_CA", waitUntil: networkIdle) {
status
}
waitForSelector(selector: "[data-testid=card-content]", timeout: 15000) {
time
}
properties: mapSelector(selector: "[data-testid=card-content]") {
price: mapSelector(selector: "[data-testid=card-price]") { innerText }
address: mapSelector(selector: "[data-testid=card-address]") { innerText }
beds: mapSelector(selector: "[data-testid=property-meta-beds] span") { innerText }
baths: mapSelector(selector: "[data-testid=property-meta-baths] span") { innerText }
sqft: mapSelector(selector: "[data-testid=property-meta-sqft] span") { innerText }
}
}
"""
response = requests.post(
'https://production-sfo.browserless.io/stealth/bql',
params={
'token': 'YOUR_API_TOKEN_HERE',
'proxy': 'residential',
'proxyCountry': 'us',
},
json={'query': query, 'variables': {}},
)
data = response.json()['data']
for prop in data['properties']:
price = prop['price'][0]['innerText']
address = prop['address'][0]['innerText']
beds = prop['beds'][0]['innerText'] if prop['beds'] else '?'
baths = prop['baths'][0]['innerText'] if prop['baths'] else '?'
sqft = prop['sqft'][0]['innerText'] if prop['sqft'] else '?'
print(f'{address} | {price} | {beds}bd/{baths}ba | {sqft} sqft')
3. Check the output
123 Valencia St, San Francisco, CA 94103 | $1,295,000 | 3bd/2ba | 1,450 sqft
456 Mission St, San Francisco, CA 94105 | $875,000 | 2bd/1ba | 1,100 sqft
1. Send the request
import java.net.URI;
import java.net.http.*;
String token = "YOUR_API_TOKEN_HERE";
String endpoint = "https://production-sfo.browserless.io/stealth/bql?token=" + token
+ "&proxy=residential&proxyCountry=us";
String query = "mutation ScrapeRealtor {"
+ " goto(url: \\\"https://www.realtor.com/realestateandhomes-search/San-Francisco_CA\\\", waitUntil: networkIdle) { status }"
+ " waitForSelector(selector: \\\"[data-testid=card-content]\\\", timeout: 15000) { time }"
+ " properties: mapSelector(selector: \\\"[data-testid=card-content]\\\") {"
+ " price: mapSelector(selector: \\\"[data-testid=card-price]\\\") { innerText }"
+ " address: mapSelector(selector: \\\"[data-testid=card-address]\\\") { innerText }"
+ " beds: mapSelector(selector: \\\"[data-testid=property-meta-beds] span\\\") { innerText }"
+ " baths: mapSelector(selector: \\\"[data-testid=property-meta-baths] span\\\") { innerText }"
+ " sqft: mapSelector(selector: \\\"[data-testid=property-meta-sqft] span\\\") { innerText }"
+ " }"
+ " }";
String payload = "{\"query\": \"" + query + "\", \"variables\": {}}";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
2. Check the output
{
"data": {
"goto": { "status": 200 },
"waitForSelector": { "time": 3654 },
"properties": [
{
"price": [{ "innerText": "$1,295,000" }],
"address": [{ "innerText": "123 Valencia St, San Francisco, CA 94103" }]
}
]
}
}
1. Send the request
using System.Net.Http;
using System.Text;
using System.Text.Json;
string token = "YOUR_API_TOKEN_HERE";
string endpoint = $"https://production-sfo.browserless.io/stealth/bql?token={token}&proxy=residential&proxyCountry=us";
var payload = new
{
query = @"mutation ScrapeRealtor {
goto(url: ""https://www.realtor.com/realestateandhomes-search/San-Francisco_CA"", waitUntil: networkIdle) { status }
waitForSelector(selector: ""[data-testid=card-content]"", timeout: 15000) { time }
properties: mapSelector(selector: ""[data-testid=card-content]"") {
price: mapSelector(selector: ""[data-testid=card-price]"") { innerText }
address: mapSelector(selector: ""[data-testid=card-address]"") { innerText }
beds: mapSelector(selector: ""[data-testid=property-meta-beds] span"") { innerText }
baths: mapSelector(selector: ""[data-testid=property-meta-baths] span"") { innerText }
sqft: mapSelector(selector: ""[data-testid=property-meta-sqft] span"") { innerText }
}
}",
variables = new { },
};
using (HttpClient httpClient = new HttpClient())
{
var content = new StringContent(
JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(endpoint, content);
string body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
}
2. Check the output
{
"data": {
"goto": { "status": 200 },
"waitForSelector": { "time": 3654 },
"properties": [
{
"price": [{ "innerText": "$1,295,000" }],
"address": [{ "innerText": "123 Valencia St, San Francisco, CA 94103" }]
}
]
}
}
Connect through stealth mode and a residential proxy so Realtor.com sees traffic from a real browser, then extract property data from the rendered listings.
- Puppeteer
- Playwright
1. Install dependencies
npm install puppeteer-core
2. Connect and scrape
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({
browserWSEndpoint:
'wss://production-sfo.browserless.io/stealth?token=YOUR_API_TOKEN_HERE&proxy=residential&proxyCountry=us',
});
try {
const page = await browser.newPage();
await page.goto('https://www.realtor.com/realestateandhomes-search/San-Francisco_CA', {
waitUntil: 'networkidle2',
});
await page.waitForSelector('[data-testid="card-content"]');
const properties = await page.evaluate(() =>
Array.from(document.querySelectorAll('[data-testid="card-content"]')).map((card) => ({
price: card.querySelector('[data-testid="card-price"]')?.innerText?.trim() ?? '',
address: card.querySelector('[data-testid="card-address"]')?.innerText?.trim() ?? '',
beds: card.querySelector('[data-testid="property-meta-beds"] span')?.innerText?.trim() ?? '',
baths: card.querySelector('[data-testid="property-meta-baths"] span')?.innerText?.trim() ?? '',
sqft: card.querySelector('[data-testid="property-meta-sqft"] span')?.innerText?.trim() ?? '',
}))
);
console.log(JSON.stringify(properties, null, 2));
} finally {
await browser.close();
}
3. Check the output
Run with node scrape-realtor.mjs. Each object has price, address, beds, baths, and sqft fields.
[
{
"price": "$1,295,000",
"address": "123 Valencia St, San Francisco, CA 94103",
"beds": "3",
"baths": "2",
"sqft": "1,450"
}
]
1. Install dependencies
npm install playwright-core
2. Connect and scrape
import { chromium } from 'playwright-core';
const browser = await chromium.connectOverCDP(
'wss://production-sfo.browserless.io?token=YOUR_API_TOKEN_HERE&stealth&proxy=residential&proxyCountry=us'
);
try {
const context = browser.contexts()[0];
const page = await context.newPage();
await page.goto('https://www.realtor.com/realestateandhomes-search/San-Francisco_CA', {
waitUntil: 'networkidle',
});
await page.waitForSelector('[data-testid="card-content"]');
const properties = await page.evaluate(() =>
Array.from(document.querySelectorAll('[data-testid="card-content"]')).map((card) => ({
price: card.querySelector('[data-testid="card-price"]')?.innerText?.trim() ?? '',
address: card.querySelector('[data-testid="card-address"]')?.innerText?.trim() ?? '',
beds: card.querySelector('[data-testid="property-meta-beds"] span')?.innerText?.trim() ?? '',
baths: card.querySelector('[data-testid="property-meta-baths"] span')?.innerText?.trim() ?? '',
sqft: card.querySelector('[data-testid="property-meta-sqft"] span')?.innerText?.trim() ?? '',
}))
);
console.log(JSON.stringify(properties, null, 2));
} finally {
await browser.close();
}
3. Check the output
Run with node scrape-realtor.mjs. Each object has price, address, beds, baths, and sqft fields.
[
{
"price": "$1,295,000",
"address": "123 Valencia St, San Francisco, CA 94103",
"beds": "3",
"baths": "2",
"sqft": "1,450"
}
]
1. Write the mutation
Navigate to Realtor.com's search results, wait for property cards to load, then extract prices, addresses, and property details. We use /stealth/bql because Realtor.com's bot detection blocks standard headless browsers.
mutation ScrapeRealtor {
goto(url: "https://www.realtor.com/realestateandhomes-search/San-Francisco_CA", waitUntil: networkIdle) {
status
}
waitForSelector(selector: "[data-testid=card-content]", timeout: 15000) {
time
}
properties: mapSelector(selector: "[data-testid=card-content]") {
price: mapSelector(selector: "[data-testid=card-price]") { innerText }
address: mapSelector(selector: "[data-testid=card-address]") { innerText }
beds: mapSelector(selector: "[data-testid=property-meta-beds] span") { innerText }
baths: mapSelector(selector: "[data-testid=property-meta-baths] span") { innerText }
sqft: mapSelector(selector: "[data-testid=property-meta-sqft] span") { innerText }
status: mapSelector(selector: "[data-testid=card-description]") { innerText }
}
}
2. Run it
Paste into the BQL IDE and click Run.
3. Check the output
{
"data": {
"goto": { "status": 200 },
"waitForSelector": { "time": 3654 },
"properties": [
{
"price": [{ "innerText": "$1,295,000" }],
"address": [{ "innerText": "123 Valencia St, San Francisco, CA 94103" }],
"beds": [{ "innerText": "3" }],
"baths": [{ "innerText": "2" }],
"sqft": [{ "innerText": "1,450" }],
"status": [{ "innerText": "For Sale" }]
},
{
"price": [{ "innerText": "$875,000" }],
"address": [{ "innerText": "456 Guerrero St, San Francisco, CA 94110" }],
"beds": [{ "innerText": "2" }],
"baths": [{ "innerText": "1" }],
"sqft": [{ "innerText": "980" }],
"status": [{ "innerText": "For Sale" }]
}
]
}
}
Next steps
- Scrape Amazon Product Listings -- scrape another major commercial site with stealth mode
- Scrape Structured Data -- extract data from sites using the
/scrapeendpoint - Solving Cloudflare Challenges -- bypass Cloudflare's interstitial pages before scraping