Google Maps results are useful for a lot of projects.
Not only for SEO people.
Developers may need Google Maps data for:
local SEO tracking
business directory research
competitor monitoring
lead research
market analysis
location-based AI agents
local ranking reports
For example, you may want to answer questions like:
Which dentists appear in Austin for "emergency dentist"?
Which coffee shops show up in Google Maps for "coffee shop near me"?
Which competitors appear in multiple cities?
What businesses rank in the top 3 local results?
Which local results have websites, ratings, and reviews?
You can try scraping Google Maps manually.
For a tiny experiment, maybe it works.
For anything repeated, it becomes messy fast.
Google Maps pages are dynamic. Results depend on location. Business fields vary. You may run into blocked requests, missing data, changing layouts, and the usual pile of scraping goblins wearing tiny safety helmets.
A cleaner approach is to use a SERP API that supports Google Maps or local results.
The workflow looks like this:
query + location
→ Google Maps SERP API
→ structured local results
→ clean fields
→ CSV or database
In this article, we will build a simple Python script that collects Google Maps results and saves them to CSV.
We will keep the API layer generic so you can adapt it to providers like Talordata, SerpApi, SearchAPI, DataForSEO, or another SERP API provider that supports Google Maps data.
What we are building
We will build a script that:
- Reads search queries from a text file
- Reads locations from a text file
- Calls a Google Maps SERP API
- Extracts local business results
- Normalizes fields like name, address, website, rating, and reviews
- Saves the result rows to CSV
The final CSV will look like this:
date,query,location,position,name,address,website,rating,reviews,phone,category,place_id
2026-01-01,dentist near me,Austin TX,1,Example Dental Clinic,123 Main St,example.com,4.8,214,+1 555-123-4567,Dentist,abc123
That is already useful for local SEO, competitor tracking, market research, or local search monitoring.
Why Google Maps data is different
Normal Google organic results usually look like this:
position
title
URL
snippet
Google Maps results are different.
A local result may include:
business name
address
phone number
website
rating
review count
category
place ID
maps URL
opening hours
latitude and longitude
That makes the data useful, but also harder to parse.
A normal organic ranking script is usually asking:
Does this domain rank for this keyword?
A Google Maps script usually asks:
Which businesses appear for this query in this location?
That location part is important.
A query like:
coffee shop
means almost nothing without a location.
Google Maps results in Austin will not be the same as Google Maps results in Chicago, London, or Singapore.
A useful Google Maps data workflow should always store:
query
location
date
position
business fields
Without those fields, your dataset becomes a foggy little spreadsheet swamp.
Install dependencies
Create a new project folder.
Install the packages:
pip install requests python-dotenv pandas
We will use:
requests → call the SERP API
python-dotenv → load API keys from .env
pandas → save results to CSV
Create a .env file
Create a .env file:
SERP_API_KEY=your_api_key
SERP_API_URL=https://your-serp-api-endpoint.example.com/search
This article uses a generic SERP API format.
Your provider may use different parameter names.
Some providers use:
q
query
engine
location
ll
hl
gl
type
search_type
That is normal.
The important idea is this:
send query + location + Google Maps engine
receive structured local results
Adjust the request function to match your provider’s documentation.
Create input files
Create a file called queries.txt:
dentist near me
coffee shop
best hotel
plumber
private school
Create a file called locations.txt:
Austin, TX
Dallas, TX
Chicago, IL
New York, NY
Use real queries and locations from your workflow.
Do not only test cute demo inputs. Real local search data is messy, and messy is where useful scripts prove they are not decorative furniture.
Step 1: Load settings and inputs
Create a file called google_maps_results.py.
import os
import re
import time
import requests
import pandas as pd
from datetime import date
from urllib.parse import urlparse
from dotenv import load_dotenv
load_dotenv()
SERP_API_KEY = os.getenv("SERP_API_KEY")
SERP_API_URL = os.getenv("SERP_API_URL")
def validate_settings():
if not SERP_API_KEY:
raise ValueError("Missing SERP_API_KEY")
if not SERP_API_URL:
raise ValueError("Missing SERP_API_URL")
def load_lines(filename):
with open(filename, "r", encoding="utf-8") as file:
return [
line.strip()
for line in file
if line.strip()
]
This gives us a simple way to load both query and location files.
queries = load_lines("queries.txt")
locations = load_lines("locations.txt")
Step 2: Call the Google Maps SERP API
Now write a request function.
def fetch_google_maps_results(query, location, language="en"):
params = {
"api_key": SERP_API_KEY,
"engine": "google_maps",
"q": query,
"location": location,
"language": language,
"output": "json",
}
response = requests.get(
SERP_API_URL,
params=params,
timeout=30,
)
response.raise_for_status()
return response.json()
Depending on your provider, the engine value may be different.
You may see values like:
google_maps
google_local
maps
local
Use whatever your provider expects.
For example, if your SERP API provider uses type=maps instead of engine=google_maps, change the params:
params = {
"api_key": SERP_API_KEY,
"q": query,
"location": location,
"language": language,
"type": "maps",
"output": "json",
}
The rest of the script does not need to care.
That is why we keep the API request in one function. Future-you deserves fewer headaches. Future-you is already tired.
Step 3: Extract local result items
Different APIs may return Google Maps results under different keys.
Common examples include:
local_results
maps_results
places
place_results
results
Let’s write a defensive parser.
def get_local_items(data):
possible_keys = [
"local_results",
"maps_results",
"places",
"place_results",
"results",
]
for key in possible_keys:
value = data.get(key)
if isinstance(value, list):
return value
serp = data.get("serp", {})
if isinstance(serp, dict):
for key in possible_keys:
value = serp.get(key)
if isinstance(value, list):
return value
return []
This makes the script easier to adapt across providers.
A SERP API response is usually similar, but not identical. Like cousins at a family dinner, except the field names keep changing seats.
Step 4: Clean text fields
Google Maps data can contain extra whitespace or missing values.
Add a small text cleaner.
def clean_text(value):
if value is None:
return ""
value = str(value)
value = re.sub(r"\s+", " ", value)
return value.strip()
Small cleaning functions are not glamorous.
They are useful.
Useful beats glamorous, especially in data pipelines.
Step 5: Normalize websites
Some local results return full URLs.
Some return domains.
Some return nothing.
Let’s normalize websites into clean domains.
def normalize_website(value):
if not value:
return ""
value = str(value).strip()
if not value.startswith("http://") and not value.startswith("https://"):
value = "https://" + value
parsed = urlparse(value)
domain = parsed.netloc.lower()
if domain.startswith("www."):
domain = domain[4:]
return domain
This turns:
https://www.example.com/contact
into:
example.com
That is easier to compare, group, and analyze.
Step 6: Normalize one Google Maps result
Now convert each local result into one stable format.
def normalize_local_result(item, query, location):
name = (
item.get("title")
or item.get("name")
or item.get("business_name")
or ""
)
website = (
item.get("website")
or item.get("website_url")
or item.get("link")
or item.get("url")
or ""
)
return {
"date": date.today().isoformat(),
"query": query,
"location": location,
"position": item.get("position") or item.get("rank") or "",
"name": clean_text(name),
"address": clean_text(
item.get("address")
or item.get("formatted_address")
or ""
),
"phone": clean_text(
item.get("phone")
or item.get("phone_number")
or ""
),
"website": normalize_website(website),
"rating": item.get("rating") or item.get("stars") or "",
"reviews": item.get("reviews") or item.get("review_count") or "",
"category": clean_text(
item.get("category")
or item.get("type")
or ""
),
"place_id": clean_text(
item.get("place_id")
or item.get("data_id")
or ""
),
"maps_url": item.get("maps_url") or item.get("link") or "",
}
Now your final rows have a consistent shape.
That is the whole trick.
Your provider-specific data gets cleaned at the edge. Your app logic stays simple.
Step 7: Fetch and normalize one query-location pair
Now combine the request and parser.
def collect_for_query_location(query, location, language="en"):
data = fetch_google_maps_results(
query=query,
location=location,
language=language,
)
local_items = get_local_items(data)
rows = [
normalize_local_result(
item=item,
query=query,
location=location,
)
for item in local_items
]
return rows
This gives us local business rows for one query and one location.
Example:
rows = collect_for_query_location(
query="dentist near me",
location="Austin, TX",
)
Step 8: Collect all queries and locations
Now loop through everything.
def collect_all(queries, locations, language="en", delay=1):
all_rows = []
for location in locations:
for query in queries:
print(f"Collecting: {query} | {location}")
try:
rows = collect_for_query_location(
query=query,
location=location,
language=language,
)
all_rows.extend(rows)
except Exception as exc:
print(f"Failed: {query} | {location}")
print(f"Error: {exc}")
all_rows.append({
"date": date.today().isoformat(),
"query": query,
"location": location,
"position": "",
"name": "",
"address": "",
"phone": "",
"website": "",
"rating": "",
"reviews": "",
"category": "",
"place_id": "",
"maps_url": "",
"error": str(exc),
})
time.sleep(delay)
return all_rows
The delay matters.
Respect API rate limits.
Automation should be calm. Not a hyperactive raccoon with a request loop.
Step 9: Save results to CSV
Now save the collected rows.
def save_to_csv(rows):
today = date.today().isoformat()
filename = f"google_maps_results_{today}.csv"
df = pd.DataFrame(rows)
df.to_csv(filename, index=False)
print(f"Saved: {filename}")
return filename
Full script
Here is the complete script.
import os
import re
import time
import requests
import pandas as pd
from datetime import date
from urllib.parse import urlparse
from dotenv import load_dotenv
load_dotenv()
SERP_API_KEY = os.getenv("SERP_API_KEY")
SERP_API_URL = os.getenv("SERP_API_URL")
def validate_settings():
if not SERP_API_KEY:
raise ValueError("Missing SERP_API_KEY")
if not SERP_API_URL:
raise ValueError("Missing SERP_API_URL")
def load_lines(filename):
with open(filename, "r", encoding="utf-8") as file:
return [
line.strip()
for line in file
if line.strip()
]
def fetch_google_maps_results(query, location, language="en"):
params = {
"api_key": SERP_API_KEY,
"engine": "google_maps",
"q": query,
"location": location,
"language": language,
"output": "json",
}
response = requests.get(
SERP_API_URL,
params=params,
timeout=30,
)
response.raise_for_status()
return response.json()
def get_local_items(data):
possible_keys = [
"local_results",
"maps_results",
"places",
"place_results",
"results",
]
for key in possible_keys:
value = data.get(key)
if isinstance(value, list):
return value
serp = data.get("serp", {})
if isinstance(serp, dict):
for key in possible_keys:
value = serp.get(key)
if isinstance(value, list):
return value
return []
def clean_text(value):
if value is None:
return ""
value = str(value)
value = re.sub(r"\s+", " ", value)
return value.strip()
def normalize_website(value):
if not value:
return ""
value = str(value).strip()
if not value.startswith("http://") and not value.startswith("https://"):
value = "https://" + value
parsed = urlparse(value)
domain = parsed.netloc.lower()
if domain.startswith("www."):
domain = domain[4:]
return domain
def normalize_local_result(item, query, location):
name = (
item.get("title")
or item.get("name")
or item.get("business_name")
or ""
)
website = (
item.get("website")
or item.get("website_url")
or item.get("link")
or item.get("url")
or ""
)
return {
"date": date.today().isoformat(),
"query": query,
"location": location,
"position": item.get("position") or item.get("rank") or "",
"name": clean_text(name),
"address": clean_text(
item.get("address")
or item.get("formatted_address")
or ""
),
"phone": clean_text(
item.get("phone")
or item.get("phone_number")
or ""
),
"website": normalize_website(website),
"rating": item.get("rating") or item.get("stars") or "",
"reviews": item.get("reviews") or item.get("review_count") or "",
"category": clean_text(
item.get("category")
or item.get("type")
or ""
),
"place_id": clean_text(
item.get("place_id")
or item.get("data_id")
or ""
),
"maps_url": item.get("maps_url") or item.get("link") or "",
}
def collect_for_query_location(query, location, language="en"):
data = fetch_google_maps_results(
query=query,
location=location,
language=language,
)
local_items = get_local_items(data)
rows = [
normalize_local_result(
item=item,
query=query,
location=location,
)
for item in local_items
]
return rows
def collect_all(queries, locations, language="en", delay=1):
all_rows = []
for location in locations:
for query in queries:
print(f"Collecting: {query} | {location}")
try:
rows = collect_for_query_location(
query=query,
location=location,
language=language,
)
all_rows.extend(rows)
except Exception as exc:
print(f"Failed: {query} | {location}")
print(f"Error: {exc}")
all_rows.append({
"date": date.today().isoformat(),
"query": query,
"location": location,
"position": "",
"name": "",
"address": "",
"phone": "",
"website": "",
"rating": "",
"reviews": "",
"category": "",
"place_id": "",
"maps_url": "",
"error": str(exc),
})
time.sleep(delay)
return all_rows
def save_to_csv(rows):
today = date.today().isoformat()
filename = f"google_maps_results_{today}.csv"
df = pd.DataFrame(rows)
df.to_csv(filename, index=False)
print(f"Saved: {filename}")
return filename
def main():
validate_settings()
queries = load_lines("queries.txt")
locations = load_lines("locations.txt")
rows = collect_all(
queries=queries,
locations=locations,
language="en",
delay=1,
)
save_to_csv(rows)
print(f"Collected {len(rows)} rows.")
if __name__ == "__main__":
main()
Run it:
python google_maps_results.py
You should get a file like:
google_maps_results_2026-01-01.csv
Example output
Your CSV may look like this:
date,query,location,position,name,address,phone,website,rating,reviews,category,place_id,maps_url
2026-01-01,dentist near me,Austin TX,1,Example Dental Clinic,123 Main St,+1 555-123-4567,exampledental.com,4.8,214,Dentist,abc123,https://maps.google.com/...
2026-01-01,dentist near me,Austin TX,2,Austin Smile Center,456 Oak Ave,+1 555-987-6543,austinsmilecenter.com,4.7,182,Dentist,def456,https://maps.google.com/...
Now you have structured Google Maps data you can analyze.
Analyze top businesses
Once you have CSV data, you can quickly answer:
Which businesses appear most often?
Which businesses rank in position 1?
Which categories dominate the results?
Which locations have different competitors?
Example:
import pandas as pd
df = pd.read_csv("google_maps_results_2026-01-01.csv")
top_businesses = (
df.groupby("name")
.size()
.reset_index(name="appearances")
.sort_values("appearances", ascending=False)
)
print(top_businesses.head(20))
Save it:
top_businesses.to_csv("top_google_maps_businesses.csv", index=False)
That gives you a quick local competitor view.
Find top 3 results only
For local SEO, top 3 often matters a lot.
Filter by position:
df["position"] = pd.to_numeric(df["position"], errors="coerce")
top_3 = df[df["position"] <= 3]
top_3.to_csv("google_maps_top_3.csv", index=False)
Now you can inspect which businesses dominate the top local results.
Compare locations
You can group by location and business.
location_summary = (
df.groupby(["location", "name"])
.size()
.reset_index(name="appearances")
.sort_values(["location", "appearances"], ascending=[True, False])
)
location_summary.to_csv("google_maps_location_summary.csv", index=False)
This helps answer:
Which competitors are strong in Austin?
Which competitors are strong in Dallas?
Does the same business appear across multiple cities?
Add a simple local visibility score
You can create a rough visibility score based on ranking position.
Example scoring rule:
position 1 = 10 points
position 2 = 9 points
position 3 = 8 points
...
position 10 = 1 point
not found or position over 10 = 0 points
def visibility_score(position):
try:
position = int(position)
except (ValueError, TypeError):
return 0
if position < 1 or position > 10:
return 0
return 11 - position
Apply it:
df["visibility_score"] = df["position"].apply(visibility_score)
visibility = (
df.groupby(["location", "name"])["visibility_score"]
.sum()
.reset_index()
.sort_values(["location", "visibility_score"], ascending=[True, False])
)
visibility.to_csv("google_maps_visibility_score.csv", index=False)
This is not a perfect SEO metric.
It is a useful starting point.
Perfect metrics are nice. Useful metrics actually get used. Humanity survives another spreadsheet.
Save to SQLite instead of CSV
CSV is enough for the first version.
If you run this daily, use SQLite.
import sqlite3
def save_to_sqlite(rows, database="google_maps_results.db"):
df = pd.DataFrame(rows)
with sqlite3.connect(database) as connection:
df.to_sql(
"maps_results",
connection,
if_exists="append",
index=False,
)
Then query historical data:
SELECT date, query, location, position, name, rating, reviews
FROM maps_results
ORDER BY date, location, query, position;
This gives you a small local search database.
Still simple. Much more useful.
Common mistakes
Only testing one location
Google Maps results are location-sensitive.
One city tells you very little.
Test the locations that matter to your product, client, or market.
Ignoring business name variations
A business may appear with slightly different names.
For example:
Example Dental Clinic
Example Dental
Example Dental Clinic - Downtown
If you later build rank tracking, use website, place ID, or fuzzy name matching.
Treating position as the whole story
Position matters, but local visibility also depends on:
rating
review count
distance
category
business profile quality
search intent
This script collects data. It does not explain every ranking factor.
Not storing the date
Without the date, you cannot track change over time.
Always store the date.
Future analysis depends on boring fields. Tragic, but true.
Sending raw results straight into an LLM
If you use Google Maps results for an AI agent, clean the data first.
A better context format is:
Business [1]
Name: Example Dental Clinic
Position: 1
Address: 123 Main St, Austin, TX
Rating: 4.8
Reviews: 214
Website: exampledental.com
Do not dump raw JSON into a prompt unless you enjoy token confetti.
Where this is useful
This Google Maps data workflow can support:
local SEO monitoring
competitor research
lead generation
market mapping
business directory analysis
AI local research agents
multi-city visibility reports
review and rating analysis
For example, a local SEO workflow could be:
keywords + cities
→ Google Maps SERP API
→ local business results
→ visibility score
→ weekly report
An AI agent workflow could be:
user asks about local competitors
→ fetch Google Maps data
→ clean business results
→ summarize top competitors by city
The same dataset can serve multiple products.
That is why structured data matters.
Provider note
Most SERP API providers that support Google Maps will return similar business fields, but the response shapes differ.
When testing a provider, check:
Does it return Maps or local results?
Does it include name, address, website, rating, reviews, and position?
Does it support your target locations?
Does it return enough results?
Does it include place ID?
Is the JSON easy to normalize?
How often are results empty?
The response body tells you more than the homepage.
Annoying, but true.
Final thoughts
Google Maps results are valuable because they show local search visibility.
They can tell you:
who appears
where they appear
which businesses dominate
which locations differ
how ratings and reviews show up
which competitors are visible
The core workflow is simple:
queries
→ locations
→ Google Maps SERP API
→ normalized business fields
→ CSV or database
→ analysis
Start with a small version.
Use 5 queries.
Use 3 locations.
Collect name, address, website, rating, reviews, and position.
Save the CSV.
Inspect the data.
Then add historical tracking, competitor matching, visibility scores, SQLite, dashboards, or AI summaries.
Do not build the entire castle before checking whether the front door exists.
Start with clean Google Maps data.
Everything else gets easier after that.
Top comments (0)