Homes.com All Address Scraper
Under maintenancePricing
from $1.00 / 1,000 results
Homes.com All Address Scraper
Under maintenanceA barebones scraper for homes.com that will allow you to find the address of any and every property whether they're currently listed or not. Use for your mailing campaigns or door-to-door sales.
Pricing
from $1.00 / 1,000 results
Rating
0.0
(0)
Developer
Alex Storey
Maintained by CommunityActor stats
1
Bookmarked
15
Total users
2
Monthly active users
9 days ago
Last modified
Categories
Share
Homes.com Address Scraper
Paste a homes.com search or map URL, get back every address in that area.
Deployed as the Apify Actor imalex/homes-address-scraper, built from this repo's main branch.
How it works
- Zoom the homes.com map to the neighborhood you want and copy the URL
(it looks like
https://www.homes.com/all-inventory/?bb=l0k5vq2m7K382wH— thebbparameter is the map bounding box). - Pass it as
startUrl. - The Actor reads page 1, detects how many pages exist, then walks them all, deduplicating by listing URL.
Each result row:
{"address": "4134 Thornhill Ln, Saint Paul, MN 55127","street": "4134 Thornhill Ln","city": "Saint Paul","state": "MN","zip": "55127","price": "$450,000","estimatedPrice": "","beds": "2","baths": "2","sqft": "2388","listingUrl": "https://www.homes.com/property/...","imageUrl": "https://imagescdn.homes.com/..."}
The unblocker requirement (important)
homes.com sits behind Akamai Bot Manager. Every one of these gets a 403
"Access Denied" page served from edgesuite.net:
- plain HTTP requests, however good the headers
- headless Chromium, on datacenter or residential proxies
- headless Firefox
- a real Chrome binary running headless
Even robots.txt returns 403. The protection is domain-wide and
fingerprint-based, so requests must be routed through a commercial unblocking
API. Set provider and apiKey accordingly:
provider | Service | Notes |
|---|---|---|
brightdata | Bright Data Web Unlocker | Cheapest per request. Also needs brightDataZone. |
scraperapi | ScraperAPI | Uses the ultra_premium tier. |
scrapedo | Scrape.do | Uses the super tier. |
scrapingbee | ScrapingBee | Uses the stealth_proxy tier. |
direct | — | No unblocker. Debug only; always blocked. |
A typical neighborhood is ~10 pages, so ~10 unblocker requests per run.
Bright Data requests are sent with render: true, which runs a real browser —
homes.com populates its listings client-side, so without it the response is a
near-empty pre-hydration shell. Rendering costs ~50 seconds per page, and the
unblocker occasionally answers 502 under that load, so pages are retried up to
three times with backoff.
Instead of passing secrets on every call, set them once as Actor environment
variables: UNBLOCKER_PROVIDER, UNBLOCKER_API_KEY, BRIGHTDATA_ZONE.
Input fields take precedence over env vars when both are present.
Two things that will waste an hour if you don't know them:
- Environment variables are baked into the build. Changing them on the Source tab does nothing to existing builds — you must rebuild the Actor afterwards or runs keep reporting the old (or missing) values.
- Values typed into the Input form on the Run tab only apply to runs started from the Apify Console. Runs started via the API — which is what any app integration does — never see them. Those need environment variables.
For Bright Data specifically, apiKey is the account API token from
Account settings → API tokens. It is not the zone password shown on the
zone's own page; using that returns 401 Invalid token.
Input
| Field | Type | Default | Description |
|---|---|---|---|
startUrl | string | — | Required. The homes.com search/map URL. |
maxPages | integer | 0 | 0 scrapes every page found. Set a number to cap it. |
provider | string | brightdata | Unblocker to route through. |
apiKey | string (secret) | — | Provider API key. |
brightDataZone | string | — | Bright Data Web Unlocker zone name. |
delayMs | integer | 1500 | Politeness delay between pages. |
Failure behaviour
The Actor throws rather than finishing quietly, so a broken run shows up as FAILED in Apify instead of "SUCCEEDED with 0 results":
- Akamai served a block page → error naming the provider and the HTTP status.
- Page 1 fetched fine but parsed 0 listings → error saying the markup changed.
- Zero rows saved overall → error.
A single mid-run page failure is logged as a warning and skipped; the pages that did work are still saved.
Local development
$npm install
Put an input file at storage/key_value_stores/default/INPUT.json:
{"startUrl": "https://www.homes.com/all-inventory/?bb=l0k5vq2m7K382wH","maxPages": 2,"provider": "brightdata","apiKey": "...","brightDataZone": "..."}
Then:
$npm start
Deployment
Pushing to main on GitHub triggers an Apify rebuild — there is no apify push
step. Confirm the new build is tagged latest on the Actor's Builds tab.
Source layout
src/main.js— paging, deduplication, saving, loud failure handling.src/parse.js— JSON-LD parsing with an HTML-selector fallback.src/unblocker.js— provider adapters and Akamai block detection.
Notes on the markup (verified July 2026)
- The JSON-LD
ItemListsits at@graph[0].mainEntity, and each listing'saddress/numberOfBedrooms/floorSizehang offitem.mainEntity— not off the item itself.parse.jssearches recursively so a future re-nesting won't break it. - HTML fallback selectors still valid:
li.placard-container,address,.price-container .price-content,ul.detailed-info-container li. - Page count comes from the "Page 1 of 18" text.
- Only actively-listed homes carry an
offersnode. For everything else homes.com shows its own valuation as "Est $470,551" on the card, which lands inestimatedPrice— never inprice, so an algorithmic estimate is never mistaken for an asking price. Expect most rows beyond page 1 to have an estimate and no price:/all-inventory/means every home in the area, not just the ones for sale.


