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.
Quick answer: A SERP API helps local SEO teams track rankings by city, country, language, and device. Instead of manually searching from different locations, you can send structured requests, collect ranking positions, normalize the results, and export them to CSV for reports or dashboards.
Local SEO is not only about whether a page ranks.
It is also about where it ranks.
The same keyword can return different results in New York, Austin, Toronto, London, or Singapore. Results can also change by language and device. That matters for agencies, local businesses, marketplaces, franchise brands, and teams that need city-level visibility.
A SERP API is useful because it lets you collect structured search results with location, language, country, device, and pagination controls.
What We Will Build
We will create a Python workflow that:
Keyword list
→ City and language list
→ SERP API request
→ Extract organic results
→ Find target domain rankings
→ Export local ranking data to CSV
The final CSV can include:
|
Field |
Meaning |
|
|
Search query |
|
|
Target city |
|
|
Target market |
|
|
Search language |
|
|
Desktop or mobile |
|
|
Ranking position of the target domain |
|
|
Ranking URL |
|
|
Ranking page title |
|
|
Domains in the top results |
|
|
Collection timestamp |
This setup is useful for local SEO reports, city-level ranking checks, multilingual SEO tracking, franchise SEO, competitor monitoring, and AI-assisted SEO dashboards.
Why Local SEO Rankings Need City and Language Tracking
A single national ranking report can hide local differences.
For example:
Keyword: emergency plumber
City: Dallas
Language: English
may show a different result set than:
Keyword: emergency plumber
City: Houston
Language: English
The difference becomes larger when you add language:
Keyword: dentist near me
City: Miami
Language: English
Keyword: dentista cerca de mí
City: Miami
Language: Spanish
For serious local SEO tracking, city, language, country, and device should be stored as part of every ranking row.
Organic Results vs Local Pack Results
Local SEO reports often need more than one ranking type.
|
Result type |
What it tells you |
|
Organic results |
Which pages rank in standard search results |
|
Local pack |
Which businesses appear in map-style local results |
|
Maps results |
How businesses appear in Google Maps-style searches |
|
PAA results |
What local questions users ask |
|
Ads |
Which competitors are paying for visibility |
This article focuses on organic ranking extraction, but the same structure can be extended to local pack or Maps results when your SERP API response includes those modules.
Step 1: Set Environment Variables
Do not hardcode API credentials.
export TALORDATA_API_KEY="your_api_key_here"
export TALORDATA_SERP_ENDPOINT="your_talordata_serp_endpoint_here"
On Windows PowerShell:
$env:TALORDATA_API_KEY="your_api_key_here"
$env:TALORDATA_SERP_ENDPOINT="your_talordata_serp_endpoint_here"
Install the Python package:
pip install requests
Talordata’s query parameter documentation covers SERP API request parameters and search workflows.
Step 2: Define Keywords, Cities, and Languages
Start with a small test set.
KEYWORDS = [
"emergency plumber",
"dentist near me",
"coffee shop",
]
LOCATIONS = [
{
"city": "Austin",
"country": "us",
"location": "Austin, Texas, United States",
"language": "en",
},
{
"city": "Miami",
"country": "us",
"location": "Miami, Florida, United States",
"language": "en",
},
{
"city": "Miami",
"country": "us",
"location": "Miami, Florida, United States",
"language": "es",
},
]
TARGET_DOMAIN = "example.com"
DEVICE = "desktop"
This lets you compare the same keyword across different local markets and languages.
Step 3: Create a SERP API Request Helper
Keep the request layer separate from parsing logic.
import os
import requests
def require_env(name):
value = os.getenv(name)
if not value:
raise RuntimeError(f"Missing required environment variable: {name}")
return value
TALORDATA_API_KEY = require_env("TALORDATA_API_KEY")
TALORDATA_SERP_ENDPOINT = require_env("TALORDATA_SERP_ENDPOINT")
def call_serp_api(payload):
headers = {
"Authorization": f"Bearer {TALORDATA_API_KEY}",
"Content-Type": "application/json",
}
response = requests.post(
TALORDATA_SERP_ENDPOINT,
json=payload,
headers=headers,
timeout=30,
)
response.raise_for_status()
return response.json()
Some APIs use GET instead of POST, or pass the API key as a query parameter. If your setup is different, update only this helper.
Step 4: Fetch Local SERP Results
Now create a function that sends a localized search request.
def fetch_local_serp(keyword, location, country, language, device="desktop"):
payload = {
"engine": "google",
"q": keyword,
"location": location,
"gl": country,
"hl": language,
"device": device,
"num": 10,
"output": "json",
}
return call_serp_api(payload)
In local SEO, the most important fields are usually:
|
Parameter |
Why it matters |
|
|
The keyword being tracked |
|
|
City or area used for local targeting |
|
|
Country or market |
|
|
Search language |
|
|
Desktop or mobile result behavior |
|
|
Number of results returned |
Talordata’s Google query parameter guide discusses common SERP API parameters such as q, location, gl, hl, device, num, start, and search type.
Step 5: Extract Organic Results
SERP API responses may use different field names, so keep the parser 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 []
def normalize_organic_results(serp_json):
results = get_organic_results(serp_json)
rows = []
for index, item in enumerate(results, 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
This gives you a clean list of ranking results for each city and language.
Step 6: Find the Target Domain Ranking
Now check whether your target domain appears in the top results.
def find_domain_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"],
"matched_title": item["title"],
}
return {
"position": None,
"matched_url": "",
"matched_title": "",
}
def summarize_top_domains(results, limit=10):
domains = []
for item in results[:limit]:
if item["domain"]:
domains.append(item["domain"])
return " | ".join(domains)
If the target domain is not found, keep the position as None. That is better than forcing a fake rank.
Step 7: Export Local Ranking Data to CSV
import csv
from datetime import datetime, timezone
CSV_COLUMNS = [
"keyword",
"city",
"location",
"country",
"language",
"device",
"target_domain",
"position",
"matched_url",
"matched_title",
"top_domains",
"collected_at",
]
def export_to_csv(rows, filename="local_seo_rankings.csv"):
if not rows:
print("No ranking rows found.")
return
with open(filename, mode="w", newline="", encoding="utf-8") as file:
writer = csv.DictWriter(file, fieldnames=CSV_COLUMNS)
writer.writeheader()
writer.writerows(rows)
print(f"Exported {len(rows)} ranking rows to {filename}")
Complete Python Example
import os
import csv
import requests
from datetime import datetime, timezone
from urllib.parse import urlparse
KEYWORDS = [
"emergency plumber",
"dentist near me",
"coffee shop",
]
LOCATIONS = [
{
"city": "Austin",
"country": "us",
"location": "Austin, Texas, United States",
"language": "en",
},
{
"city": "Miami",
"country": "us",
"location": "Miami, Florida, United States",
"language": "en",
},
{
"city": "Miami",
"country": "us",
"location": "Miami, Florida, United States",
"language": "es",
},
]
TARGET_DOMAIN = "example.com"
DEVICE = "desktop"
CSV_COLUMNS = [
"keyword",
"city",
"location",
"country",
"language",
"device",
"target_domain",
"position",
"matched_url",
"matched_title",
"top_domains",
"collected_at",
]
def require_env(name):
value = os.getenv(name)
if not value:
raise RuntimeError(f"Missing required environment variable: {name}")
return value
TALORDATA_API_KEY = require_env("TALORDATA_API_KEY")
TALORDATA_SERP_ENDPOINT = require_env("TALORDATA_SERP_ENDPOINT")
def call_serp_api(payload):
headers = {
"Authorization": f"Bearer {TALORDATA_API_KEY}",
"Content-Type": "application/json",
}
response = requests.post(
TALORDATA_SERP_ENDPOINT,
json=payload,
headers=headers,
timeout=30,
)
response.raise_for_status()
return response.json()
def fetch_local_serp(keyword, location, country, language, device="desktop"):
payload = {
"engine": "google",
"q": keyword,
"location": location,
"gl": country,
"hl": language,
"device": device,
"num": 10,
"output": "json",
}
return call_serp_api(payload)
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 []
def normalize_organic_results(serp_json):
results = get_organic_results(serp_json)
rows = []
for index, item in enumerate(results, 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
def find_domain_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"],
"matched_title": item["title"],
}
return {
"position": None,
"matched_url": "",
"matched_title": "",
}
def summarize_top_domains(results, limit=10):
domains = []
for item in results[:limit]:
if item["domain"]:
domains.append(item["domain"])
return " | ".join(domains)
def export_to_csv(rows, filename="local_seo_rankings.csv"):
if not rows:
print("No ranking rows found.")
return
with open(filename, mode="w", newline="", encoding="utf-8") as file:
writer = csv.DictWriter(file, fieldnames=CSV_COLUMNS)
writer.writeheader()
writer.writerows(rows)
print(f"Exported {len(rows)} ranking rows to {filename}")
if __name__ == "__main__":
collected_at = datetime.now(timezone.utc).isoformat()
output_rows = []
for keyword in KEYWORDS:
for loc in LOCATIONS:
serp_json = fetch_local_serp(
keyword=keyword,
location=loc["location"],
country=loc["country"],
language=loc["language"],
device=DEVICE,
)
organic_results = normalize_organic_results(serp_json)
ranking = find_domain_ranking(organic_results, TARGET_DOMAIN)
output_rows.append({
"keyword": keyword,
"city": loc["city"],
"location": loc["location"],
"country": loc["country"],
"language": loc["language"],
"device": DEVICE,
"target_domain": TARGET_DOMAIN,
"position": ranking["position"],
"matched_url": ranking["matched_url"],
"matched_title": ranking["matched_title"],
"top_domains": summarize_top_domains(organic_results),
"collected_at": collected_at,
})
export_to_csv(output_rows)
After running the script, you should get:
local_seo_rankings.csv
Example Output
keyword,city,location,country,language,device,target_domain,position,matched_url,matched_title,top_domains,collected_at
emergency plumber,Austin,"Austin, Texas, United States",us,en,desktop,example.com,3,https://example.com/austin-plumber,Example Plumbing Austin,domain1.com | domain2.com | example.com,2026-06-22T00:00:00Z
dentist near me,Miami,"Miami, Florida, United States",us,es,desktop,example.com,,,,domain3.com | domain4.com | domain5.com,2026-06-22T00:00:00Z
How to Use the Output
For a local SEO report, you can group the CSV by:
|
Grouping |
What it shows |
|
Keyword |
Which terms are ranking or missing |
|
City |
Where visibility is strong or weak |
|
Language |
Whether multilingual pages are ranking |
|
Device |
Whether mobile and desktop differ |
|
Top domains |
Which competitors appear most often |
For example:
Keyword: emergency plumber
Austin: position 3
Dallas: position 8
Houston: not found
Miami Spanish: not found
This tells you where to improve local landing pages, language targeting, internal links, business profiles, or content depth.
Best Practices
Track the same keyword list consistently. Changing keywords too often makes trend analysis harder.
Store city, language, country, and device in every row. Without them, ranking data loses context.
Keep raw API responses during development. They help debug missing fields and SERP feature changes.
Do not mix organic, local pack, and ads without labels. Each result type should be tracked separately.
Add historical tracking later. A database is better than CSV once you need trends, alerts, and dashboards.
Use language-specific keywords. Do not only translate the interface language; users may search with different local phrasing.
Sign up and claim 1,000 API responses >>
FAQ
What is a SERP API for local SEO?
A SERP API for local SEO lets you collect search results programmatically by keyword, city, country, language, and device. This makes it easier to track local rankings at scale.
Why track rankings by city?
Search results can change by city. A business may rank well in one market but not appear in another, even for the same keyword.
Why track rankings by language?
Multilingual users may search differently. Tracking by language helps you see whether localized pages are visible for the right audience.
Can I track Google Maps rankings with this workflow?
Yes, but you should parse Maps or local pack results separately from organic results. This article focuses on organic rankings, but the same workflow can be extended to Maps-style data.
Should I use CSV or a database?
CSV works for testing and small reports. For recurring local SEO tracking, use a database so you can compare historical ranking changes by city, language, keyword, and device.