Export Google Maps List to CSV: A Python Tutorial (2026)

Export Google Maps list data to CSV with Python and the FlyByAPIs API. Pull business names, phones, ratings, and websites straight into your spreadsheet.

You can see every coffee shop in Austin on Google Maps. Names, ratings, phone numbers, websites, all right there on the screen. Now try to get that into a spreadsheet.

There is no export button. No “download as CSV.” The app will happily show you 200 businesses and let you save exactly none of them to a file.

So this tutorial does the obvious thing the app won’t: it calls our Google Maps scraping API , pulls the full list, and writes it to a CSV you can open in Excel or Google Sheets. The goal is simple. Learn how to export Google Maps list data to a clean spreadsheet with about 70 lines of Python, keeping every field the API returns.

The FlyByAPIs Google Maps API returns up to 200 businesses per request, each with 36 structured fields (names, ratings, phones, websites, addresses, coordinates). About 70 lines of Python paginate past the 200 limit and write every field to a CSV that opens directly in Excel or Google Sheets. No browser extension, no manual copy-paste, and only one dependency beyond the standard library.

1

API call to start

200

Businesses per request

36

Fields per business

0

Extra dependencies

FlyByAPIs is a suite of web data extraction APIs, and its Google Maps API turns any Maps search into structured JSON you can save. We run extraction infrastructure that handles millions of requests a month, so the script below is the same approach we use ourselves. By the end you will have a working exporter that turns any Maps search into a CSV: names, ratings, phones, websites, addresses.

No browser extensions. No copy-paste marathon. No scraper to babysit.

What you need:

Python 3.9 or newer, the requests package, and a free FlyByAPIs key from RapidAPI. That is the whole shopping list. Total setup time is about ten minutes.

How to export Google Maps list data with FlyByAPIs

The whole tutorial rests on one endpoint from our Google Maps Scraper API : /locate_and_search. You hand it a plain-English query like “coffee shops in Austin,” and it resolves the location, runs the search, and returns every business with full details in one response.

No coordinates to look up. No browser to drive. Just a GET request with a query and a couple of parameters.

Here is the shape of the response, trimmed to the fields we care about:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "status": true,
  "location": { "latitude": 30.2672, "longitude": -97.7431 },
  "data": [
    {
      "google_id": "0x8644b59f7d6b1c2d:0x...",
      "name": "Houndstooth Coffee",
      "full_address": "401 Congress Ave #100, Austin, TX 78701",
      "full_phone": "+15125550143",
      "rating": 4.6,
      "reviews_count": 1840,
      "main_category": "Coffee shop",
      "website_url": "https://houndstoothcoffee.com",
      "latitude": 30.2683,
      "longitude": -97.7428
    }
  ]
}

That is the whole game. Every business comes back with the data you actually want, and turning it into CSV is a few lines of Python. Let me walk through it step by step.

The one number that shapes the script:

A single request returns at most 200 businesses. That is why the script paginates. To pull a list bigger than 200, you keep asking for the next slice with the offset parameter until the results run dry.

Step 1: Get a free API key

Head to our Google Maps data API on RapidAPI and subscribe to the free Basic plan. It gives you 100 requests a month, no credit card. That is plenty to follow this tutorial and pull a few full lists.

Once you subscribe, RapidAPI shows you your X-RapidAPI-Key. Copy it. We will keep it out of the code and in an environment variable, which is the right habit for anything you might commit to git.

1
export RAPIDAPI_KEY="your_key_here"

Pro tip:

Never paste your API key directly into a script you might share or push to GitHub. Read it from an environment variable like the example above. The full code for this tutorial lives in a public repo, and that is exactly how it handles the key.

Step 2: Set up Python

You need Python 3.9 or newer and one package, requests. Everything else, including the CSV writing, uses the standard library.

Create a folder, set up a virtual environment, and install the one dependency:

1
2
3
4
mkdir export-google-maps-list && cd export-google-maps-list
python3 -m venv venv
source venv/bin/activate        # Windows: venv\Scripts\activate
pip install requests

The virtual environment keeps this project’s package separate from your system Python. If you have never used one, think of it as a clean sandbox per project. Activate it, and pip install only touches this folder.

Step 3: Make your first request

Let’s pull a single page of results to confirm everything works. This hits /locate_and_search with our example query and prints the first few businesses.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import os
import requests

API_HOST = "google-maps-extractor2.p.rapidapi.com"
URL = f"https://{API_HOST}/locate_and_search"

headers = {
    "X-RapidAPI-Key": os.environ["RAPIDAPI_KEY"],
    "X-RapidAPI-Host": API_HOST,
}
params = {
    "query": "coffee shops in Austin",
    "language": "en",
    "country": "us",
    "limit": 50,
    "offset": 0,
}

resp = requests.get(URL, headers=headers, params=params, timeout=30)
data = resp.json()

for biz in data["data"][:5]:
    print(biz["name"], "|", biz.get("rating"), "stars")

Run it. You should see five coffee shops with their ratings. If you do, your key works and the endpoint is talking back. The limit parameter controls how many results per call, up to a maximum of 200, and offset is how we will page through the rest.

Why locate_and_search?

It combines geocoding and search in one call, so you pass a plain-English query instead of looking up coordinates first. The endpoint reference lists every parameter if you want to tune language, country, or zoom.

Step 4: Paginate past the 200 limit

One request gives you up to 200 results. A busy search like “coffee shops in Austin” has more than that, so to get the full list we loop: bump the offset by the page size each round, collect results, and stop when the API runs out of new ones.

Two things matter here. First, we dedupe by google_id, because paginated map results occasionally repeat a business across page boundaries. Second, we sleep a second between calls to stay polite and within the rate limit.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import time

MAX_PAGE_SIZE = 200    # the most the API returns per request

def fetch_page(query, offset, limit=MAX_PAGE_SIZE):
    params = {
        "query": query, "language": "en", "country": "us",
        "limit": min(limit, MAX_PAGE_SIZE), "offset": offset,
    }
    resp = requests.get(URL, headers=headers, params=params, timeout=30)
    return resp.json()

def collect_businesses(query, target=300, page_size=MAX_PAGE_SIZE):
    seen, rows, offset = set(), [], 0
    while len(rows) < target:
        payload = fetch_page(query, offset, page_size)
        if not payload.get("status"):
            print(f"status=false at offset {offset}, stopping")
            break
        batch = payload.get("data", [])
        if not batch:
            break
        for biz in batch:
            bid = biz.get("google_id")
            if not bid or bid in seen:
                continue
            seen.add(bid)
            rows.append(biz)
        print(f"offset {offset}: +{len(batch)} ({len(rows)} unique)")
        if len(batch) < page_size:
            break          # last page reached
        offset += page_size
        time.sleep(1)      # respect the rate limit
    return rows[:target]

Notice the guard clauses. We check status before touching the data, bail out on an empty batch, and stop when a page returns fewer results than we asked for. That last condition is how you know you have hit the end of the list, because a short page means there is no next one.

Want 300 businesses? With a page size of 200, that is two requests: offset 0, then offset 200. Want 50? One request is enough, and the loop exits after the first page. The same code handles both.

Step 5: Write the list to CSV

Now the part you came for. The standard library csv module turns our list of businesses into a spreadsheet file with zero extra dependencies.

Each business comes back with 36 fields, so instead of cherry-picking, we keep them all. The only catch is that a few are nested: detailed_address is an object, categories is a list, and working_hours, about, and price_breakdown are deeper structures.

So we flatten first. The address parts each get their own column, plain lists are joined with a pipe, and the deep structures are stored as JSON so nothing is lost.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import csv
import json

# detailed_address is nested; give each part its own column.
ADDRESS_PARTS = ["street", "district", "city", "state", "zip_code", "country"]

def flatten(biz):
    row = {}
    for key, val in biz.items():
        if key == "detailed_address":
            parts = val or {}
            for part in ADDRESS_PARTS:
                row[f"address_{part}"] = parts.get(part, "")
        elif isinstance(val, list) and all(isinstance(x, str) for x in val):
            row[key] = " | ".join(val)              # e.g. categories
        elif isinstance(val, (list, dict)):
            row[key] = json.dumps(val, ensure_ascii=False) if val else ""
        else:
            row[key] = "" if val is None else val
    return row

def write_csv(businesses, path="google_maps_list.csv"):
    rows = [flatten(b) for b in businesses]

    # Column order = union of all keys, preserving first-seen order.
    columns = []
    for row in rows:
        for key in row:
            if key not in columns:
                columns.append(key)

    with open(path, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=columns, extrasaction="ignore")
        writer.writeheader()
        writer.writerows(rows)
    print(f"Wrote {len(rows)} rows x {len(columns)} columns to {path}")

That gives you a 41-column CSV: the 36 fields, with the address split into six of its own.

The encoding="utf-8" matters more than it looks. Business names love accents, emoji, and the occasional curly apostrophe, and skipping it is how you get garbled text when the file lands in Excel. The Python csv docs cover the edge cases if you want to go deeper.

Step 6: Run the whole thing

Wire the three functions together and you have a complete exporter. Pass a query and a target count, get a CSV.

1
2
3
4
5
6
if __name__ == "__main__":
    import sys
    query = sys.argv[1] if len(sys.argv) > 1 else "coffee shops in Austin"
    target = int(sys.argv[2]) if len(sys.argv) > 2 else 300
    businesses = collect_businesses(query, target=target)
    write_csv(businesses)
1
python export.py "coffee shops in Austin" 300

That is it. A few seconds later you have google_maps_list.csv with up to 300 unique coffee shops and all 41 columns per row. Double-click it and it opens straight in Excel or Google Sheets.

Try the Google Maps API free on RapidAPI →

100 requests/month free · No credit card required

The complete, runnable script lives in our public examples repo: blog-web-scraping-code on GitHub , in the export-google-maps-list folder. Clone it, set your key, and run.

Every field you get when you export Google Maps to CSV

You are not stuck with whatever fields Google chooses to show. Our Google Maps listings API returns 36 fields per business, clean and typed, and the script writes every one. These are the columns you will reach for most:

CSV columnAPI fieldExampleWhy it matters
namenameHoundstooth CoffeePrimary identifier
categorymain_categoryCoffee shopFilter and segment
ratingrating4.6Prioritize quality leads
reviews_countreviews_count1840Gauge size and traction
phonefull_phone+15125550143Direct outreach
websitewebsite_urlhoundstoothcoffee.comEnrichment + email lookup
full_addressfull_address401 Congress Ave, AustinTerritory mapping
latitude / longitudelatitude, longitude30.2683, -97.7428Plot on a map or cluster

That table is the shortlist. The script does not stop there. It writes the full response, so your CSV also carries the IDs, the owner, the opening hours, the price breakdown, and more. Here is the complete set, grouped:

Identity & IDs

name google_id place_id cid google_mid

Category & status

main_category categories status can_claim

Ratings & price

rating reviews_count price_range price_breakdown

Contact

full_phone phone website_url website_domain reservation_url order_online_url

Location

full_address address address_* ×6 latitude longitude time_zone

address_* = street, district, city, state, zip, country

Owner, hours & media

owner_name owner_id owner_profile_url working_hours about featured_photo

plus hotel fields when relevant

About the nested fields:

A handful of fields (working_hours, about, price_breakdown) are objects, not plain text. The script keeps them as JSON inside their cell so no data is lost. Drop those columns in Excel if you do not need them, or parse the JSON later when you do.

Need more detail on a business? Call business_details

The search response is a summary. It is deliberately lean so one request can return up to 200 businesses. When you want the full profile for a specific place, reach for a different endpoint: /business_details.

Every result from the list carries a google_id. Pass that value as the business_id parameter to /business_details and you get the deep data the search omits: the full opening hours, popular times by hour, the complete review history with no 5-review cap, every photo, the menu, and the amenities list.

It is one extra call per business, so run it on the shortlist you actually care about, not the whole export. Pull the list with /locate_and_search, filter it down, then fetch the full record only for the rows worth the spend. Our Google Maps data API covers every field both endpoints return.

One honest caveat:

There is no email field. Google Maps does not publish business emails, so neither does any honest API. What you get is the website, which is the hook: scrape the contact page or run it through an enrichment step to find the address. Anyone promising emails straight from Maps is guessing.

What to do with your exported list

A CSV sitting on your disk is not the goal. The goal is what you do with it. Once you can scrape Google Maps business listings on demand, the data becomes the first step in a pipeline.

From list to pipeline

Step 1

Export the list

Category + city → CSV with contact data

Step 2

Filter by signal

Rating, review count, has-website

Step 3

Enrich

Company data, decision-makers, sizing

Step 4

Push to CRM + reach out

Import, assign, contact

The enrichment step is where the rest of the FlyByAPIs suite earns its keep. Once you have a website per business, you can layer in more data with a few more calls, all from the same family of APIs.

Pull funding and headcount with the Crunchbase Data API to sort the serious businesses from the side projects. Find the right person to contact by checking who they are hiring through the Jobs Search API . Check how a business ranks for its own keywords with the Google SERP API before you pitch them on visibility.

Selling to e-commerce sellers instead? The Amazon Product Data API does the same job for marketplace listings. And if your list spans countries, the Translation API handles localizing outreach across 250 languages without a human translator in the loop.

Key takeaway:

The Maps export is the first link in a chain. Pull the list once, then let each enrichment pass add a column. By the time it reaches your CRM, a row is not a pin on a map. It is a qualified lead with context.

Rate limits, troubleshooting, and staying on the right side of the rules

A few things will trip you up the first time. Let me save you the debugging.

!

status is false

The endpoint returns 200 with status:false on a timeout. The script already checks for this and stops. Just retry the offset that failed.

!

Asking for more than 200

The API caps a single response at 200 businesses. Set a higher limit and it still returns 200. To go bigger, page with offset, which the script handles for you.

!

Duplicate rows

Paginated map results overlap at page boundaries. The dedupe-by-google_id set in Step 4 removes them. Do not skip it.

On the legal side, be straight with yourself. Business names, addresses, ratings, and phone numbers are public information, and pulling them for research is common practice. Routing through our Google Maps data extraction tool also keeps you from poking Google’s own properties with automation, which its terms restrict.

But the data carries obligations once you hold it. If you are contacting these businesses, GDPR, CAN-SPAM, and local marketing rules still apply. Collecting a list is not the same as having permission to spam it. Treat it like the lead-research tool it is.

The complete script

Here is everything above stitched into one file. Save it as export.py, set your RAPIDAPI_KEY, and run it. It is also in our examples repo on GitHub if you would rather clone than copy.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
"""
Export a Google Maps List to CSV. FlyByAPIs.

Pulls a full list of businesses from a Google Maps search via the FlyByAPIs
Google Maps Extractor API and writes it to a CSV you can open in Excel or
Google Sheets. One request returns up to 200 businesses, so the script
paginates with `offset` to collect lists larger than that.

Only dependency: requests. Everything else is the Python standard library.

Usage:
    export RAPIDAPI_KEY="your_key_here"
    python export.py                              # coffee shops in Austin, 300 rows
    python export.py "dentists in Chicago" 500    # custom query + target count
"""

import csv
import json
import os
import sys
import time

import requests

API_HOST = "google-maps-extractor2.p.rapidapi.com"
URL = f"https://{API_HOST}/locate_and_search"

# The API returns at most 200 results per request. To get more, page with offset.
MAX_PAGE_SIZE = 200

HEADERS = {
    "X-RapidAPI-Key": os.environ.get("RAPIDAPI_KEY", ""),
    "X-RapidAPI-Host": API_HOST,
}

# detailed_address is a nested object; we give each part its own column.
ADDRESS_PARTS = ["street", "district", "city", "state", "zip_code", "country"]


def fetch_page(query, offset, limit=MAX_PAGE_SIZE):
    """Fetch one page of results from /locate_and_search."""
    params = {
        "query": query,
        "language": "en",
        "country": "us",
        "limit": min(limit, MAX_PAGE_SIZE),   # never ask for more than the cap
        "offset": offset,
    }
    resp = requests.get(URL, headers=HEADERS, params=params, timeout=30)
    resp.raise_for_status()
    return resp.json()


def collect_businesses(query, target=300, page_size=MAX_PAGE_SIZE):
    """Page through the search results until we hit `target` unique businesses."""
    seen, rows, offset = set(), [], 0

    while len(rows) < target:
        payload = fetch_page(query, offset, page_size)

        if not payload.get("status"):
            print(f"API returned status=false at offset {offset}, stopping.")
            break

        batch = payload.get("data", [])
        if not batch:
            break

        for biz in batch:
            bid = biz.get("google_id")
            if not bid or bid in seen:
                continue
            seen.add(bid)
            rows.append(biz)

        print(f"offset {offset}: +{len(batch)} results ({len(rows)} unique so far)")

        if len(batch) < page_size:
            break          # last page reached

        offset += page_size
        time.sleep(1)      # be polite, respect the rate limit

    return rows[:target]


def flatten(biz):
    """Flatten one business into a single row, keeping every field.

    detailed_address is split into address_street, address_city, etc.;
    plain lists are joined with " | "; nested objects (working_hours, about,
    price_breakdown) are kept as JSON so nothing is dropped.
    """
    row = {}
    for key, val in biz.items():
        if key == "detailed_address":
            parts = val or {}
            for part in ADDRESS_PARTS:
                row[f"address_{part}"] = parts.get(part, "")
        elif isinstance(val, list) and all(isinstance(x, str) for x in val):
            row[key] = " | ".join(val)
        elif isinstance(val, (list, dict)):
            row[key] = json.dumps(val, ensure_ascii=False) if val else ""
        else:
            row[key] = "" if val is None else val
    return row


def write_csv(businesses, path="google_maps_list.csv"):
    """Write every field the API returns to a UTF-8 CSV (one row per business)."""
    rows = [flatten(b) for b in businesses]

    # Column order = union of all keys, preserving first-seen order.
    columns = []
    for row in rows:
        for key in row:
            if key not in columns:
                columns.append(key)

    with open(path, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=columns, extrasaction="ignore")
        writer.writeheader()
        writer.writerows(rows)
    print(f"Wrote {len(rows)} rows x {len(columns)} columns to {path}")


def main():
    if not HEADERS["X-RapidAPI-Key"]:
        sys.exit("Set RAPIDAPI_KEY first:  export RAPIDAPI_KEY=your_key_here")

    query = sys.argv[1] if len(sys.argv) > 1 else "coffee shops in Austin"
    target = int(sys.argv[2]) if len(sys.argv) > 2 else 300

    print(f"Searching: {query!r} (target {target} businesses)\n")
    businesses = collect_businesses(query, target=target)
    write_csv(businesses)


if __name__ == "__main__":
    main()

Wrapping up

Remember the coffee shops you could see but not save? That problem is gone. One API call, a short pagination loop, and a CSV writer turn any Maps search into a spreadsheet you can actually use.

You now have a working Python exporter. Run it for coffee shops in Austin, then change two words and run it for dentists in your own city. The Google Maps data API does the heavy lifting, and your script just decides what to keep.

Grab a free key, clone the repo on GitHub , and pull your first list. If you build something useful on top of the Google Maps business data API , I would genuinely like to hear what it is.

Get your free API key →

100 requests/month free · No credit card required

P.S. The one-second sleep in the script is not paranoia, it is courtesy. Hammer any API and you will get throttled. Be a good citizen and your scripts last longer.

Oriol.

FAQ

Frequently Asked Questions

Q Is there a way to export a Google Maps list?

The Google Maps app has no export button, so the practical route is the FlyByAPIs Google Maps API. Send it a search query, get every matching business back as structured JSON, then write it to a spreadsheet with a few lines of Python. Names, addresses, phones, ratings, websites, hours, and more, all in one file.

Q Can you export Google Maps to CSV?

Yes, with a short script. Call the FlyByAPIs /locate_and_search endpoint, which returns clean JSON, then write that JSON to CSV using Python's built-in csv module. You decide the columns: name, rating, reviews count, phone, website, address. No browser extensions, no manual copy-paste.

Q How do I export map data from Google Maps?

Pick a search query and a location, send it to the FlyByAPIs Google Maps API, and it returns every matching business with names, addresses, coordinates, ratings, and contact details. One request returns up to 200 businesses. Page with the offset parameter to pull larger lists, then save to CSV or push into your CRM.

Q How many businesses can I pull in one request?

Up to 200 per request via the limit parameter. To collect a list larger than that, paginate: increase the offset by your page size each call until the API stops returning new businesses. The script in this tutorial does this automatically and dedupes by business ID so you never get a row twice.

Q Is scraping Google Maps data legal?

Extracting public business listings (names, addresses, phones, ratings) for legitimate use like lead research is widely practiced, but Google's Terms of Service restrict automated access to its own properties. Using a dedicated API shifts that responsibility to the provider and keeps you off Google's anti-bot radar. Always respect privacy laws like GDPR when contacting the businesses you collect.

Q Do I need to know how to code to export a Google Maps list?

A little. The Python in this tutorial is copy-paste ready and uses only the standard library plus the requests package, so you can run it without writing anything from scratch. If you can install Python and paste a command into a terminal, you can pull a full list to CSV.

Q Can I export Google Maps results to Excel?

Yes. CSV files open directly in Excel and Google Sheets with no conversion, so the script here gives you a Google Maps list in Excel by double-clicking the output file. If you want a native .xlsx with formatting, add pandas and openpyxl, but plain CSV keeps the dependencies to zero.
Share this article
Oriol Marti
Oriol Marti
Founder & CEO

Computer engineer and entrepreneur based in Andorra. Founder and CEO of FlyByAPIs, building reliable web data APIs for developers worldwide.

Free tier available

Ready to stop maintaining scrapers?

Production-ready APIs for web data extraction. Whatever you're building, up and running in minutes.

Start for free on RapidAPI