Scrape Apple Maps Businesses for Lead Generation (2026)
Pull local businesses from Apple Maps with phone, website, address, rating and hours using Thirdwatch's Apple Maps Scraper. Python and CRM recipes inside.

Thirdwatch's Apple Maps Scraper turns a plain-language search such as "roofing contractors in Phoenix, AZ" into a structured lead list: business name, category, full address, GPS coordinates, phone, website, rating with its provider, review count, opening hours and amenities. Built for B2B sales teams, lead-generation agencies and local-service marketers who want a contact source their competitors are not already exhausting.
Why scrape Apple Maps for lead generation
Apple Maps is the map layer nearly every iPhone owner touches by default, and it is a lead source most prospecting teams have never queried. Apple reported more than 2.35 billion active devices in its January 2025 results, and Maps is the preinstalled default on all of them. Business owners who ignore their Apple listing lose walk-in customers, so the ones who maintain it tend to be the ones actively investing in growth — exactly the profile a local-services seller wants at the top of a call list.
The job-to-be-done is concrete. An agency selling websites to home-service contractors wants 3,000 roofers, plumbers and HVAC firms across five Sun Belt metros, each with a phone number and a rating, so reps can open with something specific. A payments company wants merchants in a city sorted by whether they already accept Apple Pay. A field-sales team wants every independent clinic within driving distance of a rep's home base. All three reduce to the same shape: query plus location plus a result cap. The Actor returns that as clean JSON with phone, website, rating, ratingCount and amenities already parsed, ready for a CRM import or a pandas filter.
How does this compare to the alternatives?
There are three realistic ways to get Apple Maps business data into a pipeline, and they differ mostly in how much of your week they consume.
| Approach | Cost model | Reliability | Setup time | Maintenance |
|---|---|---|---|---|
| DIY Python against Apple's map endpoints | Your engineering time | Breaks when response shapes shift | Days of reverse engineering | You own every change |
| Generic scraping API | Subscription plus per-request fees | Returns raw HTML or JSON you must still parse | Hours | You maintain the parser |
| Thirdwatch Apple Maps Scraper | Pay per place returned | Production-tested, parsed place cards | Five minutes | Thirdwatch tracks Apple-side changes |
Apple does publish MapKit JS for developers, but it is licensed for building map interfaces rather than exporting business lists, and it requires a paid developer account plus token signing before you see a single record. The Apple Maps Scraper actor page skips that entirely: you type a query the way you would type it into the Maps search bar and you get rows.
How to scrape Apple Maps for lead generation in 5 steps
Step 1: How do I authenticate against Apify?
Create a free Apify account at apify.com, open Settings then Integrations, and copy your personal API token. Every example below reads it from the environment:
export APIFY_TOKEN="apify_api_xxxxxxxxxxxxxxxx"No Apple credential is involved anywhere in this workflow.
Step 2: How do I pull one category in one city?
Pass a queries array written the way you would type into Apple Maps, with the location inside the query string, and cap the run with maxResults.
import os, requests, pandas as pd
ACTOR = "thirdwatch~apple-maps-scraper"
TOKEN = os.environ["APIFY_TOKEN"]
resp = requests.post(
f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items",
params={"token": TOKEN},
json={
"queries": ["roofing contractors in Phoenix, AZ"],
"maxResults": 120,
"language": "en-US",
"countryCode": "US",
"includePhotos": False,
"includeReviews": False,
},
timeout=900,
)
df = pd.DataFrame(resp.json())
print(f"{len(df)} places, {df.phone.notna().sum()} with phone, "
f"{df.website.notna().sum()} with website")Turning includePhotos and includeReviews off keeps the payload small when you only want contact data. Leave them on when you want imagery for a pitch deck.
Step 3: How do I sweep several categories across several metros?
Send the whole matrix in a single queries array. The Actor de-duplicates on placeId across every query in the run, so overlapping categories do not inflate your list.
CATEGORIES = ["roofing contractors", "HVAC contractors", "plumbers"]
CITIES = ["Phoenix, AZ", "Tucson, AZ", "Las Vegas, NV",
"Albuquerque, NM", "El Paso, TX"]
queries = [f"{cat} in {city}" for city in CITIES for cat in CATEGORIES]
resp = requests.post(
f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items",
params={"token": TOKEN},
json={
"queries": queries,
"maxResults": 150,
"language": "en-US",
"countryCode": "US",
"includePhotos": False,
},
timeout=1800,
)
df = pd.DataFrame(resp.json()).drop_duplicates(subset=["placeId"])
print(f"Sun Belt sweep: {len(df)} unique businesses across {df.city.nunique()} cities")Fifteen queries at 150 results each is a comfortable single run. The queries array accepts up to 100 entries, which is usually a whole quarter's territory plan in one job.
Step 4: How do I rank the list so reps call the best leads first?
Ratings arrive on the provider's own scale, so always sort on ratingOutOf5 rather than the raw rating field. A Yelp-sourced 4 and an Apple-sourced 80 both normalise to 4.0.
leads = df[df.phone.notna() & (df.phone != "")].copy()
leads["has_site"] = leads.website.notna() & (leads.website != "")
leads["credibility"] = (
leads.ratingOutOf5.fillna(0) * 20 + leads.ratingCount.fillna(0).clip(upper=200) / 4
)
# Businesses with reviews but no website are the classic web-agency pitch
pitch_list = leads[(~leads.has_site) & (leads.ratingCount.fillna(0) >= 5)]
pitch_list = pitch_list.sort_values("credibility", ascending=False)
print(pitch_list[["name", "phone", "city", "ratingOutOf5", "ratingCount"]].head(25))ratingProvider tells you who supplied the score — Yelp, Apple, TripAdvisor — which is worth keeping in the export so a rep never quotes the wrong platform on a call.
Step 5: How do I hand the list to the sales team?
Flatten the nested fields you care about and write a CSV your CRM importer will accept.
export = pd.DataFrame({
"company": leads.name,
"phone": leads.phoneFormatted.fillna(leads.phone),
"website": leads.website,
"street": leads.street,
"city": leads.city,
"state": leads.stateCode,
"postal_code": leads.postalCode,
"country": leads.countryCode,
"category": leads.category,
"rating": leads.ratingOutOf5,
"rating_count": leads.ratingCount,
"rating_source": leads.ratingProvider,
"timezone": leads.timezone,
"apple_maps_url": leads.appleMapsUrl,
"external_id": leads.placeId,
})
export.to_csv("apple_maps_leads.csv", index=False)Keep timezone in the export — it is the IANA zone for the place, so a dialer can avoid calling an Arizona roofer at 6am. Then put the whole job on the Apify scheduler at a monthly cadence and diff placeId sets to catch newly opened businesses.
Sample output
Each dataset item is one place. Here are two records trimmed to the lead-generation fields, with photos and reviews switched off.
[
{
"placeId": "I15FF30DE01EC121F",
"name": "Daydreamer Coffee",
"category": "Coffee Shop",
"categories": ["Dining", "Coffee Shop", "Cafe"],
"mapsCategoryId": "dining.cafe.coffee_shop",
"placeType": "BUSINESS",
"phone": "+17404000238",
"phoneFormatted": "(740) 400-0238",
"website": "https://www.daydreamer.coffee/",
"address": "80 Rainey St, Austin, TX 78701, United States",
"street": "80 Rainey St",
"neighborhood": "Downtown",
"city": "Austin",
"stateCode": "TX",
"postalCode": "78701",
"countryCode": "US",
"latitude": 30.2592149,
"longitude": -97.7389039,
"timezone": "America/Chicago",
"rating": 4,
"maxRating": 5,
"ratingOutOf5": 4.0,
"ratingCount": 27,
"ratingProvider": "Yelp",
"amenities": [
{ "name": "Accepts Apple Pay", "id": "crossbusiness.payments.applepay", "available": true }
],
"appleMapsUrl": "https://maps.apple.com/place?place-id=I15FF30DE01EC121F",
"query": "coffee in Austin, TX",
"scrapedAt": "2026-09-08T09:14:02Z"
}
]placeId is the stable key for upsert and month-over-month diffing. phone is E.164 for dialers, phoneFormatted for display. stateCode, postalCode and countryCode come pre-split, so no address parsing library is required. ratingOutOf5 is the field to compare across records because rating follows whichever scale ratingProvider uses.
Common pitfalls
Four things trip up first-time Apple Maps lead pipelines. Queries without a location resolve against the server's own network location, which is almost never the market you meant — always write "in Phoenix, AZ" or set latitude and longitude with radiusKm. Comparing raw ratings across records mixes a Yelp 0-to-5 score with Apple's own 0-to-100 score; sort on ratingOutOf5 instead. Expecting unlimited depth from one broad query: Apple caps a single search at roughly 60 places, and while grid tiling pushes far past that, a metro-wide generic term still tops out — split by neighbourhood or by narrower category instead. De-duplicating on name breaks for franchises, where twelve locations share one string; placeId is the only safe key.
Thirdwatch's Actor handles the tiling, the pacing and the de-duplication for you, and an empty result set finishes as a clean successful run with a status message rather than a failure, so a scheduled sweep never fails just because one niche query found nothing.
Related use cases
Frequently asked questions
Can you scrape business phone numbers from Apple Maps?
Yes. Every place card returns phone in E.164 form and phoneFormatted for display, alongside website and the full postal address. Coverage is strongest in service categories such as contractors, clinics and salons, where most listings publish a direct line.
How many businesses can one Apple Maps search return?
A single Apple Maps search returns roughly 20 to 60 places. Set maxResults higher and the Actor tiles the resolved search area into a grid, re-searches each cell and de-duplicates on placeId, so one query can yield several hundred distinct businesses.
Do I need an Apple developer account or MapKit key?
No. The Actor needs no Apple account, no MapKit JS token and no API key. You pass plain-language queries such as roofing contractors in Phoenix, AZ and receive structured place records, so there is no Apple-side quota or billing to configure.
Is the lead data different from Google Maps?
Yes, meaningfully. Apple Maps blends Apple's own listings with Yelp, TripAdvisor and Foursquare data, so ratings, photos and even which businesses appear differ. Many independents claimed on Apple but neglected on Google surface here with fresher hours and contact details.
How do I keep the lead list current across monthly runs?
Use placeId as the primary key. It is Apple's stable identifier for a place, so re-running the same queries next month and diffing the placeId set gives you new openings, while changed phone or website values on existing ids flag records worth re-verifying.
Related
100 free credits, no credit card.
About 30 real searches. Add the MCP to Claude or Cursor in two minutes.