Realtor.com Agent Scraper Recent Sold Listings
Pricing
from $2.99 / 1,000 results
Realtor.com Agent Scraper Recent Sold Listings
Realtor.com Agent Scraper extracts agent profiles and recent sold listings, including agent names, brokerage details, locations, property addresses, sale prices, dates, and listing URLs. Ideal for agent research, lead generation, market analysis, and real estate intelligence.
Pricing
from $2.99 / 1,000 results
Rating
0.0
(0)
Developer
SimpleAPI
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Realtor.com Agent Scraper — Recent Sold Listings
Realtor.com Agent Scraper — Recent Sold Listings turns a list of ZIP codes into a directory of real estate agents, each with contact details, active-listing price bands, and preview rows for their recently-sold properties — all from data Realtor.com's own agent-search API already returns in one request. It's built for real estate lead-generation teams sourcing agent contacts by territory, brokerages researching competitor agent activity and price positioning by ZIP, and market researchers who want a per-agent view of active-listing volume alongside recent-sale activity. No Realtor.com login is required. Every section below documents an input, an output field, or exactly what the recent-sold preview data does and doesn't include.
What is Realtor.com Agent Scraper — Recent Sold Listings?
This Actor searches Realtor.com's agent-search GraphQL API by ZIP code, fetches each matched agent's detailed branding profile, and — from data the search step already returned but a plain agent scraper would discard — appends a price-band summary, an active-listing snapshot, and child rows for recently-sold listing previews.
Key capabilities, read from the source:
- Zero extra requests for the added fields.
extract_listing_features()readsprice_range_min,active_listing_count,last_listing_date, and the recent-sold preview data straight out of theSearchAgentspayload the base agent search already fetches — enablingincludeActiveStats/includeRecentSalesadds no new HTTP calls, only reshapes data already in hand. - Recently-sold listings as honest previews, not fabricated full records.
build_recent_sold_children()builds one child row per sold-listing preview with only what Realtor.com's preview payload actually carries — beds, baths, city, state, and a photo. The source explicitly notes the preview payload carries no price, address, or sale date, and none of those fields are invented to fill the gap. - A soft-block guard keyed on the expected response container. A
SearchAgentsorAgentBrandingProfileresponse that returns HTTP 200 but is missing its expected data container (search_agents/agent_branding) is treated asSoftBlockError— evidence of a WAF shell response — and triggers proxy rotation, rather than being mistaken for "no more data." - Chrome-impersonated requests against an Akamai/PerimeterX-class target. Requests use
curl_cffiChrome TLS impersonation rather than a plain HTTP client, specifically because realtor.com's anti-bot layer is fingerprint-sensitive. - Sticky residential proxy escalation. The proxy ladder starts direct, escalates to datacenter then residential on a block, and — once residential is reached — stays there (
sticky_residential) for the remainder of the run rather than testing lower tiers again. - Uncharged mirror of child rows. Recent-sold listing child rows are pushed to the charged default dataset and separately mirrored to an uncharged per-run child dataset for convenient standalone export.
What data can I extract with Realtor.com Agent Scraper — Recent Sold Listings?
Every field below is read directly from extract_agent_details(), extract_listing_features(), and build_recent_sold_children() in src/main.py — the dataset's default view surfaces all 29 as table columns.
Agent rows (type: "agent")
| Field | Example Value | Notes |
|---|---|---|
agent_name | "Jane Doe, agent" | Includes a realtor-vs-non-realtor designation |
website | https://www.realtor.com/realestateagents/abc123 | The agent's Realtor.com profile page, constructed from their fulfillment_id — not necessarily their personal or brokerage website (see FAQ) |
office_phone / mobile_phones | phone numbers | Parsed from the agent's phone list by type |
areas_serviced | comma-separated area names | |
office_company_name / company_website | brokerage/office identity | Falls back from office to broker when office data is absent |
listing_count / sold_count | 12 / 8 | Active listings / annual sold count |
review_count | 24 | |
agent_photo | avatar URL | |
price_range_min / price_range_max | 450000 / 1200000 | Annual combined price band; null when includeActiveStats is off |
active_listing_count / active_listing_min / active_listing_max / last_listing_date | active-listing snapshot | null when includeActiveStats is off |
recent_sold_listings_count / more_sold_available | 10 / true | How many sold previews were emitted for this agent, and whether more exist |
Recent-sold listing child rows (type: "listing", is_child: true)
| Field | Example Value | Notes |
|---|---|---|
listing_beds / listing_baths | 3 / 2 | |
listing_city / listing_state | "Newark" / "NJ" | |
listing_photo | photo URL | |
agent_name / office_company_name / zip_code | inherited from the parent agent row | Links the listing back to its agent |
No price, street address, or sale date is ever present on a listing child row — Realtor.com's own preview payload for recently-sold listings simply doesn't carry those fields.
Why not build this yourself?
Realtor.com's public site is protected by an Akamai/PerimeterX-class anti-bot layer that fingerprints plain HTTP clients via TLS/JA3 signature, so a naive scraper using a standard HTTP library gets blocked at the network layer before it ever reaches the application logic — this is exactly why the Actor uses Chrome-impersonated requests rather than a plain client. On top of that, a block from this target often looks like a normal HTTP 200 response with an empty or missing data container rather than a clear error code, so a scraper that only checks the status code can silently treat a block as "no results" and quietly under-report. This Actor validates that the expected response container is actually present before accepting a page as successfully fetched, and separately, it reuses the same search-step payload for the price-band and recent-sold data rather than making a second, redundant request per agent.
How to use data extracted from Realtor.com Agent Scraper — Recent Sold Listings?
Real estate lead generation
Search your target ZIP codes and filter by sold_count/active_listing_count to identify the most active agents in a territory, using office_phone/mobile_phones for direct outreach.
Brokerage competitive research
Compare price_range_min/price_range_max and active_listing_count across agents in a ZIP to understand price positioning and production volume by competitor before entering or expanding in a market.
Market activity tracking
Use recent_sold_listings_count alongside listing_city/listing_state across a batch of ZIPs to get a rough sense of recent transaction activity and geographic spread, without needing per-listing sale prices.
AI agents and lead-scoring pipelines
Because every listing child row inherits agent_name and zip_code from its parent, an agent can reconstruct the full agent-to-listings relationship directly from the flat dataset without a separate join step.
🔼 Input sample
| Parameter | Required | Type | Description | Example Value |
|---|---|---|---|---|
zipCodes | Yes | array | US ZIP codes to pull agents from. | ["90211", "33139"] |
maxItemsPerZipcode | No | integer (1–10000) | Agents to collect per ZIP. Default 10. | 25 |
sortOrder | No | string enum | RELEVANT_AGENTS, RATINGS_REVIEWS, MOST_SALES, MOST_RECENT_ACTIVITY, TESTIMONIALS_RECOMMENDATIONS. Default "RELEVANT_AGENTS". | "MOST_SALES" |
includeActiveStats | No | boolean | Add price band + active-listing summary. No extra requests. Default true. | true |
includeRecentSales | No | boolean | Emit recent-sold preview child rows. Default true. | true |
maxListingsPerAgent | No | integer (0–100) | Cap on sold-preview child rows per agent. 0 = unlimited. Default 10. | 5 |
maxRetries | No | integer (1–10) | Per-request retries before rotating the proxy IP. Default 3. | 3 |
proxyConfiguration | No | object | Starts direct; auto-escalates to datacenter then sticky residential on a block. | {"useApifyProxy": false} |
{"zipCodes": ["90211", "33139"],"maxItemsPerZipcode": 25,"sortOrder": "MOST_SALES","includeActiveStats": true,"includeRecentSales": true}
Common pitfall: website on an agent row is always the constructed Realtor.com profile page URL, never a personal or brokerage domain — the underlying API response does carry a separate branding.website field, but this Actor's extraction doesn't surface it as website. Use company_website for the brokerage/office's own domain instead.
🔽 Output sample
Output is one JSON row per agent, plus one additional row per recent-sold listing preview, pushed to the run's default dataset and charged as one row_result event per row.
{"zip_code": "90211","agent_name": "Jane Doe, agent","website": "https://www.realtor.com/realestateagents/abc123","listing_count": 12,"sold_count": 8,"office_phone": "310-555-0100","mobile_phones": "310-555-0199","office_company_name": "Acme Realty","review_count": 24,"type": "agent","is_child": false,"price_range_min": 450000,"price_range_max": 1200000,"active_listing_count": 5,"recent_sold_listings_count": 10,"more_sold_available": true}
A recent-sold listing child row:
{"type": "listing","is_child": true,"zip_code": "90211","agent_name": "Jane Doe, agent","listing_beds": 3,"listing_baths": 2,"listing_city": "Beverly Hills","listing_state": "CA","listing_photo": "https://ap.rdcpix.com/..."}
How do you filter and target specific agents and listings?
Sort order is the primary agent-ranking lever. sortOrder: "MOST_SALES" surfaces the highest-volume agents first for lead-gen prioritization; RATINGS_REVIEWS is better suited to a quality-focused referral search.
Turn off what you don't need for a leaner run. Both includeActiveStats and includeRecentSales cost nothing extra to enable (same underlying data already fetched), so there's little reason to disable them unless you specifically want a minimal output schema.
Cap listing volume per agent, not per ZIP. maxListingsPerAgent controls sold-preview rows per individual agent — a ZIP with many highly-active agents can still produce a large total row count even with a low per-agent cap, since the cap multiplies by however many agents maxItemsPerZipcode returns.
Three real examples:
{ "zipCodes": ["90211"], "sortOrder": "MOST_SALES", "maxItemsPerZipcode": 20, "maxListingsPerAgent": 5 }
Top producers in one ZIP with a modest sold-listing sample each.
{ "zipCodes": ["90211", "33139", "10001"], "includeRecentSales": false, "maxItemsPerZipcode": 50 }
Multi-ZIP agent directory without sold-listing rows — leaner, agent-only output.
{ "zipCodes": ["33139"], "sortOrder": "RATINGS_REVIEWS", "includeActiveStats": true }
Quality-ranked agent search with price-band context for referral sourcing.
▶️ Want to try other scrapers?
| Scraper | What it extracts |
|---|---|
| Apartments.com Scraper — Floor Plan & Units | Rental listings with floor plan detail |
| Google Maps Scraper by Radius | Local business listings by geographic radius |
| LinkedIn Company URL Mass Profile Finder | Bulk company URL discovery with executive contacts |
| Instagram Outreach Lead Scorer | Profiles scored for outreach fit |
How to extract Realtor.com agent data programmatically
This Actor runs as a standard Apify Actor call — one API call in, structured JSON dataset out, using your Apify API token.
Python example
from apify_client import ApifyClientclient = ApifyClient("<YOUR_API_TOKEN>")run = client.actor("<YOUR_USERNAME>/realtor-com-agent-scraper-recent-sold-listings").call(run_input={"zipCodes": ["90211", "33139"],"sortOrder": "MOST_SALES","maxItemsPerZipcode": 25,})for item in client.dataset(run["defaultDatasetId"]).iterate_items():if item.get("type") == "agent":print(item["agent_name"], item["office_phone"], item["sold_count"])
Export to spreadsheets or CRM
Every dataset supports one-click CSV/Excel export from the Apify Console. Filter on type/is_child to separate agent rows from sold-listing preview rows before loading into a CRM's Contact and Property objects respectively.
Is it legal to scrape Realtor.com agent data?
Scraping publicly visible Realtor.com agent listings is generally lawful, since this data is published for anyone to view without logging in — the underlying legal question was tested directly in hiQ Labs v. LinkedIn (9th Cir.), which held that scraping public, non-password-protected data does not violate the U.S. Computer Fraud and Abuse Act. Agent names, phone numbers, and photos are business-professional contact data typically used for legitimate business outreach, but check Realtor.com's own Terms of Service for commercial-scale usage, and consult legal counsel for high-volume commercial applications, particularly around outbound phone/SMS regulations (TCPA) if using phone numbers for calling or texting campaigns.
❓ FAQ
Why doesn't the website field show the agent's real personal website?
The website field is built from the agent's fulfillment_id as a Realtor.com profile page URL — the underlying API response does include a separate real branding.website field, but this Actor's field extraction doesn't surface it under website. Use company_website for the brokerage/office's own domain instead.
Why do sold-listing rows have no price or address?
Realtor.com's recently-sold preview payload — the same data this Actor reads to build those child rows — genuinely doesn't include price, street address, or sale date. Rather than fabricate those fields, the Actor emits only what's actually present: beds, baths, city, state, and a photo.
Does enabling includeActiveStats or includeRecentSales slow the run down?
No — both are read from the same SearchAgents response the base agent search already fetches for every agent; enabling them adds zero additional HTTP requests.
What does more_sold_available mean?
It's Realtor.com's own flag indicating there are more recently-sold listings for that agent beyond what the preview payload returned — it doesn't mean this Actor can fetch them; the preview list itself is capped by Realtor.com's response, further capped by your maxListingsPerAgent.
What happens if realtor.com blocks a request mid-run?
The Actor detects a WAF shell response (HTTP 200 with a missing expected data container) and rotates through a proxy ladder — direct, then datacenter, then sticky residential — retrying automatically rather than ending the run or silently returning empty data.
How does this compare to other Realtor.com agent scrapers?
As observed on the Apify Store on 2026-07-26, sian.agency/realtor-agent-scraper covers agent-level data but doesn't document combining active-listing price bands and recently-sold preview rows from a single request; ayk_6789/realtorpulse-listing-scraper focuses on listings rather than agents. This Actor's zero-extra-request enrichment approach is unique among these.
Does this work with AI agent frameworks?
Yes — call it as a standard HTTP endpoint via the Apify API from any agent framework capable of making an API call; there's no MCP-specific integration for this Actor.
Conclusion
Realtor.com Agent Scraper — Recent Sold Listings turns a ZIP code list into an agent directory enriched with price-band context and recent-sold activity — all pulled from a single search request per page, with honest preview-only sold-listing data rather than fabricated pricing. It fits lead generation, competitive brokerage research, and market activity tracking. Start a run from the Apify Console or the Apify API with your target ZIP codes to get your first agent export.