Homes.com All Address Scraper avatar

Homes.com All Address Scraper

Under maintenance

Pricing

from $1.00 / 1,000 results

Go to Apify Store
Homes.com All Address Scraper

Homes.com All Address Scraper

Under maintenance

A 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

Alex Storey

Maintained by Community

Actor stats

1

Bookmarked

15

Total users

2

Monthly active users

9 days ago

Last modified

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

  1. 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 — the bb parameter is the map bounding box).
  2. Pass it as startUrl.
  3. 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:

providerServiceNotes
brightdataBright Data Web UnlockerCheapest per request. Also needs brightDataZone.
scraperapiScraperAPIUses the ultra_premium tier.
scrapedoScrape.doUses the super tier.
scrapingbeeScrapingBeeUses the stealth_proxy tier.
directNo 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

FieldTypeDefaultDescription
startUrlstringRequired. The homes.com search/map URL.
maxPagesinteger00 scrapes every page found. Set a number to cap it.
providerstringbrightdataUnblocker to route through.
apiKeystring (secret)Provider API key.
brightDataZonestringBright Data Web Unlocker zone name.
delayMsinteger1500Politeness 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 ItemList sits at @graph[0].mainEntity, and each listing's address / numberOfBedrooms / floorSize hang off item.mainEntitynot off the item itself. parse.js searches 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 offers node. For everything else homes.com shows its own valuation as "Est $470,551" on the card, which lands in estimatedPrice — never in price, 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.