DEV Community

Cecilia Hill
Cecilia Hill

Posted on

How to Extract Google Maps Results with Python and a SERP API

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
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Reads search queries from a text file
  2. Reads locations from a text file
  3. Calls a Google Maps SERP API
  4. Extracts local business results
  5. Normalizes fields like name, address, website, rating, and reviews
  6. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

A Google Maps script usually asks:

Which businesses appear for this query in this location?
Enter fullscreen mode Exit fullscreen mode

That location part is important.

A query like:

coffee shop
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

We will use:

requests → call the SERP API
python-dotenv → load API keys from .env
pandas → save results to CSV
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

That is normal.

The important idea is this:

send query + location + Google Maps engine
receive structured local results
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Create a file called locations.txt:

Austin, TX
Dallas, TX
Chicago, IL
New York, NY
Enter fullscreen mode Exit fullscreen mode

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()
        ]
Enter fullscreen mode Exit fullscreen mode

This gives us a simple way to load both query and location files.

queries = load_lines("queries.txt")
locations = load_lines("locations.txt")
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Depending on your provider, the engine value may be different.

You may see values like:

google_maps
google_local
maps
local
Enter fullscreen mode Exit fullscreen mode

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",
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 []
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This turns:

https://www.example.com/contact
Enter fullscreen mode Exit fullscreen mode

into:

example.com
Enter fullscreen mode Exit fullscreen mode

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 "",
    }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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",
)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Run it:

python google_maps_results.py
Enter fullscreen mode Exit fullscreen mode

You should get a file like:

google_maps_results_2026-01-01.csv
Enter fullscreen mode Exit fullscreen mode

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/...
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

Save it:

top_businesses.to_csv("top_google_maps_businesses.csv", index=False)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

This helps answer:

Which competitors are strong in Austin?
Which competitors are strong in Dallas?
Does the same business appear across multiple cities?
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
def visibility_score(position):
    try:
        position = int(position)
    except (ValueError, TypeError):
        return 0

    if position < 1 or position > 10:
        return 0

    return 11 - position
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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,
        )
Enter fullscreen mode Exit fullscreen mode

Then query historical data:

SELECT date, query, location, position, name, rating, reviews
FROM maps_results
ORDER BY date, location, query, position;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

For example, a local SEO workflow could be:

keywords + cities
→ Google Maps SERP API
→ local business results
→ visibility score
→ weekly report
Enter fullscreen mode Exit fullscreen mode

An AI agent workflow could be:

user asks about local competitors
→ fetch Google Maps data
→ clean business results
→ summarize top competitors by city
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The core workflow is simple:

queries
→ locations
→ Google Maps SERP API
→ normalized business fields
→ CSV or database
→ analysis
Enter fullscreen mode Exit fullscreen mode

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)