How to Extract Business Names, Ratings, and Reviews from Google Maps
Learn how to extract business names, ratings, review counts, categories, addresses, phone numbers, websites, and place IDs from Google Maps-style search results with Python and TalorData SERP API.
Google Maps is one of the most useful sources for local business visibility. If you want to analyze restaurants, dentists, hotels, gyms, plumbers, agencies, or any other local category, the first fields you usually need are simple:
|
Field |
Why it matters |
|
Business name |
Identifies the listing |
|
Rating |
Shows reputation quality |
|
Review count |
Shows trust volume |
|
Category |
Helps group businesses |
|
Address |
Helps match local markets |
|
Phone number |
Useful for lead workflows |
|
Website |
Useful for enrichment |
|
Position |
Shows local search visibility |
|
Place ID / data CID |
Helps deduplicate listings |
The hard part is not understanding the data. The hard part is collecting it consistently.
Manual scraping with browser automation can break when layouts change, when results load dynamically, or when location-specific results behave differently. A SERP API workflow gives you structured Google Maps-style results that are easier to parse, store, and compare. TalorData’s SERP API supports structured JSON / HTML output, geo-targeted SERP data, and Google result types that include Maps and Local workflows.
This guide shows how to extract business names, ratings, and reviews from Google Maps-style search results with Python and TalorData.
What we will build
We will create a Python script that:
-
Sends a Google Maps search query
-
Gets structured JSON from TalorData
-
Extracts business names, ratings, review counts, categories, addresses, websites, and phone numbers
-
Normalizes the data
-
Exports the results to CSV
The workflow looks like this:
Search query + location
↓
TalorData SERP API
↓
Google Maps results in JSON
↓
Extract business fields
↓
Save CSV or database
What data can you extract?
For most local business workflows, start with these fields:
|
Field |
Example |
|
|
Example Coffee |
|
|
4.6 |
|
|
1280 |
|
|
Coffee shop |
|
|
123 Example St, New York |
|
|
+1 212-000-0000 |
|
|
https://example.com |
|
|
ChIJExample |
|
|
123456789 |
|
|
1 |
|
|
40.7455 |
|
|
-74.0083 |
|
|
Google Maps listing URL |
In Maps workflows, business name, place ID, address, coordinates, category, rating, review count, rank position, opening status, phone number, website, Google Maps URL, thumbnail, and price level are common useful fields when available.
Step 1: Prepare your API key
Store your TalorData API key and endpoint as environment variables.
export TALORDATA_API_KEY="your_api_key_here"
export TALORDATA_SERP_ENDPOINT="your_talordata_serp_endpoint_here"
On Windows PowerShell:
setx TALORDATA_API_KEY "your_api_key_here"
setx TALORDATA_SERP_ENDPOINT "your_talordata_serp_endpoint_here"
Install Python dependencies:
pip install requests pandas
Step 2: Send a Google Maps request
The request needs a query and a location context. For local search, coordinates are often better than a broad city name because Google Maps results can change street by street.
import os
import requests
from typing import Any, Dict, Optional
TALORDATA_API_KEY = os.getenv("TALORDATA_API_KEY")
TALORDATA_SERP_ENDPOINT = os.getenv("TALORDATA_SERP_ENDPOINT")
def fetch_google_maps(
query: str,
ll: str,
hl: str = "en",
gl: str = "us",
google_domain: str = "google.com",
no_cache: bool = True,
extra_params: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Fetch Google Maps-style search results with TalorData SERP API.
ll format example:
@40.7455096,-74.0083012,14z
"""
if not TALORDATA_API_KEY:
raise RuntimeError("Missing TALORDATA_API_KEY environment variable.")
if not TALORDATA_SERP_ENDPOINT:
raise RuntimeError("Missing TALORDATA_SERP_ENDPOINT environment variable.")
payload: Dict[str, Any] = {
"engine": "google_maps",
"type": "search",
"q": query,
"ll": ll,
"hl": hl,
"gl": gl,
"google_domain": google_domain,
"no_cache": no_cache
}
if extra_params:
payload.update(extra_params)
response = requests.post(
TALORDATA_SERP_ENDPOINT,
json=payload,
headers={
"Authorization": f"Bearer {TALORDATA_API_KEY}",
"Content-Type": "application/json"
},
timeout=30
)
response.raise_for_status()
return response.json()
Run a basic search:
if __name__ == "__main__":
data = fetch_google_maps(
query="coffee shops",
ll="@40.7455096,-74.0083012,14z",
hl="en",
gl="us"
)
print(data)
Step 3: Inspect the raw response
Before writing a parser, inspect the JSON structure.
import json
print(json.dumps(data, indent=2, ensure_ascii=False)[:5000])
You may see business results under fields like:
local_results
maps_results
place_results
results
organic_results
Different result types can use different containers. Do not build your parser like a stone tablet. Let it be a little rubbery.
Step 4: Parse business names, ratings, and reviews
Here is a parser that checks multiple possible result containers and extracts common business fields.
from typing import Any, Dict, List
def get_first_available_list(data: Dict[str, Any], keys: List[str]) -> List[Dict[str, Any]]:
for key in keys:
value = data.get(key)
if isinstance(value, list):
return value
return []
def parse_business_results(data: Dict[str, Any]) -> List[Dict[str, Any]]:
items = get_first_available_list(
data,
keys=[
"local_results",
"maps_results",
"place_results",
"results",
"organic_results"
]
)
rows = []
for index, item in enumerate(items, start=1):
gps = item.get("gps_coordinates") or {}
links = item.get("links") or {}
rows.append({
"position": item.get("position", index),
"business_name": item.get("title") or item.get("name"),
"rating": item.get("rating"),
"review_count": item.get("reviews") or item.get("review_count"),
"category": item.get("type") or item.get("category"),
"address": item.get("address"),
"phone": item.get("phone"),
"website": item.get("website") or links.get("website"),
"place_id": item.get("place_id"),
"data_cid": item.get("data_cid") or item.get("cid"),
"maps_url": item.get("link") or item.get("maps_url"),
"latitude": gps.get("latitude") or item.get("latitude"),
"longitude": gps.get("longitude") or item.get("longitude"),
"open_state": item.get("open_state") or item.get("hours"),
"price_level": item.get("price") or item.get("price_level")
})
return rows
Use it:
data = fetch_google_maps(
query="coffee shops",
ll="@40.7455096,-74.0083012,14z"
)
businesses = parse_business_results(data)
for business in businesses[:5]:
print(
business["position"],
business["business_name"],
business["rating"],
business["review_count"]
)
Step 5: Export to CSV
A CSV file is useful for quick review, manual QA, Excel analysis, CRM import, or BI dashboards.
import pandas as pd
data = fetch_google_maps(
query="coffee shops",
ll="@40.7455096,-74.0083012,14z"
)
businesses = parse_business_results(data)
df = pd.DataFrame(businesses)
df.to_csv("google_maps_businesses.csv", index=False)
print(df.head())
Example output:
|
position |
business_name |
rating |
review_count |
category |
|
1 |
Example Coffee |
4.6 |
1280 |
Coffee shop |
|
2 |
Sample Cafe |
4.4 |
760 |
Cafe |
|
3 |
Local Roasters |
4.7 |
420 |
Coffee roasters |
Step 6: Clean ratings and review counts
Ratings and review counts may arrive as strings or numbers depending on the response format. Convert them before analysis.
df["rating_num"] = pd.to_numeric(df["rating"], errors="coerce")
df["review_count_num"] = pd.to_numeric(df["review_count"], errors="coerce")
Now you can sort businesses by reputation strength:
top_reputation = df.sort_values(
by=["rating_num", "review_count_num"],
ascending=[False, False]
)
top_reputation.to_csv("top_rated_businesses.csv", index=False)
print(top_reputation[[
"business_name",
"rating",
"review_count",
"category",
"address"
]].head(10))
Rating and review count should be read together.
|
Case |
Meaning |
|
High rating + high review count |
Strong reputation |
|
High rating + low review count |
Good score, small sample |
|
Low rating + high review count |
Known business, reputation risk |
|
No rating |
New, hidden, or incomplete listing |
|
High position + weak reviews |
Strong visibility, weaker trust |
A 4.9 rating with 12 reviews is not the same as a 4.6 rating with 2,000 reviews. One is a postcard. The other is weather.
Step 7: Add search context
If you want to compare results over time, store the search context with every row.
from datetime import datetime, timezone
def add_search_context(
rows: List[Dict[str, Any]],
query: str,
ll: str,
hl: str,
gl: str
) -> List[Dict[str, Any]]:
collected_at = datetime.now(timezone.utc).isoformat()
for row in rows:
row["query"] = query
row["ll"] = ll
row["hl"] = hl
row["gl"] = gl
row["collected_at"] = collected_at
return rows
Use it:
query = "coffee shops"
ll = "@40.7455096,-74.0083012,14z"
hl = "en"
gl = "us"
data = fetch_google_maps(query=query, ll=ll, hl=hl, gl=gl)
rows = parse_business_results(data)
rows = add_search_context(rows, query=query, ll=ll, hl=hl, gl=gl)
pd.DataFrame(rows).to_csv("google_maps_snapshot.csv", index=False)
Now you can track:
|
Change |
Meaning |
|
Position changed |
Local ranking changed |
|
Rating changed |
Reputation changed |
|
Review count increased |
More customer feedback |
|
Business disappeared |
Visibility changed |
|
New competitor appeared |
Market movement |
|
Website or phone appeared |
Listing completeness improved |
Step 8: Deduplicate businesses
Business names can vary. Addresses can be formatted differently. For deduplication, use stable identifiers whenever possible.
Prefer:
-
place_id -
data_cid -
Business name + address
-
Business name + latitude + longitude
Example:
def build_business_key(row: Dict[str, Any]) -> str:
if row.get("place_id"):
return f"place_id:{row['place_id']}"
if row.get("data_cid"):
return f"data_cid:{row['data_cid']}"
name = str(row.get("business_name") or "").lower().strip()
address = str(row.get("address") or "").lower().strip()
return f"name_address:{name}|{address}"
df["business_key"] = df.apply(lambda row: build_business_key(row.to_dict()), axis=1)
df = df.drop_duplicates(subset=["business_key"])
This is especially important when you collect the same category across multiple coordinates.
Step 9: Run searches across multiple locations
For local SEO, a single coordinate is often too narrow. Use a coordinate grid.
locations = [
{
"label": "Manhattan West",
"ll": "@40.7455096,-74.0083012,14z"
},
{
"label": "Times Square",
"ll": "@40.758896,-73.985130,14z"
},
{
"label": "Lower Manhattan",
"ll": "@40.707491,-74.011276,14z"
}
]
all_rows = []
for location in locations:
data = fetch_google_maps(
query="coffee shops",
ll=location["ll"],
hl="en",
gl="us"
)
rows = parse_business_results(data)
for row in rows:
row["grid_location"] = location["label"]
rows = add_search_context(
rows,
query="coffee shops",
ll=location["ll"],
hl="en",
gl="us"
)
all_rows.extend(rows)
pd.DataFrame(all_rows).to_csv("google_maps_multi_location.csv", index=False)
This helps answer:
|
Question |
What to measure |
|
Which businesses appear most often? |
Presence across locations |
|
Which business has the best average position? |
Local visibility |
|
Which competitors have stronger reviews? |
Rating + review count |
|
Which areas are more competitive? |
Number of strong listings |
|
Which locations have weak coverage? |
Missing or low-quality results |
Complete script
Here is the complete version.
import os
import json
import requests
import pandas as pd
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
TALORDATA_API_KEY = os.getenv("TALORDATA_API_KEY")
TALORDATA_SERP_ENDPOINT = os.getenv("TALORDATA_SERP_ENDPOINT")
def fetch_google_maps(
query: str,
ll: str,
hl: str = "en",
gl: str = "us",
google_domain: str = "google.com",
no_cache: bool = True,
extra_params: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
if not TALORDATA_API_KEY:
raise RuntimeError("Missing TALORDATA_API_KEY environment variable.")
if not TALORDATA_SERP_ENDPOINT:
raise RuntimeError("Missing TALORDATA_SERP_ENDPOINT environment variable.")
payload: Dict[str, Any] = {
"engine": "google_maps",
"type": "search",
"q": query,
"ll": ll,
"hl": hl,
"gl": gl,
"google_domain": google_domain,
"no_cache": no_cache
}
if extra_params:
payload.update(extra_params)
response = requests.post(
TALORDATA_SERP_ENDPOINT,
json=payload,
headers={
"Authorization": f"Bearer {TALORDATA_API_KEY}",
"Content-Type": "application/json"
},
timeout=30
)
response.raise_for_status()
return response.json()
def get_first_available_list(data: Dict[str, Any], keys: List[str]) -> List[Dict[str, Any]]:
for key in keys:
value = data.get(key)
if isinstance(value, list):
return value
return []
def parse_business_results(data: Dict[str, Any]) -> List[Dict[str, Any]]:
items = get_first_available_list(
data,
keys=[
"local_results",
"maps_results",
"place_results",
"results",
"organic_results"
]
)
rows = []
for index, item in enumerate(items, start=1):
gps = item.get("gps_coordinates") or {}
links = item.get("links") or {}
rows.append({
"position": item.get("position", index),
"business_name": item.get("title") or item.get("name"),
"rating": item.get("rating"),
"review_count": item.get("reviews") or item.get("review_count"),
"category": item.get("type") or item.get("category"),
"address": item.get("address"),
"phone": item.get("phone"),
"website": item.get("website") or links.get("website"),
"place_id": item.get("place_id"),
"data_cid": item.get("data_cid") or item.get("cid"),
"maps_url": item.get("link") or item.get("maps_url"),
"latitude": gps.get("latitude") or item.get("latitude"),
"longitude": gps.get("longitude") or item.get("longitude"),
"open_state": item.get("open_state") or item.get("hours"),
"price_level": item.get("price") or item.get("price_level")
})
return rows
def add_search_context(
rows: List[Dict[str, Any]],
query: str,
ll: str,
hl: str,
gl: str
) -> List[Dict[str, Any]]:
collected_at = datetime.now(timezone.utc).isoformat()
for row in rows:
row["query"] = query
row["ll"] = ll
row["hl"] = hl
row["gl"] = gl
row["collected_at"] = collected_at
return rows
def main() -> None:
query = "coffee shops"
ll = "@40.7455096,-74.0083012,14z"
hl = "en"
gl = "us"
raw_data = fetch_google_maps(query=query, ll=ll, hl=hl, gl=gl)
rows = parse_business_results(raw_data)
rows = add_search_context(rows, query=query, ll=ll, hl=hl, gl=gl)
df = pd.DataFrame(rows)
df["rating_num"] = pd.to_numeric(df["rating"], errors="coerce")
df["review_count_num"] = pd.to_numeric(df["review_count"], errors="coerce")
df.to_csv("google_maps_businesses.csv", index=False)
with open("raw_google_maps_response.json", "w", encoding="utf-8") as file:
json.dump(raw_data, file, ensure_ascii=False, indent=2)
print(f"Saved {len(df)} rows to google_maps_businesses.csv")
print("Saved raw_google_maps_response.json")
if __name__ == "__main__":
main()
Common mistakes
Mistake 1: Only extracting business names
A business name alone is not very useful. Extract rating, review count, category, address, phone, website, and place ID when possible.
Mistake 2: Ignoring location
Google Maps results are local. Store the coordinate, zoom level, language, and country context.
Mistake 3: Treating rating as the whole reputation signal
Rating needs review count. A high score with few reviews can be fragile.
Mistake 4: Not deduplicating listings
The same business can appear across multiple searches. Use place_id or data_cid to reduce duplicates.
Mistake 5: Overwriting snapshots
If you want monitoring, append new data instead of replacing old files.
Mistake 6: Not saving raw JSON
Keep raw responses during development. Parser bugs are much easier to fix when you still have the original response.
Final thoughts
Extracting business names, ratings, and reviews from Google Maps is useful for local SEO, lead generation, competitor research, market analysis, review monitoring, and AI agents.
The clean workflow is simple:
-
Send a local Maps query
-
Get structured JSON
-
Extract business name, rating, review count, category, address, website, phone, and place ID
-
Save the search context
-
Export to CSV or store in a database
-
Track changes over time
Google Maps is a crowded street. Structured data turns it into a spreadsheet with streetlights. Start free trial of Google Maps API>>