A charcoal relief model of a city street grid with one map pin glowing orange, with the headline Local rank tracking by city and the Massive logo.
All Posts

Local Rank Tracking: How to Check Google Rankings by City

Franklin Uche
Franklin Uche · Community Lead
Open markdown

Local rank tracking means checking where a website ranks on Google for the same search, as seen from each city or area it serves. It is rank tracking with a location attached. It matters because Google uses location to choose results: "your current location is used to give you more helpful results," and a search for coffee shops shows ones nearby "even if you didn't include a location in your search," as Google's own help page puts it (Google Search Help). A chain of clinics can rank third in one suburb and not appear in the next town over.

This guide covers the teams that track local rankings, the two ways to make a search "local," and an offline-tested Python script that checks rankings city by city with Massive's Search endpoint. It sits next to our earlier guide to ChatGPT rank tracking, which does the same job for AI answers.

Key Takeaways
  • Google estimates location from several sources. One is the IP address, which Google says is "roughly based on geography."
  • A local rank check needs a search that Google treats as coming from the target city. You can either search from a device in that city or emulate the location with a uule string.
  • Massive's Search endpoint does the first with country, subdivision, and city parameters. With uule, its docs say "if possible, the actual location is searched from not emulated."
  • Results come back as rendered HTML, not JSON, so you count positions yourself. Our parser and loop below pass 10 offline tests; they have not been run against a live Google page.
  • Track a fixed keyword list, the same cities, and fresh results (expiration=0) on a regular schedule, and save the raw pages.

Who needs local rank tracking?

Three kinds of teams run it at scale:

  • Local SEO agencies report rankings for clients with one or many locations, often across a grid of points around each address.
  • Multi-location brands (franchises, retailers, clinics, home services) compare how each branch ranks in its own market.
  • Rank tracking tools sell local data to both groups, and they need location-accurate results on demand.

They share one problem: the person running the check is rarely in the city being checked. A search run from an office in New York shows New York results.

Why do Google rankings change by location?

Google's help page lists the sources it combines to estimate where a searcher is, including device location settings and "the IP address of your Internet connection." On IP addresses, Google says they "are roughly based on geography," so "any website you use, including google.com, may get some information about your general area." When Google used the IP address to place a search, the results page says "From your internet address" at the bottom.

That gives a local rank check two requirements: Google has to see the search as coming from the target area, and the check has to be repeatable, so last week's number and this week's number mean the same thing.

Real location or emulated location?

There are two common ways to make a search local:

Method How it works Trade-off
Search from the city The request leaves from a device or connection in the target city Matches what a local searcher sees; depends on having a device there
Emulate the location Add a uule string, an encoded location, to a search sent from anywhere Works for any place; Google still sees a request from elsewhere

Massive's Search endpoint supports both. Its geotargeting parameters route each search "through one of more than 190 countries, by subdivision and city" (Massive docs, geotargeting), using real consumer devices on Massive's network. It also accepts uule, and the docs add that "if possible, the actual location is searched from not emulated" (Massive docs, search). For most local SEO work, the city parameter is the simpler starting point.

A city-by-city rank tracking loop on Massive's Search endpoint.

How to track Google rankings by city

The steps below use Python with requests and beautifulsoup4. Set your API token as MASSIVE_TOKEN.

1. Fetch the results page from each city

serp() sends one search from one city. serps=2 fetches two results pages, and expiration=0 turns off Massive's cache, which otherwise reuses results for up to a day. A live search averages a few seconds, but the docs allow up to 3 minutes per call for retries, so the timeout is generous.

PYTHON
import os
import urllib.parse as up
import requests
from bs4 import BeautifulSoup
API = ("https://render."
       "joinmassive.com"
       "/search")
def serp(terms, country, city):
    token = os.environ[
        "MASSIVE_TOKEN"]
    hdr = {"Authorization":
           f"Bearer {token}"}
    r = requests.get(
        API,
        headers=hdr,
        params={
            "terms": terms,
            "country": country,
            "city": city,
            "serps": 2,
            "expiration": 0},
        timeout=200)
    r.raise_for_status()
    return r.text

City names follow the "commonly spelled name" from GeoNames, and the docs note they are "temporarily case sensitive," so write Chicago, not chicago.

2. Find your position in the HTML

The Search endpoint returns "rendered HTML," the same page a browser would show. rank() counts links that contain an <h3> heading and an absolute URL, which is how organic result titles are usually marked up, and returns the position of the first one on your domain or a subdomain. Results from both pages count in order.

PYTHON
def rank(html, domain):
    domain = domain.lower()
    soup = BeautifulSoup(
        html, "html.parser")
    pos = 0
    for a in soup.find_all("a"):
        if not a.find("h3"):
            continue
        host = up.urlparse(
            a.get("href", "")
        ).netloc.lower()
        if not host:
            continue
        pos += 1
        if host == domain:
            return pos
        if host.endswith(
                "." + domain):
            return pos
    return None

This is a heuristic. Google changes its markup, and features such as the map pack, ads, and "People also ask" are laid out differently, so check it against a few saved pages for your own keywords before you rely on it. If you also want AI Overviews or People-also-ask blocks in the page, add awaiting=ai or awaiting=answers; the Search docs cap the wait for an AI Overview at one minute. Our post on AI Overviews and PAA on Search covers both.

3. Run the grid

grid() checks one keyword across a list of cities and returns a position for each, or None when the domain isn't on the pages fetched.

PYTHON
def grid(terms, places, domain):
    out = {}
    for cc, city in places:
        html = serp(
            terms, cc, city)
        out[city] = rank(
            html, domain)
    return out

Run it for each keyword, for example grid("emergency plumber", [("US", "Chicago"), ("US", "Austin")], "example.com"). It returns a dict shaped like this illustrative output, not a live result:

text
{'Chicago': 3,
'Austin': None}

Write each run to a CSV or JSON file with the keyword, the date, and the city, and keep the raw HTML next to it. The Search docs say the endpoint autoscales and that a 503 should resolve within a minute, so retry a failed city after a short wait rather than stopping the whole grid.

All three functions are tested offline: 10 tests cover the request parameters, subdomain, look-alike, and mixed-case domain matching, counting across pages, and the city loop (see the methods note below). Without a token, the endpoint returns 401 with "Authorization header must be provided," which we confirmed on November 1, 2026. That is the first error a new integration sees.

What about the map pack and geo-grids?

For a search like "emergency plumber," the map pack (the local three-pack with a map) is often what customers look at first, and rank() ignores it. The function counts organic results only: links with an <h3> title. Map pack listings use different markup, so they need their own parser, written against pages you have saved and checked, because Google's layout changes. Many local SEO tools also track a grid of points around a business address rather than one city. That needs finer locations than the city parameter gives, such as a uule string per grid point. Massive's Search endpoint accepts uule and, per its docs, searches from the actual location when it can. Start with organic positions per city, and add map pack and grid tracking once the basic loop is stable.

Google's own guidance explains why map results move so much by location: "Local results are mainly based on relevance, distance, and popularity" (the page's section calls the last one prominence), and distance "refers to how far each business is from the customer who's searching" (Google Business Profile Help). Distance is the one factor that changes with every city you check.

Tips for accurate local rankings

  1. Fix the keyword list. Change it only on purpose, or week-over-week numbers stop being comparable.
  2. Use the same cities every time. If you serve a metro area, pick the suburbs that matter, and check each one.
  3. Turn off caching for tracking runs. expiration=0 makes every check a live search.
  4. Track mobile separately. The device parameter emulates a smartphone or tablet from the list at /search/devices. Mobile and desktop results may differ, so track them as separate series.
  5. Keep the raw pages. When a position changes, the saved HTML shows whether a map pack, an ad block, or a competitor moved.
  6. Budget the calls. Each successful Search call costs one base credit; the Web Render pricing page has current rates. Keywords times cities times runs adds up quickly.

If you buy local SERP data rather than build it, our local SERP tracking page covers proxies for rank tracking, and proxy geo-targeting by city, ZIP code, and ASN covers the network-level options.

Methods note: the tests use a small synthetic page shaped like a results page, not a saved Google page, and replace network calls with a stub. They check the counting rules and the documented request parameters, not live rankings.

The bottom line

  • Google picks results by location, so local rank tracking only means something with a city attached.
  • Searching from a device in the target city matches what local searchers see; uule emulation is the fallback.
  • Fix the keywords and cities, fetch fresh results, count positions in the HTML, and keep the raw pages.

Sources

Frequently Asked Questions

What is local rank tracking?+

Local rank tracking measures where a website ranks on Google for a search as seen from a specific city or area. Google uses the searcher's location to pick results, so the same keyword can rank differently in different cities.

How do I check Google rankings for a different city?+

Run the search so that Google sees it as coming from that city, either from a device located there or with an emulated location such as a uule string. Massive's Search endpoint does the first with its country, subdivision, and city parameters.

Why are my Google rankings different in another location?+

Google estimates your location from sources such as your IP address and device settings, and shows results it considers more helpful for that place. Local businesses and map results change the most.

What is uule in Google search?+

uule is an encoded location string that tells Google to treat a search as coming from a given place. Massive's Search endpoint accepts it, and its docs say it searches from the actual location when possible instead of emulating it.

How often should I track local rankings?+

Weekly works for most local SEO reporting, and daily for volatile or high-value terms. Use the same keywords, cities, and device type on every run, and turn off caching so each check is a live search.