DEV Community

Elowen
Elowen

Posted on

Build a Weekly SERP Trend Report with a Simple CSV Output

A weekly SERP report does not need to start as a dashboard.

For many teams, a CSV file is enough for the first version.

The goal is simple: take daily SERP snapshots, compare them over a week, and summarize the changes that someone should review.

This post shows a lightweight pattern for building that report from normalized SERP data. The collection layer can be a SERP API such as Talor Data SERP API, while the report logic stays in your own code.

The input shape

Assume you already save one normalized SERP snapshot per keyword per day.

A simplified record can look like this:

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

The exact fields depend on your provider and normalization layer. Do not build the report directly against a provider-specific response if you can avoid it. Normalize first, report second.

What the weekly report should answer

For a first version, avoid too many metrics.

I would start with five questions:

  • Which domains appeared most often this week?
  • Which tracked URLs moved up or down?
  • Which new URLs entered the top results?
  • Which URLs disappeared from the tracked range?
  • Which titles changed for important pages?

Those questions are easier to review than a raw dump of every daily result.

Minimal Python example

This example assumes you have a folder of JSON snapshots.

import csv
import json
from pathlib import Path
from urllib.parse import urlparse


SNAPSHOT_DIR = Path("serp_snapshots")
REPORT_PATH = Path("weekly_serp_report.csv")


def domain_from_url(url: str) -> str:
    return urlparse(url).netloc.replace("www.", "")


def load_snapshots() -> list[dict]:
    snapshots = []
    for path in SNAPSHOT_DIR.glob("*.json"):
        with path.open("r", encoding="utf-8") as file:
            snapshots.append(json.load(file))
    return snapshots


def flatten_results(snapshots: list[dict]) -> list[dict]:
    rows = []
    for snapshot in snapshots:
        keyword = snapshot["keyword"]
        checked_at = snapshot["checked_at"]

        for result in snapshot.get("results", []):
            url = result.get("url")
            if not url:
                continue

            rows.append(
                {
                    "keyword": keyword,
                    "checked_at": checked_at,
                    "position": result.get("position"),
                    "title": result.get("title"),
                    "url": url,
                    "domain": result.get("domain") or domain_from_url(url),
                }
            )
    return rows


def write_csv(rows: list[dict]) -> None:
    fieldnames = ["keyword", "checked_at", "position", "domain", "title", "url"]
    with REPORT_PATH.open("w", newline="", encoding="utf-8") as file:
        writer = csv.DictWriter(file, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(rows)


if __name__ == "__main__":
    snapshots = load_snapshots()
    rows = flatten_results(snapshots)
    write_csv(rows)
    print(f"Wrote {len(rows)} rows to {REPORT_PATH}")
Enter fullscreen mode Exit fullscreen mode

This is not a finished analytics system. It is a clean export that lets you inspect the week before deciding what to automate next.

Add a small summary layer

A raw CSV is useful, but a weekly review needs a summary.

You can start with domain frequency:

from collections import Counter


def summarize_domains(rows: list[dict]) -> list[tuple[str, int]]:
    counts = Counter(row["domain"] for row in rows if row.get("domain"))
    return counts.most_common(20)
Enter fullscreen mode Exit fullscreen mode

Then write a second CSV:

def write_domain_summary(domain_counts: list[tuple[str, int]]) -> None:
    with Path("weekly_domain_summary.csv").open("w", newline="", encoding="utf-8") as file:
        writer = csv.writer(file)
        writer.writerow(["domain", "appearances"])
        writer.writerows(domain_counts)
Enter fullscreen mode Exit fullscreen mode

Now the team has two useful files:

  • one detailed result export
  • one quick domain-level summary

Keep the first report boring

The first weekly report should not try to explain everything.

It should help someone ask better questions:

  • Why did this competitor domain appear so often?
  • Which keywords changed the most?
  • Are the same directories or review pages showing up repeatedly?
  • Did the search results become more commercial, educational, or comparison-driven?

A SERP API like Talor Data can provide the structured search result data. The report layer should translate that data into a review habit your team can maintain.

What I would add later

After the CSV version is useful, then consider adding:

  • position change calculations
  • new and removed URL detection
  • SERP feature summaries
  • keyword priority grouping
  • Slack or email delivery
  • a dashboard only if people keep asking for one

That order matters. A report that people read is better than a dashboard nobody reviews.

Final note

If you build weekly SERP reports, what do you summarize first: domains, URLs, ranking movement, SERP features, or content formats?

Top comments (0)