HasData
Back to all posts

How to Track SERP History

Google publishes no history of its own results. Yesterday’s page for a query is gone, and the only copy is the one somebody saved. That makes SERP history a recording problem before it’s an analysis problem, and the recording has to be running before the thing you want to look at happens.

Three routes below get you a recording. A Google Sheets template, an integration built in Zapier or Make.com, and a Python script. The script is the one worth reading closely, because what it stores decides what you can ask later.

Why SERP History Has to Be Recorded as It Happens

The obvious place to look for an old search results page is the Internet Archive, and it mostly isn’t there. Across 20 ordinary queries, only 9 had any capture of their google.com/search URL at all. “web scraping”, “seo tools”, “rank tracker”, “serp api” and “cheap flights” had none.

Where captures exist, most of them aren’t a results page. The 2026 capture of search?q=python is 180 characters of visible text, and all of it says “Please click here if you are not redirected within a few seconds.” A 2023 capture of search?q=javascript+tutorial does carry a page, in German, because the crawler’s locale is the one that got recorded rather than yours. The old ones are the real thing. A capture of search?q=weather from 2000 still reads “Searched the web for weather. Results 1”, which tells you a lot about that SERP and nothing about this year’s.

So the archive covers the era when a SERP was ten links and static HTML, and thins out exactly as the page became worth tracking. Your archive starts on the day you start writing one.

What a Google SERP Is Made Of Now

The 2000 capture says “Results 1-10”. Fifteen queries pulled through a SERP API today return a median of 8 organic results, ranging from 4 to 10, and organic is no longer most of the page.

BlockOn how many of the 15 queries
Organic results15
Related searches15
AI Overview12
Related questions12
Perspectives8
Discussions and forums3
Knowledge graph2

A position number alone describes less of that page every year. Rank three under an AI Overview, four related questions and a perspectives carousel is a different amount of attention from rank three in 2019, and the only way to tell the two apart later is to have kept the rest of the page.

Tools for SERP History Collection

Four routes lead to a stored SERP, and they differ less in price than in what survives the storing. That difference is easier to see once the thing being stored has a name.

What Is a SERP Snapshot?

A snapshot is the stored page, not a number derived from it. With the API used below it arrives as HTML, the same markup a browser would have rendered, which is why a snapshot can answer a question you never thought to add a column for. A rank log only answers the question its columns were built for.

What separates the routes is how far back they reach, how often they record, and how much of the page survives.

ApproachHow far back it goesHow often it recordsWhat you get to keep
Web archivesto 2000 in principlewhenever a crawler happened to pass, which for most queries is nevera stored page, often a redirect stub for recent dates
SEO platformsfrom the day you add the keywordon the platform’s schedule, daily for Semrush Position Trackingthe position, plus whichever SERP features the platform parses
SERP APIfrom your first callevery time you call itparsed JSON for the whole page plus the stored HTML behind it
Your own scraperfrom your first runevery time you run itwhatever you wrote a parser for, until the markup moves

The last two columns are where the choice usually gets made. A platform hands you a number per keyword per day, which answers “did I move” and can’t answer “what did the page look like when I moved”. An API that stores the page answers both, at the cost of you deciding when to call it.

Serpstat, Ahrefs and Semrush all take the platform route. Ahrefs and Semrush publish their ladders. Entry plans sell at $99 and $139 a month, and the tiers above them at $449 and $549.

The rest of this article uses the HasData SERP API, which returns the parsed results and a stored copy of the page.

How to Track Historical SERPs

Three ways to run this, in order of how much code they need. A pre-configured Google Sheet with an App Script takes none. Zapier or Make.com takes none either, and gives you somewhere to send the data afterwards. The Python script takes the most and gives you the most control over what gets stored.

Use Google Sheets Template

The easiest way to collect Google search results snapshots is to use a ready-made template. To do this, copy our Google Spreadsheet and launch the HasData add-on. Select Rank Checker, and enter your API key, keywords, and a link to the domain you want to check the position. You can also customise the other parameters as desired.

Once you have configured Rank Checker, you can launch it to get snapshots and links to the found page for every keyword, along with its current position. To collect an archive of search results screenshots, simply run Rank Checker at the desired frequency.

Rank Checker screenshot showing snapshots and links to the found page for every keyword

SERP history tracker in Google Sheets

The same template also does automatic position tracking in Google Sheets, which runs off the same key and the same keyword list.

Use Zapier or Make.com Integration Service

Another solution is to use integration services to create personalised apps without programming skills. Zapier and Make.com are the most popular services of this type. We have already covered how to develop zaps with Zapier, get data from Google SERP, and various websites with Make.com.

To create a SERP history tracker in Zapier, create a trigger for the snapshot collection, the keyword source, and the HasData SERP API. This will result in the following data:

This image shows a Zapier automation that uses HasData's SERP API to track the SERP history for a specific keyword.

Zapier SERP history tracker

You can then integrate this data with Google Sheets, email, or another service to save or process it.

Make.com has more flexibility for processing arrays. For periodic SERP history snapshots scraping for all keywords, you can use Make.com’s capabilities without adding additional sources. The rest of the approach is the same as for Zapier.

A screenshot of a Make workflow that includes a step to scrape SERP history for keywords.

SERP history tracker based on HasData and Make

You can also decide how to process or store the collected data and integrate it into your app.

Use Python Script

There’s a ready-to-run copy on Google Colaboratory. Drop in your API key and your keywords and run it.

If you want to make your own, create a new file with the *.py extension and import the necessary libraries. In this script, we will use:

  1. The Requests library to make HTTP requests to the API.
  2. The OS library checks whether the file we append the SERP history to already exists.
  3. The DateTime library adds the current date and time.
  4. The Pandas library creates a data frame and saves the data to a file. We will save the data to a CSV file, and Pandas supports other formats if you want one.

os and datetime ship with Python. requests and pandas don’t, so on a fresh machine install them first. Colab already has both, which is why the notebook skips this step.

pip install requests pandas

To import the libraries to your Python script, use the following code:

import requests
import pandas as pd
from datetime import datetime
import os

Now, let’s define the variables we will store the data. We need two, one for storing the processed keywords and one for collecting the results. We will use them later:

keywords = []
results = []

Set the variable in which you can place the keywords that will be processed in the future and added to the keywords variable:

serp_keywords = ["""
web scraping, top scraping tools
scraper
"""]

This approach is needed for convenience, as it is sometimes easier to specify keywords with a new line and sometimes with a comma. Therefore, to avoid errors in the future, we will first place the original data in a separate variable and then process it and create an array.

Next, we will set the parameters for working with the API, meaning the link, the API key, and the basic parameters such as the Google domain, the country, and the search language. This is necessary to make the request more customised and useful:

api_url = 'https://api.hasdata.com/scrape/google/serp'
headers = {'x-api-key': 'API-KEY'}
base_params = {
    'domain': 'google.com',
    'gl': 'us',
    'hl': 'en'
}

To continue, we need to extract all keywords from the string stored in the serp_keywords variable. To do this, we will split it line by line and check for commas as a separator. After that, we will add each keyword to the keywords variable:

serp_keywords = [line.strip() for line in serp_keywords[0].split('\n') if line.strip()]
for input_line in serp_keywords:
    words = input_line.split(',')
    if words:
        keywords.extend(words)

Now, we can iterate over the resulting array element by element and collect snapshots for each query. Let’s add a loop to iterate over all keywords and add it as a parameter for calling the API along with the basic parameters added earlier.

for keyword in keywords:
    if keyword.strip():
        params = {**base_params, 'q': keyword.strip()}
        try:
            pass  # the request goes here, filled in below
        except Exception as e:
            print('Error:', e)

We used a try/except block to isolate code from errors that could occur during the execution of requests. With this approach, even if an error occurs when processing a keyword, the script will output error information to the console and move on to the next keyword instead of completely halting execution.

Make an API request, and if the response is positive (status code 200), store the keyword, Google SERP snapshot URL, and current date and time in the results variable:

response = requests.get(api_url, params=params, headers=headers)
if response.status_code == 200:
    data = response.json()
    print(f"{keyword.strip()},{data['requestMetadata']['html']},{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    result = {
        'Keyword': keyword.strip(),
        'Snapshot': data['requestMetadata']['html'],
        'Date': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    }
    results.append(result)

requestMetadata.html is the URL of the stored page, and it’s the field that makes this an archive rather than a rank log. Two things about it are worth knowing before you build on it. Fetching it needs the same x-api-key header as the API call, so pasting the link into a browser tab returns 401 rather than the page. And because the whole loop is wrapped in try/except, a field name that stops existing costs you nothing visible. Every keyword prints one Error: line, the script exits clean, and the CSV it writes is a header row and nothing else. Print data.keys() once when a run comes back empty.

Finally, after iterating over all keywords, let’s save the historical data to a CSV file. We will check if the file exists, and if not, we will create it. If the file does exist, we will simply append the data to the existing file.

df = pd.DataFrame(results)

csv_file_path = 'serp_history.csv'
file_exists = os.path.isfile(csv_file_path)
mode = 'a' if file_exists else 'w'
df.to_csv(csv_file_path, mode=mode, index=False, encoding='utf-8', header=not file_exists)

Run the cells in order and the output looks like this.

The script running in Google Colaboratory, printing one line per keyword with the snapshot URL and timestamp before writing them to CSV

Python SERP history tracker in Google Collaboratory

Conclusion

A platform gives you a position per keyword per day and nothing behind it. An API that stores the page gives you the AI Overview that pushed you down, the related questions that took the space, and the competitor who wasn’t there last month. On the 15 queries measured above, that’s most of what the page actually was.

Whichever route you pick, the useful property is the same. It starts recording now, and in six months it can answer a question you haven’t thought of yet. That’s the whole argument for running it before you need it.

Valentina Skakun
Valentina Skakun
Valentina is a software engineer who builds data extraction tools before writing about them. With a strong background in Python, she also leverages her experience in JavaScript, PHP, R, and Ruby to reverse-engineer complex web architectures.If data renders in a browser, she will find a way to script its extraction.
Articles

Might Be Interesting