Santaji GadeDevelopment, PythonYesterday7 Views
Build a simple rank tracker in Python that checks keyword positions through a compliant SERP API, stores history, and charts movement, no manual spreadsheet checking required.
Table of Contents
ToggleTyping forty keywords into an incognito window every Monday is not rank tracking, it is a chore that produces unreliable numbers. A simple rank tracker in python runs the same check the same way, every time, and remembers what it found.
A client once tracked two hundred keywords by hand in a spreadsheet. Someone spent most of a Monday morning searching each one and writing down where the site landed.
The numbers were never quite trustworthy. Personalization, location, and small mistakes in a long manual process all crept in somewhere along the way.
A simple rank tracker in python fixes both problems at once. It checks every keyword the same way every time, and it never gets tired on keyword one hundred and eighty.
This guide builds one using a proper SERP API rather than scraping Google directly, stores the results with history attached, and turns raw positions into a chart that actually shows movement.
None of it needs a paid rank tracking platform. A SERP API key, a Python environment, and a place to store a growing CSV cover the whole build.
Manual rank checking has a quality problem before it even has a scale problem. Google personalizes results based on location, device, and search history, so the same query can return a different order for two different people.
Incognito mode helps a little. It does not remove location signals or device differences, which means two people checking the same keyword in incognito can still see two different rankings.
A script run with fixed parameters does not have that inconsistency. The same location, the same device setting, and the same query structure every single time make the resulting numbers actually comparable week to week.
Comparing keyword rank against a broader keyword research process becomes far more useful once the tracking data behind it is this consistent.
How a rank tracker actually gets its data matters more than most guides admit. Scraping Google's results pages directly sits outside Google's terms of service, and it tends to break the moment a CAPTCHA shows up.
A dedicated SERP API is the sustainable choice. These services run the search on Google's behalf through infrastructure built for exactly this, and return clean, structured results instead of raw HTML to parse and hope holds together.
| Method | Compliance | Reliability | Cost |
|---|---|---|---|
| Direct scraping of Google results | Outside Google's terms | Breaks often, blocked frequently | Free until it stops working |
| Dedicated SERP API | Compliant, run through licensed infrastructure | Consistent, structured results | Usage based, this guide's approach |
| Search Console average position | Fully compliant, official Google data | Aggregated, not a per search snapshot | Free with a verified property |
This guide builds around a SERP API, using SerpApi as the working example. The same pattern applies to comparable providers, since most return organic results in a similar structured shape.
# create an isolated environment so package versions don't clash
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install google-search-results python-dotenv pandas
The google-search-results package is SerpApi's official client, despite the slightly generic sounding name. python-dotenv keeps the API key out of the code, and pandas holds every result once it comes back.
A single search returns a list of organic results, each with its own position. Finding your own domain in that list is the entire job.
import os
from dotenv import load_dotenv
from serpapi import GoogleSearch
load_dotenv()
API_KEY = os.getenv("SERPAPI_KEY")
def check_rank(keyword: str, target_domain: str, location: str = "United States") -> dict:
params = {
"engine": "google",
"q": keyword,
"location": location,
"api_key": API_KEY,
}
search = GoogleSearch(params)
results = search.get_dict()
organic = results.get("organic_results", [])
for result in organic:
if target_domain in result.get("link", ""):
return {"keyword": keyword, "position": result.get("position"), "url": result.get("link")}
return {"keyword": keyword, "position": None, "url": None}
A None position is not an error, it is real information. The domain genuinely did not appear in the organic results the API returned for that search.
A real tracker checks a whole list, and often needs to check it against more than one location if the business serves more than one market.
import time
import pandas as pd
from rank_checker import check_rank
KEYWORDS = ["python for seo", "rank tracker api", "seo automation tools"]
LOCATIONS = ["United States", "United Kingdom"]
TARGET_DOMAIN = "brandella.in"
def run_batch() -> pd.DataFrame:
rows = []
for location in LOCATIONS:
for keyword in KEYWORDS:
result = check_rank(keyword, TARGET_DOMAIN, location)
result["location"] = location
rows.append(result)
time.sleep(1) # stay well under API rate limits
return pd.DataFrame(rows)
The one second pause between calls, using Python's own time.sleep(), is not strictly required by every provider, but it keeps a large batch well behaved and avoids tripping a rate limit partway through a run.
A single day of positions is a snapshot. History is what actually makes a simple rank tracker in python worth running, since movement only means something when you can see it against what came before.
import os
from datetime import date
import pandas as pd
HISTORY_PATH = "data/rank_history.csv"
def append_history(df: pd.DataFrame):
df["checked_on"] = date.today().isoformat()
if os.path.exists(HISTORY_PATH):
existing = pd.read_csv(HISTORY_PATH)
combined = pd.concat([existing, df]).drop_duplicates(
subset=["keyword", "location", "checked_on"]
)
else:
combined = df
combined.to_csv(HISTORY_PATH, index=False)
Running this daily or weekly through GitHub Actions, the same scheduling pattern used across the rest of this series, is what turns a one time check into an actual tracker.
A table of numbers is accurate but hard to read at a glance. A simple line chart, built with matplotlib, makes movement obvious in a way a spreadsheet full of positions never quite does.
import pandas as pd
import matplotlib.pyplot as plt
def plot_keyword_history(history_path: str, keyword: str):
df = pd.read_csv(history_path)
subset = df[df["keyword"] == keyword].sort_values("checked_on")
plt.plot(subset["checked_on"], subset["position"], marker="o")
plt.gca().invert_yaxis() # position 1 should sit at the top
plt.title(f"Ranking history: {keyword}")
plt.xlabel("Date checked")
plt.ylabel("Position")
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig(f"charts/{keyword.replace(' ', '_')}.png")
Inverting the axis is a small detail that matters a lot. Without it, a page climbing from position ten to position one draws a line heading downward, which reads backward to anyone glancing at the chart.
A working script still needs a few judgment calls built in, or the numbers it produces will mislead more than they inform. Search Engine Journal's guide to rank tracking covers several of these same pitfalls from a broader, tool agnostic angle worth reading alongside a simple rank tracker in python.
Estimate the monthly call volume for a tracking schedule, based on keywords, locations, and check frequency.
SerpApi, the company whose API this guide builds around, published an official walkthrough of the same search endpoint and response structure used in the code above.
Video credit: SerpApi, LLC.
Seeing the raw response structure in the video makes it clear why parsing organic_results in Python is as simple as the code in this guide shows it to be.
Directly scraping Google's results pages falls outside Google's terms of service and tends to get blocked quickly. A licensed SERP API runs the same search through infrastructure built for exactly this, which is the compliant and durable approach.
Just as accurate for the core number, since both pull from the same kind of SERP API data underneath. Paid platforms mostly add convenience features like dashboards and alerts on top of the same raw check.
Weekly is a reasonable default for most sites. Daily checks make sense for a small set of high value keywords, but running the full list daily usually costs more than the extra detail is worth.
Yes. Most SERP APIs accept a location parameter, and looping the same keyword across several locations is exactly what the batch checking example in this guide does.
It means the target domain did not appear anywhere in the organic results the API returned for that search, typically the first page or two. It is a real result worth recording, not something to filter out.
Personalization and location make hand checked rankings hard to compare over time.
A compliant SERP API beats direct scraping on reliability, not just on legality.
Finding a domain inside organic results is a short loop, not complex logic.
A single check is a snapshot. Stored history is what makes movement visible.
An inverted line chart makes rank movement obvious at a glance.
More keywords, more locations, and more frequency all add up in API calls.
Pair a simple rank tracker in python with a wider SEO toolkit to see the full picture behind every ranking change.









