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.

talor ai
最后更新于
10 分钟阅读

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

keyword

Search query

city

Target city

country

Target market

language

Search language

device

Desktop or mobile

position

Ranking position of the target domain

matched_url

Ranking URL

matched_title

Ranking page title

top_domains

Domains in the top results

collected_at

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

q

The keyword being tracked

location

City or area used for local targeting

gl

Country or market

hl

Search language

device

Desktop or mobile result behavior

num

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.

立即开展您的数据业务

加入全球最强大的代理网络

免费试用