面向本地 SEO 的 SERP API:按城市和語言追蹤排名
了解如何使用 SERP API 追蹤本地 SEO 排名。利用 Python 按城市、國家、語言和裝置收集搜尋結果,並將排名資料匯出為 CSV 格式。
快速回答: SERP API 可以幫助本地 SEO 團隊按城市、國家、語言和裝置追蹤排名。你不需要手動切換地區搜尋,而是可以發送結構化請求,收集排名位置,整理結果,最後匯出 CSV 用於報告或資料看板。
本地 SEO 不只是看某個頁面有沒有排名。
更重要的是:它在哪裡有排名。
同一個關鍵字,在 New York、Austin、Toronto、London 或 Singapore 可能會返回不同搜尋結果。結果也可能因語言和裝置不同而變化。這對代理商、本地商家、平台型網站、連鎖品牌,以及需要城市級可見度的團隊都很重要。
SERP API 的價值在於,你可以用地區、語言、國家、裝置和分頁參數收集結構化搜尋結果。
我們要建立什麼?
我們會建立一個 Python 流程:
關鍵字列表
→ 城市和語言列表
→ SERP API 請求
→ 提取自然搜尋結果
→ 找出目標網域排名
→ 匯出本地排名資料到 CSV
最終 CSV 可以包含:
|
欄位 |
含義 |
|
|
搜尋關鍵字 |
|
|
目標城市 |
|
|
目標市場 |
|
|
搜尋語言 |
|
|
桌面或行動裝置 |
|
|
目標網域排名位置 |
|
|
命中的排名 URL |
|
|
命中的頁面標題 |
|
|
搜尋結果前幾名網域 |
|
|
採集時間 |
這套流程適合本地 SEO 報告、城市級排名檢查、多語 SEO 追蹤、連鎖品牌 SEO、競品監控和 AI 輔助 SEO 看板。
為什麼本地 SEO 需要按城市和語言追蹤?
單一全國排名報告可能會掩蓋本地差異。
例如:
Keyword: emergency plumber
City: Dallas
Language: English
和下面這組條件可能返回不同結果:
Keyword: emergency plumber
City: Houston
Language: English
加入語言後,差異可能更明顯:
Keyword: dentist near me
City: Miami
Language: English
Keyword: dentista cerca de mí
City: Miami
Language: Spanish
對嚴肅的本地 SEO 追蹤來說,城市、語言、國家和裝置都應該被保存到每一條排名資料中。
自然搜尋結果 vs 本地結果包
本地 SEO 報告通常不只需要一種排名類型。
|
結果類型 |
可以說明什麼 |
|
自然搜尋結果 |
哪些頁面出現在標準搜尋結果中 |
|
本地結果包 |
哪些商家出現在地圖型本地結果中 |
|
Maps 結果 |
商家在 Google Maps 類搜尋中的呈現方式 |
|
PAA 問題 |
本地使用者常問哪些問題 |
|
廣告 |
哪些競品正在付費獲取可見度 |
本文重點是提取自然搜尋排名,但如果 SERP API 回應中包含本地結果包或 Maps 模組,也可以沿用同樣結構進行擴展。
Step 1:設定環境變數
不要把 API 金鑰直接寫進程式碼。
export TALORDATA_API_KEY="your_api_key_here"
export TALORDATA_SERP_ENDPOINT="your_talordata_serp_endpoint_here"
Windows PowerShell:
$env:TALORDATA_API_KEY="your_api_key_here"
$env:TALORDATA_SERP_ENDPOINT="your_talordata_serp_endpoint_here"
安裝 Python 套件:
pip install requests
Talordata 查詢參數文件提供 SERP API 請求參數和搜尋流程說明。
Step 2:定義關鍵字、城市和語言
先從一組小型測試資料開始。
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"
這樣可以比較同一組關鍵字在不同本地市場和語言下的表現。
Step 3:建立 SERP API 請求輔助函式
把請求層和解析邏輯分開。
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()
有些 API 使用 GET,也有些會把 API key 放在查詢參數中。如果你的設定不同,只需要修改這個輔助函式。
Step 4:取得本地 SERP 結果
接著建立一個發送本地化搜尋請求的函式。
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)
本地 SEO 中,最重要的參數通常是:
|
參數 |
為什麼重要 |
|
|
被追蹤的關鍵字 |
|
|
用於本地定位的城市或區域 |
|
|
國家或市場 |
|
|
搜尋語言 |
|
|
桌面或行動裝置結果差異 |
|
|
返回結果數量 |
Talordata 的 Google 查詢參數指南提到常見 SERP API 參數,包括 q、location、gl、hl、device、num、start 和搜尋類型。
Step 5:提取自然搜尋結果
不同 SERP API 可能使用不同欄位名稱,所以解析器要保持彈性。
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
這會為每個城市和語言生成一份乾淨的排名結果列表。
Step 6:找出目標網域排名
接著檢查目標網域是否出現在前幾名搜尋結果中。
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)
如果沒有找到目標網域,就把排名保留為 None,不要填入假排名。
Step 7:匯出本地排名資料到 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}")
完整 Python 範例
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)
執行後會得到:
local_seo_rankings.csv
輸出範例
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
如何使用輸出結果?
本地 SEO 報告可以按以下方式分組:
|
分組方式 |
可以看出什麼 |
|
關鍵字 |
哪些詞有排名,哪些詞沒有排名 |
|
城市 |
哪些地區可見度強或弱 |
|
語言 |
多語頁面是否有排名 |
|
裝置 |
桌面和行動裝置是否存在差異 |
|
前排網域 |
哪些競品最常出現 |
例如:
Keyword: emergency plumber
Austin: position 3
Dallas: position 8
Houston: not found
Miami Spanish: not found
這可以幫你判斷哪些城市需要優化本地落地頁、語言版本、內部連結、商家資料或內容深度。
最佳實踐
固定追蹤同一組關鍵字。關鍵字頻繁變化會讓趨勢分析變得困難。
每一行都保存城市、語言、國家和裝置,否則排名資料會失去上下文。
開發階段保存原始 API 回應,方便排查欄位缺失和搜尋結果模組變化。
不要在未標記的情況下混合自然搜尋、本地結果包和廣告。每種結果類型都應分開追蹤。
後續可以增加歷史追蹤。當你需要趨勢、提醒和看板時,資料庫會比 CSV 更合適。
使用語言對應的本地關鍵字。不要只翻譯介面語言,使用者實際搜尋方式可能完全不同。
FAQ
什麼是用於本地 SEO 的 SERP API?
它是一種可以按關鍵字、城市、國家、語言和裝置程式化收集搜尋結果的 API,適合用來批量追蹤本地排名。
為什麼要按城市追蹤排名?
搜尋結果可能因城市而變化。同一個關鍵字在某個城市排名很好,在另一個城市可能完全不出現。
為什麼要按語言追蹤排名?
多語使用者的搜尋方式不同。按語言追蹤可以確認本地化頁面是否觸達正確受眾。
可以用這個流程追蹤 Google Maps 排名嗎?
可以,但 Maps 或本地結果包應和自然搜尋結果分開解析。本文聚焦自然搜尋排名,但流程可以擴展到 Maps 類資料。
應該使用 CSV 還是資料庫?
CSV 適合測試和小型報告。如果要定期追蹤本地 SEO 排名,建議使用資料庫,方便按城市、語言、關鍵字和裝置比較歷史變化。