If you need a rank tracker that you can adapt to your own workflow, the core loop is small: submit a keyword, inspect the returned SERP items, find your domain, and store the position with a timestamp. Python handles the scheduling and storage, while a SERP API removes the need to maintain Google result selectors.
This guide builds a minimum viable tracker with Python and SearchCans. It is a good fit for teams that want transparent data and control over the output. It is not a replacement for a mature SEO platform’s historical database, reporting UI, or location model.
The cost depends on the number of keywords, search days, target engine, and plan. SearchCans uses credits, and a successful standard SERP call costs 1 credit. Use the current pricing page when estimating a production workload instead of treating an old competitor table as a quote.
Before You Build
Decide what a “rank” means for your project before writing the first request:
- Target engine: This example queries Google. The same data model can support another engine when the API payload and result fields are adjusted.
- Location and device: Rankings can vary by market, language, device, and personalization. Store these dimensions with each observation so that two checks are comparable.
- Result depth: Decide whether you need the first page or a deeper result set. A tracker that only checks the first page should record “not found” separately from a request error.
- Schedule: Daily checks are simple, but they are not always necessary for every keyword. Match the schedule to the volatility and decision cycle of the project.
- Storage: A CSV is enough for a proof of concept. A database becomes useful when you need history, comparisons, retries, and multiple domains.
The code below keeps the first version deliberately small. Add retries, structured logs, and alerting only after the basic result parsing is correct.
The Economics of Rank Tracking
Most SaaS rank trackers are essentially wrappers around a SERP API.
- They take your keyword.
- They send it to Google via an API/proxy.
- They parse the position of your URL.
- They charge you a markup for the UI.
If you use the current Ultimate reference price of $0.56 per 1,000 credits and one successful standard SERP call per keyword, tracking 500 keywords daily would consume:
500 × 30 days = 15,000 requests
15 × $0.56 = $8.40 at that reference rate
Treat this as a calculation example, not a promise about another provider’s current price. For the current SearchCans plan details, see our SERP API pricing comparison.
Build It Yourself (Python Code)
Here is a script that checks a list of keywords and saves your ranking position to a CSV file.
Basic Rank Checker
import requests
import csv
import datetime
API_KEY = "YOUR_SEARCHCANS_KEY"
MY_DOMAIN = "searchcans.com"
KEYWORDS = ["serp api pricing", "google search api", "best rank tracker"]
def check_rank(keyword):
url = "https://www.searchcans.com/api/v1/search"
payload = {"s": keyword, "t": "google", "d": 100} # Get top 100 results
headers = {"Authorization": f"Bearer {API_KEY}"}
try:
data = requests.post(url, json=payload, headers=headers).json()
if data['code'] == 0:
for index, item in enumerate(data['data']):
if MY_DOMAIN in item['url']:
return index + 1 # Rank found!
return 0 # Not in top 100
except:
return -1 # Error
# Run the tracker
results = []
today = datetime.date.today()
print(f"📉 Checking ranks for {today}...")
for kw in KEYWORDS:
rank = check_rank(kw)
results.append([today, kw, rank])
print(f"Keyword: {kw} | Rank: {rank}")
# Save to CSV
with open('rankings.csv', 'a', newline='') as f:
writer = csv.writer(f)
writer.writerows(results)
Advanced: Multi-Domain Tracking
Track your site and competitors:
def check_multiple_domains(keyword, domains):
url = "https://www.searchcans.com/api/v1/search"
payload = {"s": keyword, "t": "google", "d": 100}
headers = {"Authorization": f"Bearer {API_KEY}"}
response = requests.post(url, json=payload, headers=headers)
data = response.json()
rankings = {domain: 0 for domain in domains}
if data['code'] == 0:
for index, item in enumerate(data['data']):
for domain in domains:
if domain in item['url'] and rankings[domain] == 0:
rankings[domain] = index + 1
return rankings
# Usage
competitors = ["searchcans.com", "serpapi.com", "zenserp.com"]
results = check_multiple_domains("serp api", competitors)
print(results)
# Output: {'searchcans.com': 5, 'serpapi.com': 3, 'zenserp.com': 12}
Building a Complete Tracker
1. Database Storage
Store historical data for trend analysis:
import sqlite3
from datetime import datetime
def init_database():
conn = sqlite3.connect('ranks.db')
c = conn.cursor()
c.execute('''
CREATE TABLE IF NOT EXISTS rankings (
id INTEGER PRIMARY KEY,
date TEXT,
keyword TEXT,
domain TEXT,
rank INTEGER,
url TEXT,
title TEXT
)
''')
conn.commit()
conn.close()
def save_ranking(keyword, domain, rank, url, title):
conn = sqlite3.connect('ranks.db')
c = conn.cursor()
c.execute('''
INSERT INTO rankings (date, keyword, domain, rank, url, title)
VALUES (?, ?, ?, ?, ?, ?)
''', (datetime.now().date(), keyword, domain, rank, url, title))
conn.commit()
conn.close()
def get_rank_history(keyword, domain, days=30):
conn = sqlite3.connect('ranks.db')
c = conn.cursor()
c.execute('''
SELECT date, rank FROM rankings
WHERE keyword=? AND domain=?
ORDER BY date DESC
LIMIT ?
''', (keyword, domain, days))
results = c.fetchall()
conn.close()
return results
2. Trend Analysis
def analyze_trends(keyword, domain):
history = get_rank_history(keyword, domain, 30)
if len(history) < 2:
return "Insufficient data"
current_rank = history[0][1]
previous_rank = history[1][1]
if current_rank < previous_rank:
change = previous_rank - current_rank
return f"⬆️ Improved by {change} positions"
elif current_rank > previous_rank:
change = current_rank - previous_rank
return f"⬇️ Dropped by {change} positions"
else:
return "➡️ No change"
# Calculate average rank over time
def get_average_rank(keyword, domain, days=30):
history = get_rank_history(keyword, domain, days)
ranks = [r[1] for r in history if r[1] > 0]
return sum(ranks) / len(ranks) if ranks else 0
3. Alerting System
import smtplib
from email.mime.text import MIMEText
def send_alert(keyword, old_rank, new_rank):
if new_rank == 0:
subject = f"🚨 ALERT: {keyword} dropped out of top 100!"
elif new_rank > old_rank + 5:
subject = f"⚠️ WARNING: {keyword} dropped {new_rank - old_rank} positions"
else:
return # No alert needed
msg = MIMEText(f"Keyword: {keyword}\nOld Rank: {old_rank}\nNew Rank: {new_rank}")
msg['Subject'] = subject
msg['From'] = 'alerts@yoursite.com'
msg['To'] = 'seo@yoursite.com'
with smtplib.SMTP('smtp.gmail.com', 587) as server:
server.starttls()
server.login('your_email', 'your_password')
server.send_message(msg)
def track_with_alerts(keywords, domain):
for keyword in keywords:
old_rank = get_last_rank(keyword, domain)
new_rank = check_rank(keyword, domain)
if old_rank and new_rank:
if abs(new_rank - old_rank) > 5:
send_alert(keyword, old_rank, new_rank)
save_ranking(keyword, domain, new_rank)
Scheduling Automated Checks
Using cron (Linux/Mac):
# Check ranks daily at 9 AM
0 9 * * * /usr/bin/python3 /path/to/rank_tracker.py
Using Task Scheduler (Windows):
Create a batch file run_tracker.bat:
python C:\path\to\rank_tracker.py
Schedule it to run daily.
Using Python Schedule:
import schedule
import time
def job():
print("Running rank check...")
track_keywords(KEYWORDS, MY_DOMAIN)
# Schedule daily at 9 AM
schedule.every().day.at("09:00").do(job)
while True:
schedule.run_pending()
time.sleep(60)
Visualization Dashboard
Create a simple Flask web dashboard:
from flask import Flask, render_template
import sqlite3
app = Flask(__name__)
@app.route('/')
def dashboard():
conn = sqlite3.connect('ranks.db')
c = conn.cursor()
# Get latest rankings
c.execute('''
SELECT keyword, rank, date
FROM rankings
WHERE date = (SELECT MAX(date) FROM rankings)
ORDER BY rank
''')
current_ranks = c.fetchall()
conn.close()
return render_template('dashboard.html', ranks=current_ranks)
@app.route('/keyword/<keyword>')
def keyword_detail(keyword):
history = get_rank_history(keyword, MY_DOMAIN, 90)
return render_template('keyword.html', keyword=keyword, history=history)
if __name__ == '__main__':
app.run(debug=True)
Enhancing Your Tracker
1. Location-Specific Tracking
def check_rank_by_location(keyword, location):
payload = {
"s": keyword,
"t": "google",
"d": 100,
"location": location # e.g., "New York, US"
}
# ... rest of the code
2. Mobile vs Desktop Rankings
def check_mobile_rank(keyword):
payload = {
"s": keyword,
"t": "google",
"d": 100,
"device": "mobile"
}
# ... rest of the code
3. SERP Features Tracking
def track_serp_features(keyword):
data = get_serp_data(keyword)
features = {
'featured_snippet': False,
'people_also_ask': False,
'local_pack': False,
'knowledge_panel': False
}
for result in data:
if result.get('type') == 'featured_snippet':
features['featured_snippet'] = True
# ... check other features
return features
For more on building SEO tools, see our comprehensive guide.
Cost Comparison
| Solution | Monthly Cost (500 keywords) | Features |
|---|---|---|
| Ahrefs | $99+ | Full suite, UI, reports |
| SEMrush | $119+ | Full suite, UI, reports |
| ProRankTracker | $49+ | Rank tracking only |
| DIY with SearchCans | $8.40 | Custom code, full control |
Automated Reporting
Generate weekly reports:
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
def generate_weekly_report(keywords, domain):
report = []
for keyword in keywords:
history = get_rank_history(keyword, domain, 7)
if history:
current = history[0][1]
week_ago = history[-1][1] if len(history) >= 7 else current
change = week_ago - current
report.append({
'keyword': keyword,
'current_rank': current,
'change': change,
'trend': '⬆️' if change > 0 else ('⬇️' if change < 0 else '➡️')
})
return report
# Visualize with matplotlib
def plot_rank_history(keyword, domain):
history = get_rank_history(keyword, domain, 30)
dates = [h[0] for h in history]
ranks = [h[1] for h in history]
plt.figure(figsize=(10, 6))
plt.plot(dates, ranks, marker='o')
plt.title(f'Rank History: {keyword}')
plt.xlabel('Date')
plt.ylabel('Rank')
plt.gca().invert_yaxis() # Lower rank is better
plt.grid(True)
plt.savefig(f'{keyword}_history.png')
For no-code solutions, check out our Google Sheets automation guide.
Conclusion
SaaS tools are great for convenience, but for raw data efficiency, nothing beats a custom script connected to a wholesale API.
Start building your SEO toolkit today. For more advanced features, explore our documentation or check out our pricing for transparent rates.