| |

Automate a Content Refresh Pipeline: Turn Decaying Pages into Prioritized Update Briefs (GSC + Python + LLM)

Most SEO teams have a decay detector by now — a job that flags pages losing clicks in Google Search Console. The problem is what happens next. A list of 40 slipping URLs lands in a Slack channel, everyone agrees they should be refreshed, and three weeks later nothing has shipped because no one decided which pages were worth the editor’s time or what specifically to change.

Detection is the easy half. The hard half is turning a decay signal into a prioritized, opinionated update brief that an editor can act on in twenty minutes. This post walks through a content-refresh pipeline that does exactly that: it pulls the decaying pages, scores them by recoverable opportunity, diffs each one against the live SERP, and uses an LLM to generate a concrete update brief — not “make it better,” but “you’re missing the three subtopics your top-ranking competitors all cover, your title hasn’t been touched since 2024, and a section references a deprecated API.”

If you don’t yet have the upstream half wired up, start with our automated content decay detection pipeline — this refresh workflow consumes its output.

Why a refresh pipeline beats a refresh checklist

Refreshing content manually fails for two boring reasons. First, prioritization is done by gut feeling, so teams refresh the page that’s emotionally salient rather than the one with the most recoverable traffic. Second, the brief is vague, so the writer rewrites the intro, changes nothing structural, and the page keeps sliding. A pipeline fixes both by making the prioritization quantitative and the brief specific and evidence-backed.

The mental model: decay detection answers which pages are bleeding; the refresh pipeline answers which of those are worth saving and exactly how. Those are different questions, and conflating them is why most “we’ll refresh old content” initiatives stall.

Step 1: Pull the decay candidates and quantify the opportunity

Start from your decay detector’s output — a table of URLs with their click trend over the last 90 days. Rather than refreshing everything, score each page by recoverable opportunity so a human spends effort where it pays back. A workable formula multiplies the lost clicks by how close the page already is to page one:

from googleapiclient.discovery import build

def opportunity_score(row):
    # row: clicks_now, clicks_peak, avg_position, impressions
    lost_clicks = max(row["clicks_peak"] - row["clicks_now"], 0)
    # pages parked at positions 5-15 recover fastest; deep pages rarely do
    if row["avg_position"] <= 3:
        proximity = 0.3        # already strong, low upside
    elif row["avg_position"] <= 15:
        proximity = 1.0        # the sweet spot for a refresh
    else:
        proximity = 0.4        # needs more than a refresh
    return lost_clicks * proximity

candidates = [r for r in decay_rows if r["impressions"] > 200]
candidates.sort(key=opportunity_score, reverse=True)
top = candidates[:15]   # cap the queue so editors aren't overwhelmed

The impression floor and the queue cap matter as much as the formula. A page with 12 impressions a month is noise; refreshing it is a vanity exercise. Capping the queue at 10–15 pages per cycle keeps the output actionable instead of producing another list nobody reads.

Step 2: Diff each page against the live SERP

The single most useful input to a refresh brief is the gap between your page and the pages currently outranking it. For each candidate, pull the live top-10 results for its primary query, fetch the ranking competitors, and extract their heading structure. The deltas — subtopics they cover that you don’t — are your refresh agenda.

import httpx
from selectolax.parser import HTMLParser

def page_headings(url):
    html = httpx.get(url, timeout=20,
                     headers={"User-Agent": "Mozilla/5.0"}).text
    tree = HTMLParser(html)
    return [n.text(strip=True) for n in tree.css("h2, h3")]

def serp_gap(my_url, competitor_urls):
    mine = set(h.lower() for h in page_headings(my_url))
    theirs = {}
    for u in competitor_urls[:5]:
        for h in page_headings(u):
            theirs.setdefault(h.lower(), 0)
            theirs[h.lower()] += 1
    # subtopics covered by >=3 competitors but missing from my page
    return [h for h, n in theirs.items() if n >= 3 and h not in mine]

Use a consensus threshold (covered by at least three of five competitors) rather than scraping a single result. One competitor’s heading is an opinion; three competitors converging on the same subtopic is a signal that searchers expect it. Pull your SERP data through a reliable source — the Search Console API for your own performance and a SERP provider or scraping layer for competitor pages. If you’re standing up the scraping layer, our teardown of scraping backbones for SEO pipelines compares the realistic options.

Step 3: Generate the update brief with an LLM

Now assemble the evidence — the page’s current content, its decay metrics, the SERP gap list, and the page’s age — and hand it to an LLM with a structured prompt. The trick is to force concrete, checkable instructions and forbid generic advice.

import anthropic

PROMPT = """You are an SEO content editor. Produce a refresh brief for this page.
Decaying page: {url}
Primary query: {query}
Lost clicks (90d): {lost_clicks}
Current avg position: {position}
Last meaningful update: {last_modified}
Subtopics competitors cover that this page is missing:
{gaps}
Current H2/H3 outline:
{outline}

Rules:
- Recommend at most 6 changes, ordered by expected impact.
- Every recommendation must be specific and verifiable (name the section,
  the missing subtopic, or the stale fact). No advice like "improve quality".
- Flag any factual statement that is likely outdated given the page age.
- Propose one revised title tag and meta description if the current ones
  predate the page's peak traffic.
Return JSON: {{"changes": [...], "title": "...", "meta": "..."}}"""

client = anthropic.Anthropic()
def build_brief(ctx):
    msg = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1200,
        messages=[{"role": "user", "content": PROMPT.format(**ctx)}],
    )
    return msg.content[0].text

The “at most six changes, ordered by impact” constraint is doing real work. Without a cap the model returns a wishlist; with one it has to prioritize, which is exactly the judgment you want to automate. For the title and meta recommendations, this dovetails with a dedicated CTR pipeline — see how we automate title and meta description rewrites from GSC data for the click-through side of the same problem.

Step 4: Route briefs to humans, not straight to publish

Resist the urge to auto-publish LLM edits. Route each brief into your editorial queue — a Notion row, a Jira ticket, an n8n node that posts to Slack — with the evidence attached so the editor trusts the recommendation. The pipeline’s job is to eliminate the research and prioritization grunt work, not to replace editorial judgment. A reasonable cadence is a weekly n8n schedule: run decay detection, score, diff, brief, and drop the top 10 briefs into the queue every Monday.

This is also where a refresh pipeline differs from net-new content generation. If you’re producing briefs for pages that don’t exist yet, that’s a different workflow — our guide to automating SEO content briefs with Python, SERP data, and an LLM covers the from-scratch case.

Results you should expect — and how to measure them

Treat the pipeline itself as something to be measured. Tag every refreshed URL and the date it shipped, then track its click trajectory for the following 56 days against the cohort you chose not to refresh. Two things usually show up. First, pages in the position 5–15 band recover fastest and most reliably — which validates the proximity weighting in Step 1. Second, refreshes that closed a real SERP content gap outperform refreshes that only touched the intro and title, which is why Step 2 is non-negotiable.

The honest caveat: a refresh helps a page that has slipped on a query it can plausibly win. It will not resurrect a page demoted by a site-wide quality problem, and it will not manufacture demand for a topic that has stopped being searched. Decay from those causes needs a different intervention, and feeding them into a refresh queue just burns editorial hours. Quantify the opportunity first; that’s the whole point of Step 1.

Takeaways

A content-refresh pipeline is the missing action layer on top of decay detection. Score candidates by recoverable opportunity so humans work the highest-leverage pages; diff each page against SERP consensus so the brief is evidence-backed rather than vibes; constrain the LLM to a short, prioritized, verifiable change list; and keep a human in the publishing loop. Build it as a weekly scheduled job and you convert “we should refresh old content someday” into a steady, measurable stream of shipped improvements.

Found this useful? Bookmark SEO Automation Club and subscribe for a new automation playbook every week — working code, real workflows, no fluff.



Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *