A local SEO rank check becomes more useful when it keeps the location context attached to every result. Without that context, a report can say that a page ranks position 4 for a keyword, but it cannot explain where that position was observed, which market changed, or whether two locations are showing different competitors.
This post walks through a small location-aware SERP monitor that stores keyword, location, language, country, rank, URL, and snapshot time in one table. The goal is not to build a full SEO platform. The goal is to create a repeatable output that a local SEO team can inspect every week.
What the monitor should answer
For each keyword, the monitor should answer a few practical questions:
- What does Google return for this keyword in each target location?
- Which URL from our domain appears, if any?
- What position was observed?
- Which competitors are visible in the same location?
- Did the result change compared with the previous snapshot?
The important part is that the location is not a note in a file name. It is a first-class field in the output.
Minimal table design
A useful local rank table can start with these columns:
snapshot_date
keyword
location_label
gl
hl
device
rank
result_title
result_url
result_domain
matched_own_domain
is_own_result
serp_top_domains
notes
This structure keeps the ranking evidence and the local context together. If the same keyword is checked for New York, Austin, and Toronto, those become three separate rows instead of one vague metric.
Requesting a location-aware SERP
TalorData SERP API accepts Google SERP parameters such as q, location, gl, hl, device, and json=2. A simple request can look like this:
curl -X POST 'https://serpapi.talordata.net/serp/v1/request' \
-H 'Authorization: Bearer <TALORDATA_TOKEN>' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'engine=google' \
-d 'q=emergency plumber near me' \
-d 'location=Austin, Texas, United States' \
-d 'gl=us' \
-d 'hl=en' \
-d 'device=desktop' \
-d 'json=2'
If you want to build this into a script, keep the keyword and location list outside the request function. That makes it easier to add new cities without touching the API logic.
Example keyword and location config
KEYWORDS = [
"emergency plumber near me",
"water heater repair",
"drain cleaning service",
]
LOCATIONS = [
{
"label": "Austin, TX",
"location": "Austin, Texas, United States",
"gl": "us",
"hl": "en",
},
{
"label": "Dallas, TX",
"location": "Dallas, Texas, United States",
"gl": "us",
"hl": "en",
},
{
"label": "Toronto, ON",
"location": "Toronto, Ontario, Canada",
"gl": "ca",
"hl": "en",
},
]
This format also makes the final report more readable. The report can show Austin, TX while the request still sends the full location value.
Normalize organic results
The SERP response can include an organic array. For monitoring, normalize it into flat rows before doing any comparison.
from urllib.parse import urlparse
OWN_DOMAIN = "example.com"
def domain_from_url(url: str) -> str:
host = urlparse(url).netloc.lower()
return host[4:] if host.startswith("www.") else host
def normalize_organic(snapshot_date, keyword, location_cfg, response_json):
rows = []
for item in response_json.get("organic", []):
url = item.get("link") or item.get("url") or ""
domain = domain_from_url(url) if url else ""
position = item.get("position") or item.get("rank")
rows.append({
"snapshot_date": snapshot_date,
"keyword": keyword,
"location_label": location_cfg["label"],
"gl": location_cfg["gl"],
"hl": location_cfg["hl"],
"rank": position,
"result_title": item.get("title", ""),
"result_url": url,
"result_domain": domain,
"matched_own_domain": OWN_DOMAIN,
"is_own_result": domain == OWN_DOMAIN or domain.endswith("." + OWN_DOMAIN),
})
return rows
Keep this normalization boring. The monitor is easier to debug when every row represents one observed organic result in one location.
Reduce rows into a weekly monitor
Once you have normalized rows, you can produce a smaller monitoring table for stakeholders. For example, you may only need the first own-domain result and the top visible competitor domains.
def summarize_location(keyword, location_label, rows):
location_rows = [
row for row in rows
if row["keyword"] == keyword and row["location_label"] == location_label
]
own_rows = [row for row in location_rows if row["is_own_result"]]
own_rows = sorted(own_rows, key=lambda row: row["rank"] or 999)
top_domains = []
for row in sorted(location_rows, key=lambda row: row["rank"] or 999)[:10]:
domain = row["result_domain"]
if domain and domain not in top_domains:
top_domains.append(domain)
first_own = own_rows[0] if own_rows else None
return {
"keyword": keyword,
"location_label": location_label,
"own_rank": first_own["rank"] if first_own else None,
"own_url": first_own["result_url"] if first_own else "",
"top_domains": ", ".join(top_domains[:5]),
"note": "Own result not found in observed organic results" if not first_own else "",
}
This gives you a compact output without losing the evidence. If a ranking drops in one city, you can go back to the normalized rows and inspect what changed.
Add change detection
A local SEO monitor becomes more useful when it compares the current snapshot with the previous one.
def compare_rank(previous_rank, current_rank):
if previous_rank is None and current_rank is None:
return "not_visible"
if previous_rank is None:
return "newly_visible"
if current_rank is None:
return "lost_visibility"
if current_rank < previous_rank:
return "improved"
if current_rank > previous_rank:
return "declined"
return "unchanged"
The final report should not only say rank = 7. It should say something like declined from 3 to 7 in Austin, TX, or newly visible in Toronto, ON.
What to watch for
Location-aware monitoring has a few practical traps:
- Do not mix desktop and mobile results in the same comparison.
- Do not compare different countries without keeping
glvisible. - Do not hide the observed location behind a generic market label.
- Do not treat one city result as a national ranking.
- Do not overwrite old snapshots; append new ones.
For local SEO, the audit trail matters. The team needs to know when a snapshot was taken, what parameters were used, and what result was actually observed.
Final output
A simple weekly output can look like this:
Date: 2026-08-07
Keyword: emergency plumber near me
Location: Austin, TX
Device: desktop
Own rank: 5
Own URL: https://example.com/austin/emergency-plumbing
Top visible domains: yelp.com, angi.com, example.com, localcompetitor.com
Change: declined from 3 to 5
Note: review local landing page title and competing directory pages
That is much more useful than a single average position. It tells the team where the observation came from and what to inspect next.
If you are building this kind of monitor, TalorData SERP API can provide structured Google SERP data for location-aware checks. New accounts can start with 500 responses, which is enough to test a small keyword-location matrix before expanding the monitor.
Top comments (0)