Running SEO monitoring for a content site with Claude

Short answer: split the work. Code pulls the numbers from Search Console and does the arithmetic: per-page changes in clicks, impressions and average position between two equal periods, and a first classification of each drop as demand (fewer searches) or ranking (worse position). Claude reads the computed table, explains what's going on in plain language, and drafts a short, prioritised action list. Models are good at reasoning over a summary and bad at summing a spreadsheet, so give each side the job it's good at.

A real example: the drop that wasn't a penalty

A content site's traffic falls by about half. The first suspicion is always "Google penalised us", and that suspicion leads straight to rewriting pages and chasing algorithm updates.

The data can say otherwise. If impressions fell across the site while average positions stayed where they were, the pages still rank; fewer people are searching, or fewer people can reach the site. That calls for a completely different response: publish consistently, wait out external disruptions, and don't touch pages that are ranking fine.

That distinction is the most valuable thing a weekly check can give you, and it's exactly the kind of thing code can classify and a model can explain.

Step 1: get comparable data

In Search Console, open Performance → Search results, choose Compare, and pick two equal periods (for example the last 28 days against the previous 28). Export it; the Pages sheet has one row per URL with clicks, impressions, CTR and position. For automation, the Search Console API returns the same data.

Export each period separately as a CSV with the columns Top pages, Clicks, Impressions, CTR, Position.

Step 2: let code do the arithmetic

A small .NET 10 console app reads both exports, matches pages, and classifies each page:

using System.Globalization;

/// <summary>One row of Search Console's "Pages" export: Top pages, Clicks, Impressions, CTR, Position.</summary>
public sealed record PageStats(string Url, int Clicks, int Impressions, double Position);

public enum DropCause
{
    Stable,

    /// <summary>Fewer people searched: impressions fell, but the page still ranks where it did.</summary>
    Demand,

    /// <summary>The page ranks worse: average position moved down by more than a couple of places.</summary>
    Ranking,
}

public sealed record PageChange(string Url, int ClicksBefore, int ClicksAfter, double ImpressionChange, double PositionChange, DropCause Cause);

public static class TrafficDiagnosis
{
    public static IReadOnlyList<PageStats> ReadPagesCsv(string path) =>
        [.. File.ReadLines(path).Skip(1) // header: Top pages,Clicks,Impressions,CTR,Position
            .Select(line => line.Split(','))
            .Where(cells => cells.Length >= 5)
            .Select(cells => new PageStats(
                cells[0].Trim('"'),
                int.Parse(cells[1], CultureInfo.InvariantCulture),
                int.Parse(cells[2], CultureInfo.InvariantCulture),
                double.Parse(cells[^1], CultureInfo.InvariantCulture)))];

    /// <summary>
    /// Compares two periods page by page. Position is "lower is better", so a positive PositionChange means the page fell.
    /// </summary>
    public static IReadOnlyList<PageChange> Compare(IEnumerable<PageStats> before, IEnumerable<PageStats> after, int minImpressions = 100)
    {
        var current = after.ToDictionary(p => p.Url);
        return [.. before
            .Where(p => p.Impressions >= minImpressions && current.ContainsKey(p.Url))
            .Select(p =>
            {
                var now = current[p.Url];
                var impressionChange = (now.Impressions - p.Impressions) / (double)p.Impressions;
                var positionChange = now.Position - p.Position;
                var cause = impressionChange > -0.2 ? DropCause.Stable
                    : positionChange > 2 ? DropCause.Ranking
                    : DropCause.Demand;
                return new PageChange(p.Url, p.Clicks, now.Clicks, impressionChange, positionChange, cause);
            })
            .OrderBy(c => c.ClicksAfter - c.ClicksBefore)];
    }
}

The rules are deliberately simple and written down:

  • Ignore small pages (minImpressions), because a page going from 12 to 6 impressions is noise.
  • Stable if impressions fell by less than 20%.
  • Ranking if impressions fell and the average position got worse by more than two places.
  • Demand if impressions fell but the position held.

Print the result as a Markdown table. That table, not the raw export, is what the model sees.

Step 3: let Claude read it

Give Claude the computed table plus context the numbers don't contain: what you published, site changes, known outages or internet disruptions. A prompt that works well:

You are reviewing weekly Search Console data for a Persian technical publication. Below is a table of pages with clicks before and after, impression change, position change and a computed cause (Demand, Ranking or Stable). Context: we published 2 articles this period instead of the usual 8; there were nationwide internet disruptions on three days.

  1. Summarise what happened in three sentences.
  2. List the pages classified as Ranking, most clicks lost first, with one hypothesis each.
  3. Propose at most five actions for this week, ordered by expected impact. Don't propose rewriting pages classified as Demand.

The last instruction matters. Without it, a model will happily suggest optimising every page that lost clicks.

Step 4: make it a routine

Run the export and the console app every week, keep the tables, and compare week to week. Once the routine is stable, the export can come from the API on a schedule, and Claude's summary can land wherever you read things: an email, a Telegram message, a dashboard note.

Pros and cons

Pros

  • The numbers are computed, not hallucinated; the model only explains them.
  • Demand drops and ranking drops get different responses, which avoids "fixing" healthy pages.
  • A weekly written summary builds a history of what happened and why.

Cons

  • Search Console data is sampled and delayed by a few days; it shows trends, not exact truth.
  • The thresholds are heuristics; check borderline pages by hand.
  • The model only knows the context you give it. Outages, publishing gaps and site changes have to be written into the prompt.

Want a monitoring routine like this for your site, or help reading a traffic drop? See how I can help.

Frequently asked questions

Why not paste the raw Search Console export into the AI?

Because language models are unreliable at arithmetic over thousands of rows. Compute the numbers with code (deltas, classifications, totals), then give the model the computed table and ask it to explain, prioritise and draft actions.

How do I tell a demand drop from a ranking drop?

Compare impressions and average position per page between two equal periods. If impressions fell but the position stayed about the same, fewer people searched (demand). If the position moved down, you lost rankings. Clicks alone can't tell you which.

How often should this run?

Weekly is a good rhythm for most content sites: long enough to smooth daily noise, short enough to catch a problem before it costs a month. Compare the same weekdays to avoid weekend effects.