An image illustrating Case Study: Automating Keyword Clustering with n8n

Case Study: Automating Search Intent Clustering with n8n

This is a worked case study, not a tutorial in disguise. We took a real Google Search Console export of 512 keywords from a mid-size SEO blog and used a single n8n workflow to group them by search intent instead of by surface wording. The goal was practical: stop writing near-duplicate posts for queries that are really the same intent, and find the clusters that deserved one strong page instead of six thin ones.

Who this is for: a solo SEO or small team that already has a GSC property and a self-hosted n8n instance, and wants repeatable clustering without babysitting a notebook. Who this is not for: if you are clustering 50,000+ keywords or need statistically tuned HDBSCAN parameters, an in-house Python pipeline will serve you better than an n8n Code node — we cover that route in our companion piece on search intent clustering in Python with GSC, embeddings and HDBSCAN.

Keyword clustering vs. search intent clustering

Most “keyword clustering” tools group strings that look alike. “best crm software” and “crm software reviews” land together because they share tokens. But search intent clustering asks a different question: would the same page satisfy both queries? “crm pricing” and “how much does a crm cost” share almost no words yet share one intent — a pricing page. Grouping by intent is what actually prevents cannibalization, because Google ranks one URL per intent, not one URL per phrase. Google’s own guidance on creating helpful, people-first content pushes the same way: consolidate overlapping pages that serve one need. That is why we cluster on meaning (embeddings), not on tokens.

The setup: dataset and stack

The input was a CSV exported from GSC (query, clicks, impressions, position) for the trailing 90 days. We embedded each query, measured pairwise similarity, and merged anything above a cosine threshold into the same intent cluster. Here is the exact stack and what each part cost us in this run:

Component Role Cost / limit in this run
GSC export (CSV) 512 queries, 90-day window Free
n8n (self-hosted) Orchestration + Code node Free (own server)
OpenAI text-embedding-3-small Vector per query ~$0.0003 for 512 queries
Code node (cosine merge) Cluster assignment Free, ~2s runtime
Google Sheets node Write clusters back Free

Pricing note: text-embedding-3-small is billed at $0.02 per 1M tokens per the OpenAI embeddings documentation (checked August 2026); our 512 short queries were about 15k tokens total, so the embedding cost rounded to a third of a cent.

The n8n workflow, node by node

Five nodes: Read CSV → Split → HTTP Request (embeddings) → Code (cluster) → Google Sheets. The embeddings call is a plain HTTP Request node to the OpenAI endpoint; the clustering logic lives in one Code node. This is the whole clustering algorithm — it is runnable as-is, paste it into an n8n Code node after the embeddings step:

// n8n Code node (Run Once for All Items)
// Input items: { query, embedding: number[] }
const THRESHOLD = 0.82;            // cosine similarity to merge
const items = $input.all().map(i => i.json);

const dot = (a, b) => a.reduce((s, v, k) => s + v * b[k], 0);
const norm = a => Math.sqrt(dot(a, a));
const cosine = (a, b) => dot(a, b) / (norm(a) * norm(b));

const clusters = [];              // each: { centroid, members: [] }
for (const it of items) {
  let best = null, bestScore = -1;
  for (const c of clusters) {
    const s = cosine(it.embedding, c.centroid);
    if (s > bestScore) { bestScore = s; best = c; }
  }
  if (best && bestScore >= THRESHOLD) {
    best.members.push(it.query);
  } else {
    clusters.push({ centroid: it.embedding, members: [it.query] });
  }
}

return clusters.map((c, i) => ({
  json: { cluster_id: i + 1, size: c.members.length, queries: c.members.join(" | ") }
}));

The one knob that matters is THRESHOLD. At 0.82 we got tight, obviously-same-intent groups; dropping to 0.75 started merging “n8n vs make” with “n8n vs zapier” (related, but they deserve separate comparison pages). Tune it against 20 queries you already understand before trusting it on 500.

Runnable Python fallback (no n8n needed)

If you would rather run this once from a laptop, the same greedy cosine merge is about 20 lines. It reads the embeddings you already fetched and prints clusters:

import numpy as np

THRESHOLD = 0.82
queries = [...]          # list[str]
vectors = np.array([...])  # shape (n, 1536), already embedded

clusters = []            # list of dicts: {centroid, members}
for q, v in zip(queries, vectors):
    best, best_s = None, -1.0
    for c in clusters:
        s = float(v @ c["centroid"] / (np.linalg.norm(v) * np.linalg.norm(c["centroid"])))
        if s > best_s:
            best_s, best = s, c
    if best and best_s >= THRESHOLD:
        best["members"].append(q)
    else:
        clusters.append({"centroid": v, "members": [q]})

for i, c in enumerate(clusters, 1):
    print(i, len(c["members"]), " | ".join(c["members"]))

Results: 512 queries, before vs. after

The workflow collapsed 512 queries into 147 intent clusters. Cross-referencing clusters against our published URLs surfaced 23 cannibalization clusters — groups where one intent was split across two or more of our own posts. Here is a representative sample of the output:

Cluster Sample queries merged Our URLs before
Free SEO automation “free seo automation”, “free automated seo software”, “seo automation software free” 2 posts
n8n vs Power Automate “n8n vs power automate”, “power automate vs n8n”, “is power automate like n8n” 2 posts
Workspace SEO “seo workspace”, “google workspace seo”, “workspace for seo” 3 posts

The measured payoff was time and clarity, not magic rankings. Doing this by hand for 512 queries in a spreadsheet took us about 3 hours in a prior audit; the n8n run finished in under 4 minutes end to end and produced a repeatable artifact we could re-run monthly. The clustering did not lift traffic by itself — the consolidation decisions it enabled did, by pointing us at exactly which thin posts to merge with 301s. That is the honest takeaway: automating the clustering is cheap and fast; acting on it is the work.

When to automate this — and when not to

Automate it when you re-cluster regularly (monthly audits, post-migration, or after a big content push) and your set is a few hundred to a few thousand queries. Skip the automation when it is a one-off of 40 keywords — you will read them faster than you will wire the nodes. And do not reach for n8n if you need density-based clustering with noise handling on tens of thousands of queries; that is a Python-and-HDBSCAN job. For choosing the orchestration tool itself, our n8n vs Power Automate comparison walks through which fits SEO workloads, and if you are assembling a broader stack, start from free automation tools every SEO should try.

Frequently asked questions

What is the difference between keyword clustering and search intent clustering?

Keyword clustering groups queries by shared words; search intent clustering groups them by whether one page would satisfy them. Intent clustering uses embeddings so that “crm pricing” and “how much does a crm cost” land together despite sharing no tokens.

Why use n8n instead of a clustering SaaS?

n8n keeps your GSC data on your own server, costs nothing beyond the embedding calls, and lets you re-run the exact same pipeline on a schedule. A SaaS is faster to start but harder to audit and to fit into an existing workflow.

What cosine threshold should I use?

Start at 0.82 and validate against 20 queries whose intent you already know. Lower it if clusters are too fragmented, raise it if unrelated intents get merged. There is no universal value — it depends on your embedding model and query style.

Does clustering improve rankings on its own?

No. Clustering surfaces cannibalization; the ranking gains come from the consolidation decisions you make afterward — merging thin pages, adding 301 redirects, and strengthening the surviving canonical.

Next step: run the workflow on your own GSC export, then take the cannibalization clusters into a consolidation plan. If you want the heavier statistical version, read the Python GSC + embeddings + HDBSCAN pipeline.

Similar Posts