# Redfin Scraper (`mlg14/redfin-scraper`) Actor

Collect home listings from Redfin search maps, including sale, sold, and rental results. Export flat property data with prices, addresses, size, coordinates, listing status, and source URLs.

- **URL**: https://apify.com/mlg14/redfin-scraper.md
- **Developed by:** [MLG Data](https://apify.com/mlg14) (community)
- **Categories:** Real estate, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Redfin Scraper

Scrape Redfin home search results for properties for sale, recently sold homes, and rentals from a search URL. Export Redfin property data to CSV, JSON, or Excel with prices, addresses, bedrooms, bathrooms, floor area, location, status, and source links. The actor provides a structured alternative to manually copying records from a map search.

Start with the exact search page you would use in a browser. The actor reads the filters already encoded in that URL, collects matching listing records, follows additional result pages, and returns one flat record per property. It can divide a crowded map into smaller areas when the search reaches its nine-page result limit.

### What data can you extract from Redfin?

The output schema is stable across sale, sold, and rental searches. Fields that a particular listing category does not provide are null. Monetary fields are the numeric amounts shown in the search result; this actor does not convert currencies, calculate taxes, or infer missing prices. Areas use square feet where the source supplies an area. A rental range uses the minimum in the main field and the maximum in the matching `Max` field.

| Field | Description | Example |
| --- | --- | --- |
| `propertyId` | Site property identifier | `121243941` |
| `listingId` | Sale listing identifier | `223846331` |
| `rentalId` | Rental record identifier | `null` for a sale |
| `mlsId` | Public listing reference | `1088464` |
| `url` | Direct property page link | `https://www.redfin.com/FL/Mims/4109-Sweet-Bay-Dr-32754/home/121243941` |
| `offerType` | Search result category | `sale` |
| `mlsStatus` | Listing status text | `Active` |
| `price` | Asking price, sold price, or minimum rent | `340000` |
| `priceMax` | Upper rent amount if a range is shown | `null` |
| `beds` | Bedrooms, or lower bedroom count for a rental range | `3` |
| `bedsMax` | Upper bedroom count for rentals | `null` |
| `baths` | Bathrooms, or lower bathroom count for rentals | `2` |
| `bathsMax` | Upper bathroom count for rentals | `null` |
| `sqFt` | Interior square footage or lower rental area | `1726` |
| `sqFtMax` | Upper rental area in square feet | `null` |
| `pricePerSqFt` | Search result price per square foot | `197` |
| `lotSize` | Lot size in square feet | `36155` |
| `hoa` | Association amount if shown | `null` |
| `streetLine` | Street address | `4109 Sweet Bay Dr` |
| `unitNumber` | Apartment or unit label | `Unit C206` |
| `city` | City name | `Mims` |
| `state` | State abbreviation | `FL` |
| `zip` | Postal code | `32754` |
| `countryCode` | Country code | `US` |
| `location` | Neighborhood or location label | `Spruce Hills 1st Addn To` |
| `latitude` | Property latitude | `28.668593` |
| `longitude` | Property longitude | `-80.8638018` |
| `propertyType` | Site property type code | `6` |
| `listingType` | Site listing type code | `1` |
| `searchStatus` | Site search status code | `1` |
| `yearBuilt` | Construction year | `1957` |
| `soldDate` | Date of a recorded sale, when present | `1989-02-01` |
| `daysOnMarket` | Search result days on market | `2` |
| `timeOnRedfin` | Listing time value supplied by the source | `141341844` |
| `photoCount` | Number of listing photos | `32` |
| `listingAgentName` | Public agent name if shown in the result | `null` |
| `isHot` | Hot listing flag | `false` |
| `isNewConstruction` | New construction flag | `false` |
| `hasVirtualTour` | Virtual tour flag | `true` |
| `hasVideoTour` | Video tour flag | `false` |
| `has3DTour` | 3D tour flag | `false` |
| `propertyName` | Rental building or community name | `null` for a sale |
| `numAvailableUnits` | Available rental unit count | `null` for a sale |
| `listingRemarks` | Public description displayed with the result | `NO HOA + . 83 FENCED ACRE! ...` |
| `lastUpdated` | Rental update timestamp | `null` for a sale |
| `resultIndex` | Output order within the supplied search URL | `1` |
| `foundFromSearchUrl` | Input URL that produced the property | `https://www.redfin.com/zipcode/32754` |
| `zoomQuadrantSequence` | Quadrants visited after a map was split | `[]` |

A recorded `soldDate` can be a prior transaction on an active listing. Use `offerType` and `mlsStatus` to identify the result's current category. The numeric type and status codes are preserved from the search data so you can compare records consistently; the actor does not assign labels to undocumented numeric values.

### How to scrape Redfin

1. Open a Redfin search page that displays homes on a map. Select the area, sale or rental category, and any filters you need.
2. Copy the full search URL into `searchUrls`. Keep its filter and viewport portion; that part defines the search you intend to export.
3. Set `maxResults` to your intended output size. Keep `zoomIn` enabled when the area may contain thousands of listings.
4. Run the actor. When it finishes, open the default dataset and download the records in JSON, CSV, or Excel format.
5. For recurring work, reuse the same input in a schedule and compare stable `propertyId` values between runs.

Each search URL is processed in the order supplied. Listings that appear in more than one input search are deduplicated by property identifier within the run. The first search to return a property determines its `foundFromSearchUrl`. A result limit applies to the combined dataset, so place the most important search first when the limit is small.

### Input

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `searchUrls` | Array of URL objects | Required | One or more map search URLs, including sale, sold, or rental search pages. |
| `zoomIn` | Boolean | `true` | Split dense map searches into smaller areas after the page cap is reached. |
| `maxResults` | Integer | `100` | Stop after this many unique property records across all URLs. Use `0` for no result cap. |
| `proxyConfiguration` | Object | Enabled | Connection setting for the search requests. A US residential connection is used. |

A small ZIP search is a useful first run because it gives you a manageable dataset and lets you inspect field coverage before expanding to a wider map. This example requests 50 homes from a current public ZIP search:

```json
{
  "searchUrls": [
    { "url": "https://www.redfin.com/zipcode/32754" }
  ],
  "maxResults": 50,
  "zoomIn": true
}
```

A map URL with an explicit viewport is also supported. The viewport format is part of the site URL, so paste it as copied instead of entering coordinates separately. A recently sold search uses a URL with a sold filter, such as `https://www.redfin.com/zipcode/32754/filter/include=sold-3mo`. A rental search uses a rental URL, such as `https://www.redfin.com/zipcode/32754/rentals`. Search pages must be public and reachable without an account.

### Output example

The following values come from the first item of a successful 50-result ZIP search. This is a shortened selection of fields from the actual output record; the dataset also includes the other fields in the table above.

```json
{
  "propertyId": 121243941,
  "listingId": 223846331,
  "mlsId": "1088464",
  "url": "https://www.redfin.com/FL/Mims/4109-Sweet-Bay-Dr-32754/home/121243941",
  "offerType": "sale",
  "mlsStatus": "Active",
  "price": 340000,
  "beds": 3,
  "baths": 2,
  "sqFt": 1726,
  "pricePerSqFt": 197,
  "lotSize": 36155,
  "streetLine": "4109 Sweet Bay Dr",
  "city": "Mims",
  "state": "FL",
  "zip": "32754",
  "latitude": 28.668593,
  "longitude": -80.8638018,
  "yearBuilt": 1957,
  "daysOnMarket": 2,
  "photoCount": 32,
  "resultIndex": 1,
  "foundFromSearchUrl": "https://www.redfin.com/zipcode/32754",
  "zoomQuadrantSequence": []
}
```

The same 50-result run returned a `propertyId`, property URL, offer type, price, street, city, state, ZIP, bedroom count, and bathroom count on every row. Floor area appeared on 96% of rows. That run is a sample from one area at one point in time; other areas and property categories can have different coverage.

### Use cases

- **Market inventory tracking:** Save a daily set of active properties in a defined ZIP code or viewport. Match records by `propertyId` to identify additions and removals.
- **Price monitoring:** Compare `price` for the same property across runs to find changes. Retain the original dataset for each date because the search result reflects what was visible at collection time.
- **Neighborhood research:** Group listings by `city`, `zip`, or `location`, then compare bedroom counts, floor areas, and asking prices. The coordinates allow a map view without extracting addresses again.
- **Rental supply checks:** Use rental search URLs to collect available buildings and units. The minimum and maximum fields keep rent and size ranges explicit.
- **Recently sold research:** Use a sold filter URL to capture public transaction search results, then distinguish current sold listings with `offerType` and `mlsStatus`.
- **Listing quality checks:** Monitor the presence of photos, tour flags, descriptions, and key property dimensions across a set of listings.

### How much does it cost to scrape Redfin?

The configured result price is $1.00 per 1,000 records, or $0.001 for each pushed result. At that rate, 50 results account for $0.05 in result charges, 1,000 results for $1.00, and 5,000 results for $5.00. Your run dashboard also shows resource and connection usage. Check the price displayed when starting a run because account charges and Store prices can change.

Only emitted dataset records count toward the result limit. A URL that yields no listings does not produce result rows. A large map can require more source requests than a small ZIP search, particularly when it must be divided into quadrants. The `maxResults` setting gives a straightforward cap on the number of paid result events. For an initial evaluation, start with 50 or 100 results and expand after you check the output fields.

### Tips for best results

Use a search URL copied after applying the filters you want. The actor follows the endpoint parameters embedded in the page, including the category and map viewport. A short URL can be useful for a whole ZIP or city; a viewport URL is more precise for a custom geographic box. If you need only one type of property, select that type before copying the URL.

Leave `zoomIn` enabled for broad, dense maps. A search response can contain up to 350 homes per page, and the site exposes at most nine pages for a map. When the ninth page is still full, the actor can split the geographic bounds into four quadrants, search each one, and deduplicate properties that fall on an edge. Splitting continues only where the cap is still reached, with a safety depth to prevent unbounded requests. `zoomQuadrantSequence` records the path used for a listing.

For a quick sample, set `maxResults` below 350. The actor can then use the listing data already embedded in the first page and stop without fetching additional result pages. For a complete export of a large area, set the limit to `0` and allow more time. A narrow area or detailed filters will usually need fewer requests than a major metropolitan map. Combine several focused URLs when you need a known set of neighborhoods.

Use `propertyId` as the main property key. `listingId` can change when a property is relisted, and rental records can have a separate `rentalId`. Keep `foundFromSearchUrl` with the row to preserve the search context. If the same property appears in overlapping searches during one run, it is pushed once. Across separate runs, deduplicate or compare using the stable key in your own data store.

### Limits

The actor reads public search results, not full property detail pages. The output includes fields available in the search response; it does not promise every attribute visible after opening an individual home page. Some records omit bedroom and bathroom counts, floor area, association amount, agent name, photo count, or remarks. Land and other nonstandard property records can have especially sparse interior details. Rental records have a different structure and often provide ranges rather than a single bed count, bath count, area, or rent. Null indicates that the search response did not supply a value.

A result page can be capped at 350 homes, and a map search can stop after nine pages. The quadrant option addresses this for searches with usable geographic bounds, but a highly dense area can still reach the maximum split depth. In that case the run logs a cap warning and the dataset may be incomplete. Divide the area manually or apply tighter filters if complete coverage is essential. Map edges can cause the same property to appear in adjacent quadrants; the run removes duplicates by identifier.

Search pages and their endpoint formats can change. The actor requires the page's embedded response cache; it reports an error when the expected listing endpoint is missing rather than returning an empty success. Blocks and rate limits can also occur. The connection uses US residential access because ordinary data center requests were rejected during verification. Running a smaller search first is the best way to confirm that a new filter URL is supported.

A sold search can include surrounding properties in the site's own response. The actor excludes records without a listing status from that response. Historic `soldDate` values may also be present on active listings, so do not treat the presence of a date alone as proof that the current listing is sold. The source can revise listing text, prices, availability, and status between runs; each dataset is a snapshot of the search at collection time.

### Automated workflows

A workflow can pass one or more saved map URLs as input, wait for the run to finish, and read the default dataset. For example: “Collect up to 100 active homes from this ZIP search and return the price, beds, baths, floor area, and URL for each property.” Another example: “Run this recently sold search every week, retain the dataset for each week, and compare prices and property IDs.”

For repeatable comparisons, keep the search URL and the run date with the downloaded dataset. A new run using the same URL can reflect changes in inventory and filters maintained by the site. If an area is unusually broad, make the workflow provide several focused URLs and set a practical `maxResults` cap. The actor's flat fields are suitable for tabular storage and simple filtering without unpacking nested source objects.

### FAQ

#### Is it legal to collect these listings?

The actor handles publicly visible search data. Check the site's terms and the rules that apply to your use, including privacy and data protection rules. Use the results for legitimate research and avoid personal data misuse. This actor does not access account-only pages or collect private contact details.

#### Do I need to set up a proxy?

The default connection is configured for a US residential route because the site rejected ordinary data center requests in verification. Most users can keep the default setting. If your account has a custom connection arrangement, the input exposes `proxyConfiguration` for that setting. Connection availability and charges are shown by the platform.

#### How fast is a run?

A verified 50-result ZIP search completed in about 12 seconds after its build was ready. Time varies with area size, page count, rate limits, and whether the map must split. A large unlimited search can take substantially longer than a 50-result sample. The run log reports progress and any failed search URLs.

#### Can I schedule and monitor it?

Yes. Save the input and set a recurring schedule in the platform. Check each run's status, item count, and logs. For market history, retain each dated dataset rather than replacing old data with the newest values.

#### Can I export to a spreadsheet?

Yes. The default dataset can be downloaded as CSV or Excel, as well as JSON. A spreadsheet works well for sorting by price, filtering by ZIP or status, and making simple comparisons. Keep identifier columns as text if your spreadsheet software changes their formatting.

#### Why is a field empty?

The source search response may not include it for that listing or property category. Rental listings, for example, can have `propertyName` and `numAvailableUnits` while sale listings have `listingId` and `yearBuilt`. Some fields also depend on the data supplied by a particular listing. Null is preferable to an invented value.

#### Will this return every home in a very large area?

It attempts to page through results and split a saturated map, but no finite map strategy can guarantee completeness after the split depth is reached or if the site changes access rules. Check the run log for a cap warning. Use smaller map boxes or more specific filters when you need a complete inventory.

### Integrations

The dataset can be read through the platform API, downloaded on completion, or passed to another step through a webhook. Common patterns include scheduled exports, spreadsheet updates, database imports, and alerts when the inventory or price of a tracked property changes. The output is flat and uses stable property identifiers, so downstream steps can compare runs directly.

### Support

Open an issue on the Issues tab; we reply within 24h and add fields on request. Include the search URL, run link, and a short description of the missing or unexpected data so the behavior can be reproduced.

# Actor input Schema

## `searchUrls` (type: `array`):

Redfin map search URLs for homes for sale, recently sold homes, or rentals. Keep the filters and viewport in each URL.

## `zoomIn` (type: `boolean`):

Split a map into four smaller areas when the result list reaches the nine-page search cap.

## `maxResults` (type: `integer`):

Maximum number of unique listings across all search URLs. Set 0 for no result cap.

## `proxyConfiguration` (type: `object`):

Connection configuration for search requests. A US residential connection is used for reliable access.

## Actor input object example

```json
{
  "searchUrls": [
    {
      "url": "https://www.redfin.com/zipcode/32754/filter/viewport=28.61051:28.50029:-81.21071:-81.41155,no-outline"
    }
  ],
  "zoomIn": true,
  "maxResults": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All unique property listings in the default dataset.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchUrls": [
        {
            "url": "https://www.redfin.com/zipcode/32754/filter/viewport=28.61051:28.50029:-81.21071:-81.41155,no-outline"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mlg14/redfin-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "searchUrls": [{ "url": "https://www.redfin.com/zipcode/32754/filter/viewport=28.61051:28.50029:-81.21071:-81.41155,no-outline" }] }

# Run the Actor and wait for it to finish
run = client.actor("mlg14/redfin-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchUrls": [
    {
      "url": "https://www.redfin.com/zipcode/32754/filter/viewport=28.61051:28.50029:-81.21071:-81.41155,no-outline"
    }
  ]
}' |
apify call mlg14/redfin-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mlg14/redfin-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/62brjKyrKiNIxC57n/builds/TEFXDYDQSMNecCb8V/openapi.json
