🎯 A customizable, anti-detection cloud browser powered by self-developed Chromium designed for web crawlers and AI Agents.πŸ‘‰Try Now
Back to Blog

Build a Rank Tracker With Scrapeless Deep SerpApi

Emily Chen
Emily Chen

Advanced Data Extraction Specialist

29-Jun-2026

TL;DR:

  • A rank tracker is a loop, not a spreadsheet. Query Google for each keyword, read the organic positions, find where your domain lands, and write the number down on a schedule β€” that is the whole machine.
  • The hard part is getting a clean, parseable SERP. Google personalizes and rate-limits results, so a rank tracker needs a search source that returns structured organic_results with a stable position and link on every call.
  • Scrapeless Deep SerpApi returns Google results as JSON. One POST with {"q": "<keyword>"} against the scraper.google.search actor returns an organic_results array β€” each entry carries a position and a link, which is all a rank tracker needs to match your domain.
  • Domain matching is a host comparison, not a string search. Parse the link of each result to its host, strip www., and compare against your target domain β€” so blog.yourdomain.com and yourdomain.com/path both count as a hit.
  • Snapshots over time turn positions into deltas. Store each run keyed by date, then diff today against the last stored run to see which keywords moved up, dropped, or fell out of the results entirely.
  • Free to start. New Scrapeless accounts include free Deep SerpApi credits β€” sign up at app.scrapeless.com and run the tracker in this guide against your own keyword set.

Introduction: build the rank tracker, don't rent it

Search rank is a position in a list. For a given keyword, your domain sits at organic position 3, or 11, or nowhere on the first page β€” and that number changes as Google re-ranks and as competitors publish. Tracking it is the difference between knowing a page slipped from 4 to 9 last week and finding out when traffic has already gone.

Most "build a rank tracker" walkthroughs end at a Google Sheet you update by hand, or a formula that scrapes one cell at a time and breaks the moment Google changes its markup. That works for five keywords checked once a month. It does not scale to a keyword set you actually care about, and it gives you no history to diff against.

This guide builds a real, programmatic rank tracker in Python on top of Scrapeless Deep SerpApi. The API returns Google's organic results as structured JSON, so the tracker stays small: query a keyword, parse the positions, match your domain, store the snapshot, and diff runs to compute movement. By the end you have a script you can point at any keyword set and run on a schedule.


What You Can Do With a Programmatic Rank Tracker

  • Track a keyword set on a schedule. Run the same script daily or weekly against dozens of keywords and build a position history without touching a browser.
  • Watch a single page's keyword spread. Point the tracker at one URL's target keywords and see which terms it ranks for and where.
  • Detect drops before traffic does. A position that moves from 5 to 14 is a signal you can act on the day it happens, not at the end of the month.
  • Compare regional rankings. The same keyword ranks differently per country, so a tracker that varies the gl parameter shows your position in each market.
  • Feed rankings into your own dashboards. Because every result is JSON, the position data drops straight into a database, a notebook, or a reporting job with no scraping glue.
  • Audit a migration or redesign. Snapshot rankings before a site change and diff after, so a regression shows up as a list of dropped keywords.

Why Scrapeless Deep SerpApi

Scrapeless Deep SerpApi is a managed Google search endpoint that returns the SERP as structured JSON instead of raw HTML. For a rank tracker specifically, it brings:

  • Structured organic_results β€” each result is an object with position, title, and link, so there is no HTML parsing and no CSS selector to break on Google's next layout change.
  • A stable position field β€” the API reports each organic result's rank, which is the exact value a rank tracker records.
  • Country and language control β€” gl and hl parameters let you measure rankings per market from one endpoint.
  • Residential egress under the hood β€” searches run through residential proxies in 195+ countries, so results reflect a real user's SERP rather than a datacenter-flagged one.
  • An inline response β€” the scraper.google.search actor returns results in the same request, so the tracker reads the JSON directly with no polling step.

Get your API key on the free plan at app.scrapeless.com.


Prerequisites

  • Python 3.10 or newer
  • The requests library (pip install requests)
  • A Scrapeless account and API key β€” sign up at app.scrapeless.com
  • A keyword set and the target domain you want to track
  • Basic familiarity with the terminal

Read your API key from the environment so it never lands in the script:

bash Copy
export SCRAPELESS_API_KEY="your_api_token_here"

Step 1 β€” Query Google for one keyword

The tracker's smallest unit is one keyword query. Send a POST to the Deep SerpApi endpoint with the scraper.google.search actor and an input object carrying the query. The q field is the keyword; gl and hl set the country and interface language so every run measures the same market.

python Copy
import os
import requests

ENDPOINT = "https://api.scrapeless.com/api/v1/scraper/request"


def search(query, gl="us", hl="en"):
    payload = {
        "actor": "scraper.google.search",
        "input": {"q": query, "gl": gl, "hl": hl},
    }
    response = requests.post(
        ENDPOINT,
        json=payload,
        headers={"x-api-token": os.environ["SCRAPELESS_API_KEY"]},
    )
    response.raise_for_status()
    return response.json()


data = search("scrapeless scraping browser")
results = data.get("organic_results", [])
print(f"{len(results)} organic results")
for result in results[:5]:
    print(result["position"], result["link"])

The response is a JSON object returned in a single round of HTTP semantics; organic_results is the array the tracker reads. Each element carries a position (the rank on the page) and a link (the result URL). title, snippet, and source are also present, but a rank tracker only needs position and link.


Step 2 β€” Parse the organic positions

The query in Step 1 returns the full SERP object β€” organic results, plus ads, related searches, and inline videos when Google shows them. The tracker works only with organic_results, and only with two fields per entry. Reduce each result to a (position, link) pair so the matching step has a clean list to scan.

python Copy
def organic_positions(data):
    pairs = []
    for result in data.get("organic_results", []):
        pairs.append((result["position"], result["link"]))
    return pairs


positions = organic_positions(search("scrapeless scraping browser"))
for position, link in positions:
    print(position, link)

position is the rank Google assigned the result on the returned page. Read it straight from the field rather than counting list index, so the value stays correct even when the API omits a result type or the page composition shifts between runs.


Step 3 β€” Match your domain to a rank

A rank is your domain's position in that list. Matching has to be on the host, not a substring of the URL, so a search for "yourdomain" in the raw link does not falsely match a competitor's path or a query string. Parse each link to its host, strip a leading www., and treat a subdomain as a hit too.

python Copy
from urllib.parse import urlparse


def host_of(url):
    host = urlparse(url).netloc.lower()
    return host[4:] if host.startswith("www.") else host


def rank_of(domain, data):
    domain = domain.lower()
    for result in data.get("organic_results", []):
        host = host_of(result["link"])
        if host == domain or host.endswith("." + domain):
            return result["position"]
    return None


data = search("scrapeless scraping browser")
rank = rank_of("scrapeless.com", data)
print("scrapeless.com:", rank if rank is not None else "not in returned results")

rank_of returns the first position your domain holds, or None when it does not appear in the returned results. A None is real data β€” it means the domain is not on the page the API returned for that keyword, and the tracker should record that, not skip it.

Get your API key on the free plan: app.scrapeless.com


Step 4 β€” Track a keyword set and store snapshots

A single keyword is not a tracker. Run the match across a keyword set, stamp the result with a date, and append it to a history file. Each snapshot is one run: a date plus a map of keyword to position. Storing them as a list keeps the full timeline in one place for the diff step.

python Copy
import json
from datetime import date, timezone, datetime
from pathlib import Path

HISTORY = Path("rank_history.json")


def snapshot(domain, keywords):
    ranks = {}
    for keyword in keywords:
        ranks[keyword] = rank_of(domain, search(keyword))
    return {"date": date.today().isoformat(), "domain": domain, "ranks": ranks}


def append_snapshot(snap):
    history = json.loads(HISTORY.read_text()) if HISTORY.exists() else []
    history.append(snap)
    HISTORY.write_text(json.dumps(history, indent=2))
    return history


keywords = [
    "scrapeless scraping browser",
    "scrapeless deep serp api",
    "anti detection cloud browser",
]
snap = snapshot("scrapeless.com", keywords)
append_snapshot(snap)
for keyword, position in snap["ranks"].items():
    print(f"{position if position is not None else 'β€”':>3}  {keyword}")

Each run appends one snapshot. The keyword set is yours to define; swap in the terms a page is meant to rank for. Positions that come back None are written as null in the history, so a keyword that fell off the page is a recorded event rather than a gap.


Step 5 β€” Compute rank deltas between runs

History is only useful when you diff it. Compare the latest snapshot against the previous one for the same domain and classify each keyword: improved, dropped, unchanged, newly ranking, or fallen out. The delta is the previous position minus the current one, so a positive number means the domain moved up the page.

python Copy
def deltas(history, domain):
    runs = [s for s in history if s["domain"] == domain]
    if len(runs) < 2:
        return {}
    previous, current = runs[-2]["ranks"], runs[-1]["ranks"]
    report = {}
    for keyword, now in current.items():
        before = previous.get(keyword)
        if now is None and before is not None:
            report[keyword] = "fell out of results"
        elif now is not None and before is None:
            report[keyword] = f"new at {now}"
        elif now is not None and before is not None:
            move = before - now
            report[keyword] = "no change" if move == 0 else f"{move:+d} (was {before}, now {now})"
    return report


history = json.loads(HISTORY.read_text())
for keyword, change in deltas(history, "scrapeless.com").items():
    print(f"{keyword}: {change}")

On the first run there is nothing to diff, so deltas returns empty β€” the tracker just stores the baseline. From the second run on, every keyword carries a movement label you can route into an alert, a dashboard, or a weekly report. Schedule the Step 4 + Step 5 pair with cron or a CI job and the rank tracker runs itself.


What You Get Back

The history file is the tracker's output: one snapshot per run, each a date plus a keyword-to-position map. The shape below is exactly what append_snapshot writes.

json Copy
// illustrative sample β€” schema is exactly what append_snapshot writes; field values are illustrative.
[
  {
    "date": "2026-06-16",
    "domain": "yourdomain.com",
    "ranks": {
      "your primary keyword": 4,
      "your secondary keyword": 11,
      "a keyword you do not rank for yet": null
    }
  },
  {
    "date": "2026-06-23",
    "domain": "yourdomain.com",
    "ranks": {
      "your primary keyword": 2,
      "your secondary keyword": 9,
      "a keyword you do not rank for yet": 18
    }
  }
]

A few honest observations from live keyword runs:

  • Position counts come from the API's position field. Read them directly; do not infer rank from array index, because the returned result mix varies between runs.
  • Organic result counts vary per query. A keyword may return six organic results on one run and eight on another, so a None for a deep-ranking domain can simply mean it was below the returned set that day.
  • snippet_highlighted_words is conditional. Some results carry it and some do not; a rank tracker ignores it, but anything reading other fields should treat them as nullable.
  • Country matters. The same keyword ranks differently per gl value, so pin one country per tracked keyword and keep it constant across runs to compare like with like.

Conclusion: scale your rank tracking pipeline

A rank tracker reduces to five moves: query a keyword, parse the organic positions, match your domain on the host, store a dated snapshot, and diff against the last run. Built on Deep SerpApi, each of those moves reads a structured organic_results array instead of fighting Google's HTML, so the whole tracker stays under a hundred lines.

From here, scale it along the axes that matter: widen the keyword set, vary gl per market, and push snapshots into a database so the history outlives a single file. The same JSON-first approach extends to other surfaces covered in the Scrapeless Scraper API guide. Check the pricing plans and the Deep SerpApi docs for the full parameter set, pin one country per keyword, read positions from the position field, and treat an absent domain as a real null.


Join our community to claim a free plan and connect with developers building rank-tracking and search pipelines: Discord Β· Telegram.

Sign up at app.scrapeless.com for free Deep SerpApi credits and adapt the tracker above to the keyword set and markets your project needs.


FAQ

Q: Is tracking Google rankings with an API legal?
Querying publicly visible search results is generally how rank tracking works, and Deep SerpApi returns the same public SERP a browser would β€” the ranked pages Google has crawled and indexed, the same set of URLs site owners expose through the Sitemaps protocol. Rules vary by jurisdiction and use case, so review Google's terms alongside the crawl directives in the Robots Exclusion Protocol and consult counsel for your specific application.

Q: How does this differ from a Google Sheets rank tracker?
A spreadsheet tracker checks one cell at a time and stores no real history to diff. The script here queries a full keyword set programmatically, records dated snapshots, and computes movement between runs, so it scales to large keyword sets and runs on a schedule.

Q: Do I need a proxy to run the tracker?
No separate proxy setup is required. Deep SerpApi routes searches through residential egress in 195+ countries internally, so results reflect a real user's SERP without you managing IPs.

Q: How do I track rankings for a specific country?
Set the gl parameter to the country code and hl to the language when calling search. Keep the same gl for a given keyword across runs so the position history compares like with like.

Q: What does a null position mean?
It means the domain was not present in the organic results the API returned for that keyword on that run β€” either it ranks below the returned set or it dropped off the page. The tracker records it as a real event so a fall-out shows up in the deltas.

Q: How often should the tracker run?
Daily or weekly covers most needs. Schedule the snapshot-and-diff step with cron or a CI job; positions move on Google's timeline, so sub-daily polling rarely adds signal.

Q: Can I match subdomains and paths to my domain?
Yes. The rank_of matcher compares on host and treats a subdomain as a hit, so blog.yourdomain.com and yourdomain.com/page both count as your domain ranking.

At Scrapeless, we only access publicly available data while strictly complying with applicable laws, regulations, and website privacy policies. The content in this blog is for demonstration purposes only and does not involve any illegal or infringing activities. We make no guarantees and disclaim all liability for the use of information from this blog or third-party links. Before engaging in any scraping activities, consult your legal advisor and review the target website's terms of service or obtain the necessary permissions.

Most Popular Articles

Catalogue