Apple Maps vs Google Maps Data: Coverage Compared (2026)
Measure how Apple Maps and Google Maps differ on business coverage, ratings and hours for any market, with a reproducible Python join on name and coordinates.

Apple Maps and Google Maps do not index the same businesses. Running Thirdwatch's Apple Maps Scraper and Google Maps Scraper over identical queries and joining the two on name plus coordinates gives you a defensible coverage audit for any market: which businesses each map is missing, where the ratings disagree, and whose opening hours are stale. Built for data teams, location-intelligence analysts and anyone deciding which place source to build on.
Why compare Apple Maps against Google Maps coverage
Picking a place-data source on reputation alone is how teams end up with silent gaps. Google Maps is the larger index by a wide margin, but "larger overall" does not mean "more complete for your 40 postcodes and your three categories". Consumer behaviour makes the question commercially real: BrightLocal's Local Consumer Review Survey has consistently found that around 98% of consumers use the internet to find information about local businesses, and on an iPhone the default surface for that lookup is Apple Maps.
The job-to-be-done splits three ways. A multi-location brand wants to know which of its own venues are missing or wrong on Apple, because that is invisible to a Google-only listings tool. A data team choosing a vendor wants an honest coverage delta for the specific verticals it sells into, not a marketing claim. A competitive analyst wants the union of both indexes, because a rival that appears on one map and not the other still exists. All three need the same artefact: two comparable extracts of the same queries, joined and diffed.
How does this compare to the alternatives?
You can get to a coverage number three ways, and only one of them survives being asked "show your working".
| Approach | Cost model | Reliability | Setup time | Maintenance |
|---|---|---|---|---|
| Manual spot-checking in both apps | Analyst hours | Anecdotal, unrepeatable | Immediate but never finishes | Redone from scratch every quarter |
| Official mapping APIs on both sides | Two billing accounts, per-call fees | Terms often restrict storing and comparing results | Days of contract and key setup | Two integrations to keep alive |
| Two Thirdwatch Actors on identical queries | Pay per place returned | Same parsed shape on both sides | Ten minutes | Thirdwatch tracks both sources |
The Apple Maps Scraper actor page and its Google counterpart both return flat records with a name, coordinates, phone, rating and hours, which means the join is a dataframe merge rather than a schema-mapping project.
How to audit Apple Maps against Google Maps coverage in 5 steps
Step 1: How do I set up both extracts?
Authenticate once. Both Actors sit behind the same Apify token.
export APIFY_TOKEN="apify_api_xxxxxxxxxxxxxxxx"Fix the query set before you run anything. Identical strings on both sides is the whole basis of the comparison.
import os, requests, pandas as pd
TOKEN = os.environ["APIFY_TOKEN"]
CITY = "Austin, TX"
CATEGORIES = ["coffee shops", "dentists", "yoga studios", "auto repair"]Step 2: How do I pull the Apple Maps side?
Pass every category as one queries array and let the Actor de-duplicate on placeId.
APPLE = "thirdwatch~apple-maps-scraper"
apple_resp = requests.post(
f"https://api.apify.com/v2/acts/{APPLE}/run-sync-get-dataset-items",
params={"token": TOKEN},
json={
"queries": [f"{c} in {CITY}" for c in CATEGORIES],
"maxResults": 200,
"language": "en-US",
"countryCode": "US",
"includePhotos": False,
"includeReviews": False,
},
timeout=1800,
)
apple = pd.DataFrame(apple_resp.json())
print(f"Apple: {len(apple)} places, {apple.rating.notna().mean():.0%} rated")Step 3: How do I pull the Google Maps side and normalise both?
Run the sibling Actor with the same strings, then reduce both frames to a shared comparison schema.
import re, unicodedata
GOOGLE = "thirdwatch~google-maps-scraper"
google_rows = []
for c in CATEGORIES:
r = requests.post(
f"https://api.apify.com/v2/acts/{GOOGLE}/run-sync-get-dataset-items",
params={"token": TOKEN},
json={"searchQuery": f"{c} in {CITY}", "maxResults": 100,
"language": "en", "region": "us"},
timeout=900,
)
google_rows.extend(r.json())
google = pd.DataFrame(google_rows).drop_duplicates(subset=["place_id"])
def key(name):
s = unicodedata.normalize("NFKD", str(name)).encode("ascii", "ignore").decode()
s = re.sub(r"\b(the|llc|inc|ltd|co)\b", "", s.lower())
return re.sub(r"[^a-z0-9]", "", s)
apple_cmp = pd.DataFrame({
"key": apple.name.map(key), "name": apple.name,
"lat": apple.latitude, "lng": apple.longitude,
"rating5": apple.ratingOutOf5, "ratings": apple.ratingCount,
"source_of_rating": apple.ratingProvider,
"phone_digits": apple.phone.fillna("").str.replace(r"\D", "", regex=True),
})
google_cmp = pd.DataFrame({
"key": google.name.map(key), "name": google.name,
"lat": google.latitude, "lng": google.longitude,
"rating5": google.rating,
"phone_digits": google.phone.fillna("").str.replace(r"\D", "", regex=True),
})Note that Apple exposes ratingOutOf5 as a normalised value precisely because the raw rating follows whichever scale ratingProvider uses. Google publishes a single 0-to-5 scale, so ratingOutOf5 is the only fair comparand.
Step 4: How do I join two maps with no shared identifier?
Match on the normalised name first, then confirm with a coordinate distance so two different "Downtown Dental" practices do not collapse into one.
import numpy as np
def metres(lat1, lng1, lat2, lng2):
r = 6_371_000
p1, p2 = np.radians(lat1), np.radians(lat2)
dp, dl = p2 - p1, np.radians(lng2 - lng1)
a = np.sin(dp / 2) ** 2 + np.cos(p1) * np.cos(p2) * np.sin(dl / 2) ** 2
return 2 * r * np.arcsin(np.sqrt(a))
pairs = apple_cmp.merge(google_cmp, on="key", suffixes=("_apple", "_google"))
pairs["gap_m"] = metres(pairs.lat_apple, pairs.lng_apple,
pairs.lat_google, pairs.lng_google)
matched = pairs[(pairs.gap_m <= 150) |
(pairs.phone_digits_apple.str[-10:] ==
pairs.phone_digits_google.str[-10:])]
apple_only = apple_cmp[~apple_cmp.key.isin(matched.key)]
google_only = google_cmp[~google_cmp.key.isin(matched.key)]
print(f"matched {len(matched)} | apple-only {len(apple_only)} | google-only {len(google_only)}")A 150-metre threshold works for dense urban blocks. Widen it to 300 metres for suburban and rural sweeps where a listing's pin may sit on the access road rather than the storefront.
Step 5: How do I report the coverage delta?
Turn the three buckets into the numbers a stakeholder actually asks for.
total = len(matched) + len(apple_only) + len(google_only)
report = {
"union_size": total,
"apple_recall": round((len(matched) + len(apple_only)) / total, 3),
"google_recall": round((len(matched) + len(google_only)) / total, 3),
"apple_exclusive": len(apple_only),
"google_exclusive": len(google_only),
"rating_disagreements": int((matched.rating5_apple.sub(
matched.rating5_google).abs() >= 0.5).sum()),
}
pd.Series(report).to_csv("coverage_audit.csv")Schedule both Actors on the same day through Apify's scheduler and the audit becomes a trend line rather than a one-off slide.
Sample output
The Apple side of a matched pair looks like this, trimmed to the fields the comparison uses.
[
{
"placeId": "I15FF30DE01EC121F",
"name": "Daydreamer Coffee",
"category": "Coffee Shop",
"categories": ["Dining", "Coffee Shop", "Cafe"],
"mapsCategoryId": "dining.cafe.coffee_shop",
"address": "80 Rainey St, Austin, TX 78701, United States",
"latitude": 30.2592149,
"longitude": -97.7389039,
"phone": "+17404000238",
"website": "https://www.daydreamer.coffee/",
"rating": 4,
"maxRating": 5,
"ratingOutOf5": 4.0,
"ratingCount": 27,
"ratingProvider": "Yelp",
"hours": {
"Monday": [{ "open": "07:00", "close": "22:00" }],
"Tuesday": [{ "open": "07:00", "close": "22:00" }]
},
"dataProvider": "Yelp",
"appleMapsUrl": "https://maps.apple.com/place?place-id=I15FF30DE01EC121F"
}
]Three fields carry the audit. ratingProvider and maxRating tell you what the score actually means, which is why ratingOutOf5 exists. mapsCategoryId is Apple's machine category path, so you can roll results up by vertical without string matching on display labels. dataProvider names the third party behind the listing, which explains a surprising share of the disagreements you will find — Apple publishes its full data source attribution if you want to trace a specific one.
Common pitfalls
Coverage audits fail in predictable ways. Running the two sides weeks apart turns ordinary listing churn into a fake coverage gap; run both in the same week. Comparing raw rating values mixes scales — Apple's own recommendation score runs 0 to 100 while a Yelp-sourced score runs 0 to 5, so use ratingOutOf5. Name-only joins merge unrelated businesses that share a generic name; always confirm with distance or the last ten phone digits. Over-broad queries hit each source's own depth ceiling at different points, which reads as a coverage difference when it is really a pagination difference — keep maxResults well inside what a category can actually supply, and split by neighbourhood when it cannot.
Thirdwatch runs both Actors on the same account, returns both as flat parsed records, and finishes an empty search as a successful run with a status message, so a category with genuinely no results shows up in your audit as a zero rather than as a failed job.
Related use cases
Frequently asked questions
Does Apple Maps list the same businesses as Google Maps?
No. The two indexes overlap heavily on chains and large venues but diverge on independents, new openings and categories such as trades. Apple blends its own listings with Yelp, TripAdvisor and Foursquare data, so both the roster and the attributes differ.
How do I match an Apple Maps place to a Google Maps place?
There is no shared identifier. Match on normalised name plus a coordinate distance threshold, typically 75 to 150 metres, then break ties on phone digits. Apple returns latitude, longitude and phone in E.164, which makes both halves of that join straightforward.
Why do the star ratings not agree between the two maps?
Apple often surfaces a partner rating and records the source in ratingProvider, with the scale in maxRating. Apple's own recommendation score runs 0 to 100 while Yelp runs 0 to 5. Compare ratingOutOf5 against the Google rating, never the raw value.
Which map has fresher opening hours?
It varies by market and category, which is exactly why the audit is worth running. Apple returns hours as per-weekday open and close pairs, so a direct comparison against Google's schedule is a straightforward set difference once both are normalised.
How big a sample do I need for a credible coverage read?
Three to five categories across one metro, capped at a few hundred places each, is usually enough to see a stable pattern. Run the same query strings against both sources in the same week so listing churn does not contaminate the comparison.
Related
100 free credits, no credit card.
About 30 real searches. Add the MCP to Claude or Cursor in two minutes.