DEV Community

Elowen
Elowen

Posted on

Add Change Detection to Daily SERP Snapshots

Saving daily SERP snapshots is useful, but snapshots alone still leave you with a review problem.

If you collect 30 keywords every day, you do not want to open yesterday's file and today's file manually.

You need a small diff step.

This post walks through a simple way to compare two SERP snapshots and detect changes in URLs, titles, and positions.

The example is intentionally generic. Use it as a pattern, then map it to the response schema from your own SERP API provider. If you are using Talor Data SERP API, keep the normalized internal shape separate from the provider response so your diff logic stays easy to test.

The snapshot shape

Assume each daily snapshot has one keyword and a list of normalized organic results:

{
  "keyword": "serp api for seo monitoring",
  "checked_at": "2026-07-03T09:00:00Z",
  "results": [
    {
      "position": 1,
      "title": "Example title",
      "url": "https://example.com/page",
      "snippet": "Short result summary"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The key idea is to compare stable fields first.

For most SEO review workflows, the first useful questions are:

  • Which URLs are new?
  • Which URLs disappeared?
  • Which URLs moved position?
  • Which titles changed?

A minimal diff function

from dataclasses import dataclass
from typing import Any


@dataclass
class SerpChange:
    change_type: str
    url: str
    before: dict[str, Any] | None
    after: dict[str, Any] | None


def index_by_url(snapshot: dict[str, Any]) -> dict[str, dict[str, Any]]:
    return {
        result["url"]: result
        for result in snapshot.get("results", [])
        if result.get("url")
    }


def diff_snapshots(before: dict[str, Any], after: dict[str, Any]) -> list[SerpChange]:
    before_by_url = index_by_url(before)
    after_by_url = index_by_url(after)

    changes: list[SerpChange] = []

    for url, after_result in after_by_url.items():
        before_result = before_by_url.get(url)

        if before_result is None:
            changes.append(SerpChange("new_url", url, None, after_result))
            continue

        if before_result.get("position") != after_result.get("position"):
            changes.append(SerpChange("position_changed", url, before_result, after_result))

        if before_result.get("title") != after_result.get("title"):
            changes.append(SerpChange("title_changed", url, before_result, after_result))

    for url, before_result in before_by_url.items():
        if url not in after_by_url:
            changes.append(SerpChange("removed_url", url, before_result, None))

    return changes
Enter fullscreen mode Exit fullscreen mode

This is not a full rank tracker. It is a small review layer between raw collection and alerting.

Make the output readable

A diff is only useful if someone can review it quickly.

Instead of dumping raw objects, format the changes into a short table or message:

def summarize_change(change: SerpChange) -> str:
    if change.change_type == "new_url":
        return f"New result: {change.url} at position {change.after.get('position')}"

    if change.change_type == "removed_url":
        return f"Removed result: {change.url} was position {change.before.get('position')}"

    if change.change_type == "position_changed":
        return (
            f"Position changed: {change.url} "
            f"from {change.before.get('position')} to {change.after.get('position')}"
        )

    if change.change_type == "title_changed":
        return f"Title changed: {change.url}"

    return f"Changed: {change.url}"
Enter fullscreen mode Exit fullscreen mode

For a first version, I would avoid sending alerts immediately.

Save the diff output first. Review it manually for a few days. Then decide which change types deserve notification.

Add a threshold before alerting

Not every change should trigger a message.

A few practical rules:

  • alert when a tracked competitor enters the top 10
  • alert when your own page disappears from the first page
  • alert when a high-priority keyword gets a new SERP feature
  • log title changes, but do not always alert on them
  • ignore small movements for low-priority keywords

The threshold should match the decision you expect someone to make.

If a change does not lead to a decision, it probably belongs in a report, not an alert.

Where the SERP API fits

The SERP API is the collection layer. It gives you the daily search result snapshots.

The diff layer belongs to your application because your team decides what counts as meaningful.

That separation matters. A tool like Talor Data can provide structured search result data, while your code defines the review rules, storage format, and alert thresholds.

Final note

If you already compare SERP snapshots, what do you treat as a real change: URL movement, title changes, SERP features, or competitor domains entering the results?

Top comments (0)