SERP API for Local SEO: Track Rankings by City and Language
Learn how to use a SERP API for local SEO rank tracking. Use Python to collect search results by city, country, language, and device, then export ranking data to CSV.
Local SEO rank tracking gets tricky fast.
A business may rank well in one city and disappear in another. The same keyword can produce different results by country, language, device, or even neighborhood-level intent. For agencies, franchise brands, SaaS companies with local landing pages, and marketplace teams, one national ranking report is usually not enough.
Local SEO needs location-aware SERP data.
A SERP API helps you collect search results programmatically, so you can track rankings by city, language, country, and device without manually checking Google from different locations.
Quick answer
To track local SEO rankings, send a SERP API request for each keyword and target market, including city, country, language, and device parameters. Then extract organic results, match your target domain, store the ranking position, and compare ranking snapshots over time.
A practical workflow looks like this:
Keyword list
→ City and language list
→ SERP API request
→ Extract organic results
→ Match target domain
→ Store ranking snapshot
→ Compare changes by city and language
Why city-level ranking matters
Local SEO is not only about whether a website ranks. It is about where it ranks.
For example:
Keyword: emergency plumber
City: Austin
Language: English
may produce different results from:
Keyword: emergency plumber
City: Dallas
Language: English
And language adds another layer:
Keyword: dentist near me
City: Miami
Language: English
Keyword: dentista cerca de mí
City: Miami
Language: Spanish
If your report only shows one average ranking, it may hide important local differences. A city-level report can show where visibility is strong, where landing pages are weak, and where competitors are winning.
What should a local SEO tracker collect?
A useful local rank tracking row should include more than keyword and position.
|
Field |
Why it matters |
|
|
The query being tracked |
|
|
The website you want to monitor |
|
|
City or local market |
|
|
Country-level market context |
|
|
Search language |
|
|
Desktop or mobile |
|
|
Organic ranking position |
|
|
Exact page that ranked |
|
|
Search result title |
|
|
Visible search result description |
|
|
Timestamp for historical comparison |
Without location, language, device, and timestamp, a ranking number is easy to misread.
Position 4 in New York mobile search is not the same data point as position 4 in Chicago desktop search.
Organic rankings vs local pack rankings
Local SEO often includes different result types.
|
Result type |
What it tells you |
|
Organic results |
Which pages rank in standard web results |
|
Local pack |
Which businesses appear in map-style local results |
|
Maps results |
How business profiles rank in map search |
|
Ads |
Which competitors are paying for visibility |
|
People Also Ask |
Which local questions users ask |
|
Shopping results |
Which products and sellers appear for local commercial queries |
This article focuses on organic ranking tracking, but the same structure can be extended to Maps or local pack workflows. Talordata’s SEO monitoring page describes local SEO monitoring use cases, including map rankings, business profiles, ratings, reviews, and local visibility data.
Step 1: Prepare keywords and locations
Start small. Use a few keywords and a few cities before scaling.
KEYWORDS = [
"emergency plumber",
"dentist near me",
"coffee shop",
]
LOCATIONS = [
{
"city": "Austin",
"country": "us",
"language": "en",
"location": "Austin, Texas, United States",
"device": "desktop",
},
{
"city": "Dallas",
"country": "us",
"language": "en",
"location": "Dallas, Texas, United States",
"device": "desktop",
},
{
"city": "Miami",
"country": "us",
"language": "es",
"location": "Miami, Florida, United States",
"device": "mobile",
},
]
TARGET_DOMAIN = "example.com"
For production, store this in a database or spreadsheet. Each row can represent one tracked combination:
keyword + city + country + language + device
That combination becomes your rank tracking unit.
Step 2: Send localized SERP API requests
Keep the request layer separate from parsing. That makes the workflow easier to maintain. View complete document>>
import os
import requests
SERP_API_KEY = os.getenv("TALORDATA_API_KEY")
SERP_ENDPOINT = os.getenv("TALORDATA_SERP_ENDPOINT")
def call_serp_api(payload):
if not SERP_API_KEY:
raise RuntimeError("Missing TALORDATA_API_KEY")
headers = {
"Authorization": f"Bearer {SERP_API_KEY}",
"Content-Type": "application/json",
}
response = requests.post(
SERP_ENDPOINT,
json=payload,
headers=headers,
timeout=30,
)
response.raise_for_status()
return response.json()
def fetch_local_serp(keyword, location_config):
payload = {
"engine": "google",
"q": keyword,
"gl": location_config["country"],
"hl": location_config["language"],
"location": location_config["location"],
"device": location_config["device"],
"num": 10,
"output": "json",
}
return call_serp_api(payload)
The important part is not the exact variable name. It is that every request includes the local context.
Step 3: Extract organic results
SERP responses may use different field names depending on provider or search type, so the parser should be flexible.
from urllib.parse import urlparse
def clean_text(value):
if not value:
return ""
return " ".join(str(value).split())
def get_domain(url):
if not url:
return ""
parsed = urlparse(url)
return parsed.netloc.replace("www.", "")
def get_organic_results(serp_json):
return (
serp_json.get("organic_results")
or serp_json.get("organic")
or serp_json.get("results")
or []
)
def normalize_organic_results(serp_json):
rows = []
for index, item in enumerate(get_organic_results(serp_json), start=1):
url = item.get("link") or item.get("url") or ""
rows.append({
"position": item.get("position") or item.get("rank") or index,
"title": clean_text(item.get("title")),
"url": url,
"domain": get_domain(url),
"snippet": clean_text(item.get("snippet") or item.get("description")),
})
return rows
Now every city and language search returns the same clean structure.
Step 4: Match the target domain
The rank tracker needs to answer one question:
Did the target domain appear in this local SERP, and where?
def find_target_ranking(results, target_domain):
target = target_domain.replace("www.", "").lower()
for item in results:
domain = item["domain"].lower()
if domain == target or domain.endswith("." + target):
return {
"position": item["position"],
"matched_url": item["url"],
"title": item["title"],
"snippet": item["snippet"],
}
return {
"position": None,
"matched_url": "",
"title": "",
"snippet": "",
}
Use None when the domain is not found. Do not quietly turn it into 99 or 100 unless your reporting system clearly defines that convention.
Step 5: Save local ranking snapshots
CSV is enough for the first version.
import csv
from datetime import datetime, timezone
CSV_COLUMNS = [
"keyword",
"target_domain",
"city",
"country",
"language",
"location",
"device",
"position",
"matched_url",
"title",
"snippet",
"collected_at",
]
def export_rankings(rows, filename="local_seo_rankings.csv"):
with open(filename, mode="w", newline="", encoding="utf-8") as file:
writer = csv.DictWriter(file, fieldnames=CSV_COLUMNS)
writer.writeheader()
writer.writerows(rows)
def run_local_rank_tracking():
collected_at = datetime.now(timezone.utc).isoformat()
output_rows = []
for keyword in KEYWORDS:
for location_config in LOCATIONS:
serp_json = fetch_local_serp(keyword, location_config)
organic_results = normalize_organic_results(serp_json)
ranking = find_target_ranking(organic_results, TARGET_DOMAIN)
output_rows.append({
"keyword": keyword,
"target_domain": TARGET_DOMAIN,
"city": location_config["city"],
"country": location_config["country"],
"language": location_config["language"],
"location": location_config["location"],
"device": location_config["device"],
"position": ranking["position"],
"matched_url": ranking["matched_url"],
"title": ranking["title"],
"snippet": ranking["snippet"],
"collected_at": collected_at,
})
export_rankings(output_rows)
if __name__ == "__main__":
run_local_rank_tracking()
Run this daily, and you have a local ranking history.
How to use the ranking data
Once you have snapshots, you can answer more useful questions.
|
Question |
How to answer it |
|
Which cities are weak? |
Group by city and compare average position |
|
Which pages rank locally? |
Group by |
|
Which language performs better? |
Compare English, Spanish, French, or other language rows |
|
Where did rankings drop? |
Compare today’s snapshot with yesterday’s |
|
Which keywords need local pages? |
Find keywords with no ranking in target cities |
|
Are mobile rankings different? |
Compare desktop and mobile rows |
This turns local SEO from guesswork into a repeatable reporting workflow.
When to use a database
CSV is fine for testing. Once the workflow runs regularly, use a database.
A simple table might include:
|
Column |
Type |
|
|
UUID or integer |
|
|
Text |
|
|
Text |
|
|
Text |
|
|
Text |
|
|
Text |
|
|
Text |
|
|
Integer or null |
|
|
Text |
|
|
Timestamp |
From there, you can build local SEO dashboards, ranking alerts, city comparison reports, franchise visibility reports, or weekly SEO summaries.
Best practices
Track the same keyword and location combinations consistently. Changing your tracked set too often makes trends unreliable.
Store city, country, language, device, and timestamp with every row. Local ranking data without context is not useful.
Separate organic rankings from Maps, local pack, ads, and Shopping results. They belong in different reports.
Keep raw API responses during development. They help you debug parser issues and SERP feature changes.
Do not over-track stable keywords. Daily tracking is often enough for local SEO. High-frequency tracking should be reserved for important launches or volatile markets.
Use local language queries. Do not only translate the interface language. Search behavior can change with local phrasing.
FAQ
What is a SERP API for local SEO?
A SERP API for local SEO helps you collect search engine results programmatically by keyword, location, language, country, and device. This makes it easier to track local rankings at scale.
Why should I track rankings by city?
Google results can vary by city. A business may rank well in one market and poorly in another, even for the same keyword.
Why does language matter in local SEO tracking?
Search language affects query intent, result titles, snippets, and ranking pages. In multilingual markets, tracking only one language can hide important visibility gaps.
Should I track desktop and mobile separately?
Yes. Desktop and mobile SERPs can differ. Store device as a separate field so the report does not mix different result contexts.
Can this workflow track Google Maps rankings?
Yes, but Maps rankings should be parsed and reported separately from organic rankings. Local pack and Maps data answer different questions than standard web results.
Is CSV enough for local SEO rank tracking?
CSV works for a prototype or small report. For recurring tracking, alerts, historical trends, and dashboards, use a database.