Regional Settings

Language

No languages match your search.

Data collection · 2026-06-10 · Updated

Google Maps data for local SEO teams: a Playwright workflow

Collect public business names, ratings and links from Google Maps for local SEO and market research, using Playwright and proxies placed in the city you study.

Local SEO agencies, franchise teams and market researchers use Google Maps as a live directory of the businesses in an area: names, addresses, ratings and review counts. That public information answers practical questions, such as how many competitors operate in a district or how a client's rating compares with theirs. Because Maps is assembled in the browser by JavaScript, fetching the URL with a plain HTTP request returns almost nothing useful. This tutorial uses Playwright to drive a real browser, scroll the results and read each listing, with traffic sent through proxies so the session stays stable.

Places API or the public interface?

Google offers the Places API for sanctioned, structured access, and for production or commercial use it is the option to evaluate first. Working with the public Maps interface suits modest research where the API does not fit. Either way, review Google's robots.txt and Terms of Service before you start. Collect only public business information and never personal data about individuals. Do not try to get past CAPTCHAs or logins, keep the request rate gentle with real pauses, and cache results to avoid repeat fetches.

Why a browser, and why a proxy in the right city

Playwright drives Chromium, Firefox or WebKit and holds off reading the page until the elements it needs exist, which is exactly what a JavaScript-built app like Maps requires. The proxy's location matters just as much: Google shapes results around where the searcher appears to be, and a stream of automated searches from one IP soon meets CAPTCHAs. Put the exit in the city you are studying, using residential proxies, and the results describe that area, which is what a local report needs. Our premium proxies add stability for longer runs. Every type is compared on the proxies page, and teams focused on local search should also read proxies for SEO.

Getting Playwright ready

pip install playwright
playwright install chromium

Start the browser behind an authenticated proxy

Proxy details, login included, are part of Playwright's launch options, so an order's host:port address and credentials need no extra tooling:

from playwright.sync_api import sync_playwright

PROXY = {
    "server": "http://HOST:PORT",
    "username": "USER",
    "password": "PASS",
}

def launch(playwright):
    browser = playwright.chromium.launch(
        headless=True,
        proxy=PROXY,
    )
    context = browser.new_context(
        locale="en-US",
        viewport={"width": 1280, "height": 900},
    )
    return browser, context

Run a search

No form filling is needed. The search term goes into the Maps URL with its spaces turned into plus signs, and the function then waits until the results feed exists on the page:

def open_search(context, query):
    page = context.new_page()
    url = f"https://www.google.com/maps/search/{query.replace(' ', '+')}"
    page.goto(url, wait_until="domcontentloaded")
    page.wait_for_selector('div[role="feed"]', timeout=30000)
    return page

Load the full results list

Results arrive in batches while the side panel is scrolled. The loop jumps to the bottom of the feed, waits, and compares heights; once the height stops changing, every listing has loaded:

import time

def scroll_results(page, max_rounds=15):
    feed = page.query_selector('div[role="feed"]')
    last_height = 0
    for _ in range(max_rounds):
        page.evaluate(
            "(el) => el.scrollTo(0, el.scrollHeight)", feed
        )
        time.sleep(2)  # let new results load
        new_height = page.evaluate("(el) => el.scrollHeight", feed)
        if new_height == last_height:
            break
        last_height = new_height

Read each listing

Inside the feed, each business is a link whose aria-label carries its name, and the rating text sits in the same card. Google revises this markup from time to time, so every field is checked and incomplete entries are dropped:

def extract_listings(page):
    results = []
    cards = page.query_selector_all('div[role="feed"] a[href*="/maps/place/"]')
    for card in cards:
        name = card.get_attribute("aria-label")
        if not name:
            continue
        parent = card.evaluate_handle("el => el.parentElement")
        rating_el = parent.query_selector('span[role="img"]')
        rating = (
            rating_el.get_attribute("aria-label") if rating_el else None
        )
        results.append({
            "name": name,
            "rating": rating,
            "url": card.get_attribute("href"),
        })
    return results

Run it end to end

import csv

def scrape(query):
    with sync_playwright() as pw:
        browser, context = launch(pw)
        page = open_search(context, query)
        scroll_results(page)
        data = extract_listings(page)
        browser.close()
    return data

if __name__ == "__main__":
    rows = scrape("coffee shops in Austin")
    with open("maps.csv", "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=["name", "rating", "url"])
        writer.writeheader()
        writer.writerows(rows)
    print(f"Saved {len(rows)} listings")

The example searches for coffee shops in Austin and saves the name, rating and link of each listing to a CSV file for the team to analyse.

Notes for local SEO and research teams

  • Match the proxy's city to the city in the search term; otherwise the ratings and the order of results describe somewhere else.
  • Randomise the pauses between scrolls and page loads.
  • On bigger jobs, give each search a fresh residential IP, and look up the exit in our my IP tool.
  • Read a CAPTCHA as a speed warning and slow the whole job down, rather than retrying straight away.
  • Put the endpoints through the proxy checker before each run.

Playwright copes with the dynamic page and residential proxies take care of location, so the build is an afternoon's work for one person. Keep the volume modest and within Google's terms and rate limits, and graduate to the Places API when the tool turns into production work.

Planning this for your team?

Compare the four proxy types, pick the one that fits the job and pay per GB. Credentials reach your dashboard as soon as payment clears.

Questions about your setup? Talk to our support team, available 24/7.

More reading for your team

Cheapest Proxies logo

or
Continue with Email

By continuing you agree to our Terms and Privacy Policy.

Already have an account?

New here?

Subscriber offer

10% off
your team’s next order

Enter your work email and we’ll send the code there. It applies at checkout to any proxy plan, from datacenter to residential premium.

We send the code, then an occasional offer. To stop those emails, write to support and we’ll remove your address.

Your code is ready

Copy the 10% code below and paste it into the promo code field at checkout. A copy goes to your inbox too, so you can forward it to whoever places the order.

Choose a residential plan →