Tracking where your domain ranks for a list of keywords on Google sounds like a simple one-afternoon project. And it is—until you try to scrape google.com yourself.
After a few hundred requests from the same IP, you start getting the consent page, then a CAPTCHA, then nothing at all. Rotating proxies and headless browsers work for a while, but you end up spending more time keeping the scraper alive than actually using the data.
Easier path: hand that problem to a SERP API and keep your code focused on the rank itself.
In this tutorial, we'll build a complete Google rank tracker in Python using TalorData SERP API—from a single request to a full tracking system with SQLite persistence.
What We're Building
A Python script that:
- Takes a list of keywords and a target domain
- Queries Google via TalorData SERP API
- Finds the target domain's position in organic results
- Stores historical rankings in SQLite
- Retrieves ranking history for trend visualization
Why TalorData?
TalorData provides a multi-engine SERP API that returns structured search results from Google, Bing, Yandex, and DuckDuckGo through a single endpoint.
| Feature | Details |
|---|---|
| Free tier | 1,000 free API responses, no credit card required |
| Pricing | ~$0.25–$1.00 per 1,000 successful requests |
| Engines | Google, Bing, Yandex, DuckDuckGo |
| Output | Structured JSON (or raw HTML) |
| Geo-targeting | Location-specific results |
Cost comparison: SerpAPI charges ~$10 per 1,000 requests, while TalorData is ~$0.25–$1.00. For 50 keywords tracked daily, that's about $1.50/month vs $15/month.
Step 1: Get Your API Token
- Sign up at talordata.com
- Navigate to the dashboard and grab your Bearer Token (API token)
- Export it as an environment variable:
export TALORDATA_TOKEN="your_api_token_here"
Or hardcode it (not recommended for production):
TOKEN = "your_api_token_here"
Step 2: Send Your First Request
TalorData's SERP API accepts POST requests with form-encoded parameters. Here's the minimal curl example:
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=serp api" \
-d "device=desktop" \
-d "location=United States" \
-d "num=10" \
-d "json=1"
Key parameters:
| Parameter | Description |
|---|---|
engine |
Search engine: google, bing, yandex, duckduckgo
|
q |
Search query/keyword |
device |
desktop or mobile
|
location |
Geo-target (e.g., "United States") |
num |
Number of results to return (max 100) |
json |
1 or 2 for structured JSON output |
The response includes top-level keys like:
-
organic— organic search results (where the rank lives) -
sponsored_results— paid ads -
people_also_ask— related questions -
pagination— next page info -
search_information— metadata about the search
Step 3: Parse Rankings in Python
Here's a complete Python function that sends a query and extracts the target domain's position:
import os
import requests
ENDPOINT = "https://serpapi.talordata.net/serp/v1/request"
TOKEN = os.environ.get("TALORDATA_TOKEN")
def search(query: str, location: str = "United States", num: int = 10) -> dict:
"""Send a search request to TalorData SERP API and return the JSON response."""
resp = requests.post(
ENDPOINT,
headers={
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/x-www-form-urlencoded",
},
data={
"engine": "google",
"q": query,
"device": "desktop",
"location": location,
"num": num,
"json": 1,
},
timeout=60,
)
resp.raise_for_status()
return resp.json()
def find_rank(data: dict, target_domain: str) -> int | None:
"""
Find the rank (position) of a target domain in the organic search results.
Returns None if the domain is not found.
"""
for item in data.get("organic", []):
link = item.get("link", "")
# Extract domain from link for matching
if target_domain.lower() in link.lower():
return item.get("position")
return None
# Example usage
if __name__ == "__main__":
results = search("serp api")
# Print top 5 results
for r in results.get("organic", [])[:5]:
print(f'{r["position"]:>2} {r.get("display_link", "")}')
print(f' {r.get("title", "")}')
# Find where talordata.com ranks
rank = find_rank(results, "talordata.com")
print(f"\nTalorData rank for 'serp api': {rank if rank else 'Not found'}")
Expected output (actual results will vary):
1 https://serpapi.com
SerpApi: Google Search API
2 https://brightdata.com/products/serp-api
SERP API - SERP Scraper API - Free Trial
3 https://dataforseo.com/APIs
SERP API You Can Trust
4 https://serper.dev
Serper - The World's Fastest and Cheapest Google Search API
5 https://github.com/serpapi/google-search-results-python
serpapi/google-search-results-python
TalorData rank for 'serp api': Not found
Each entry in organic contains:
-
position— the rank (1 = first result) -
title— page title -
link— full URL -
display_link— breadcrumb URL shown under the title -
description— snippet text
Note: For domain matching, use the host of
linkrather thandisplay_link, asdisplay_linkis Google's rendered version and may differ.
Step 4: Build the Full Rank Tracker
Now let's build a complete tracker that:
- Tracks multiple keywords
- Stores historical data in SQLite
- Retrieves history for trend analysis
Complete Code
import os
import sqlite3
from datetime import datetime
from typing import Optional
import requests
ENDPOINT = "https://serpapi.talordata.net/serp/v1/request"
class RankTracker:
def __init__(self, api_token: str, db_path: str = "rank_tracker.db"):
self.api_token = api_token
self.conn = sqlite3.connect(db_path)
self._init_db()
def _init_db(self) -> None:
"""Initialize the SQLite database table."""
cursor = self.conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS rankings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
keyword TEXT NOT NULL,
domain TEXT NOT NULL,
rank INTEGER,
checked_at TIMESTAMP NOT NULL
)
""")
self.conn.commit()
def check_rank(self, keyword: str, target_domain: str, location: str = "United States") -> Optional[int]:
"""
Query Google for a keyword and return the rank of the target domain.
Returns None if the domain is not found in the top results.
"""
headers = {
"Authorization": f"Bearer {self.api_token}",
"Content-Type": "application/x-www-form-urlencoded",
}
data = {
"engine": "google",
"q": keyword,
"device": "desktop",
"location": location,
"num": 100, # Get top 100 results
"json": 1,
}
resp = requests.post(ENDPOINT, headers=headers, data=data, timeout=60)
resp.raise_for_status()
results = resp.json()
for item in results.get("organic", []):
link = item.get("link", "")
if target_domain.lower() in link.lower():
return item.get("position")
return None
def save_rank(self, keyword: str, domain: str, rank: Optional[int]) -> None:
"""Save a ranking record to the database."""
cursor = self.conn.cursor()
cursor.execute(
"INSERT INTO rankings (keyword, domain, rank, checked_at) VALUES (?, ?, ?, ?)",
(keyword, domain, rank, datetime.utcnow()),
)
self.conn.commit()
def track_keywords(self, keywords: list[str], domain: str, location: str = "United States") -> list[dict]:
"""
Track multiple keywords for a single domain.
Returns a list of results with keyword, rank, and timestamp.
"""
results = []
for keyword in keywords:
rank = self.check_rank(keyword, domain, location)
self.save_rank(keyword, domain, rank)
results.append({
"keyword": keyword,
"rank": rank,
"checked_at": datetime.utcnow().isoformat(),
})
print(f"{keyword}: {rank if rank else 'Not found'}")
return results
def get_history(self, keyword: str, domain: str, limit: int = 30) -> list[tuple]:
"""
Get historical rankings for a specific keyword and domain.
Returns a list of (rank, checked_at) tuples, most recent first.
"""
cursor = self.conn.cursor()
cursor.execute(
"SELECT rank, checked_at FROM rankings "
"WHERE keyword = ? AND domain = ? "
"ORDER BY checked_at DESC LIMIT ?",
(keyword, domain, limit),
)
return cursor.fetchall()
def get_latest_rank(self, keyword: str, domain: str) -> Optional[int]:
"""Get the most recent rank for a keyword and domain."""
cursor = self.conn.cursor()
cursor.execute(
"SELECT rank FROM rankings "
"WHERE keyword = ? AND domain = ? "
"ORDER BY checked_at DESC LIMIT 1",
(keyword, domain),
)
row = cursor.fetchone()
return row[0] if row else None
def close(self) -> None:
"""Close the database connection."""
self.conn.close()
# Example usage
if __name__ == "__main__":
API_TOKEN = os.environ.get("TALORDATA_TOKEN")
if not API_TOKEN:
raise ValueError("Please set TALORDATA_TOKEN environment variable")
tracker = RankTracker(API_TOKEN)
# Keywords to track
keywords = [
"serp api",
"google search api",
"best serp api 2026",
"seo rank tracker",
"search engine results api",
]
# Your domain
domain = "talordata.com"
# Run the tracker
print("Tracking keywords...")
tracker.track_keywords(keywords, domain)
# Check history for one keyword
print("\nHistory for 'serp api':")
history = tracker.get_history("serp api", domain, limit=10)
for rank, checked_at in history:
print(f" {checked_at}: rank {rank if rank else 'Not found'}")
tracker.close()
What This Code Does
check_rank()— Sends a request to TalorData, iterates throughorganicresults, and returns the position where the target domain appears.save_rank()— Stores each check with a timestamp in SQLite.track_keywords()— Loops through a list of keywords, checks each one, saves the results, and prints progress.get_history()— Retrieves historical rankings for a specific keyword/domain pair, ordered by date (most recent first).get_latest_rank()— Quick lookup for the most recent rank without fetching all history.
Step 5: Visualize the Data (Optional)
With historical data in SQLite, you can easily visualize ranking trends. Here's a quick example using matplotlib:
import matplotlib.pyplot as plt
from datetime import datetime
def plot_rank_history(tracker, keyword: str, domain: str):
history = tracker.get_history(keyword, domain, limit=30)
if not history:
print("No history found.")
return
# Reverse to plot chronological (oldest to newest)
history.reverse()
dates = [datetime.fromisoformat(str(ts).replace(" ", "T")) for _, ts in history]
ranks = [r if r is not None else 101 for r, _ in history] # 101 = not found
plt.figure(figsize=(10, 5))
plt.plot(dates, ranks, marker="o")
plt.gca().invert_yaxis() # Lower rank is better
plt.xlabel("Date")
plt.ylabel("Rank")
plt.title(f"Ranking History: '{keyword}'")
plt.grid(True, alpha=0.3)
plt.show()
# Usage
# plot_rank_history(tracker, "serp api", "talordata.com")
Step 6: Automate with a Scheduler
To run this automatically (e.g., daily), you can use:
Option 1: Cron job (Linux/macOS)
# Run daily at 9 AM
0 9 * * * cd /path/to/project && python rank_tracker.py
Option 2: GitHub Actions
Create .github/workflows/rank-track.yml:
name: Track Rankings
on:
schedule:
- cron: '0 9 * * *' # Daily at 9 AM UTC
jobs:
track:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: pip install requests
- run: python rank_tracker.py
env:
TALORDATA_TOKEN: ${{ secrets.TALORDATA_TOKEN }}
Cost Analysis
Let's calculate the monthly cost for tracking 50 keywords daily:
| Metric | Value |
|---|---|
| Keywords | 50 |
| Checks per day | 50 |
| Checks per month | 50 × 30 = 1,500 |
| TalorData price | ~$0.25–$1.00 per 1,000 requests |
| Monthly cost | ~$1.35–$1.50 |
Compare this to building and maintaining your own scraper:
| Approach | Monthly Cost | Maintenance Time |
|---|---|---|
| Self-built scraper | $20–50+ (proxies) | Several hours/week |
| TalorData SERP API | ~$1.50 | 0 |
Supported Search Engines
TalorData supports multiple search engines through the same endpoint:
| Engine |
engine parameter |
|---|---|
google |
|
| Bing | bing |
| Yandex | yandex |
| DuckDuckGo | duckduckgo |
To switch engines, simply change the engine parameter in your request:
data = {
"engine": "bing", # or "yandex", "duckduckgo"
"q": keyword,
# ... other parameters
}
Common Pitfalls and Tips
Domain matching: Use the host of
linkrather thandisplay_linkfor reliable matching.Rate limiting: TalorData charges per successful request—failed requests are not billed. Still, cache results for stable keywords to save costs.
Location matters: Rankings vary significantly by geographic location. Always specify
locationfor consistent results.Free tier: You get 1,000 free requests after signing up—more than enough to test and build your MVP.
Next Steps
Once your rank tracker is running, consider these enhancements:
- Multi-domain tracking — Monitor competitors alongside your own domain
- Alert system — Send email/Slack notifications when rankings change significantly
- Dashboard — Build a web UI with Streamlit or Flask to visualize trends
- Multi-engine tracking — Check rankings on Bing and DuckDuckGo too
- SERP feature extraction — Track "People Also Ask" questions and featured snippets for content ideas
Summary
In ~100 lines of Python, we built a complete Google rank tracker that:
- Fetches search results via TalorData SERP API (no scraping, no CAPTCHAs)
- Extracts target domain positions from organic results
- Persists historical data to SQLite
- Retrieves ranking history for analysis
Why this works:
- TalorData handles the hard parts (proxies, parsing, rate limits)
- You focus on the data and what it means
- Cost is minimal (~$1.50/month for 50 keywords daily)
- 1,000 free requests to get started
Get started today: Sign up at talordata.com, grab your free 1,000 requests, and start tracking your rankings in minutes.
Have questions? Drop them in the comments below. Happy tracking!
Top comments (0)