Most SEOs treat HARO like a volume game—blast out a generic pitch, cross your fingers, and move on.That works about as well as link building with spun articles in 2015.
Automating Keyword Clustering with Free NLP Libraries and GitHub Actions
If you are a solo marketer running your own SEO stack, you already know the pain of manual keyword grouping. Exporting a thousand keywords from Ahrefs or Semrush, then dragging cells around in Google Sheets is soul-crushing work. Worse, it introduces human bias and inconsistency. The solution is not a paid enterprise tool that costs as much as your monthly rent. It is a serverless, open-source pipeline that runs on free tiers and delivers deterministic clusters with no GPU required. This is the kind of automation that scales from a side project to a full site without asking for your credit card.
The core idea is straightforward. Use a lightweight natural language processing library to compute semantic similarity between keyword vectors, then apply a graph-based clustering algorithm. Run the entire process inside a GitHub Actions workflow triggered on a cron schedule or a push to a YAML file. No dedicated server, no database, no monthly subscription. Just a repository, a few Python dependencies, and a token for an optional embedding API if you want to squeeze out marginal improvements.
Start with the data layer. You need a clean list of target keywords. Export from your favorite tool as a CSV with a single column, or pull them directly from the Google Search Console API via a free code snippet. Store the CSV in a GitHub repository under a `keywords/` directory. The workflow will clone the repo, read the file, and process it every night. If you want to get fancy, set up a scheduled automated export from your SEO tool using its webhook or Zapier free tier, and push the file to the repo via a simple REST call. That gives you a zero-cost ingestion pipeline.
Now the heavy lifting. The Python script loads the keywords, strips whitespace, lowercases everything, and generates embeddings. For maximum frugality, use the `sentence-transformers` library with the `all-MiniLM-L6-v2` model. It is small enough to download in under ten seconds and runs on a CPU core in a GitHub Actions runner. The embeddings capture semantic relationships: “cheap running shoes” and “budget athletic sneakers” will land close together even without exact lexical overlap. If you have fewer than five thousand keywords, the free runner memory is plenty. For larger sets, chunk them or switch to a lighter model like `distiluse-base-multilingual-cased`.
Once you have a matrix of embedding vectors, apply affinity propagation clustering. This algorithm does not require you to pre-specify the number of clusters, which is perfect when you have no idea how many topical groups exist. It works by sending messages between data points until a set of exemplars emerges. The result is a dictionary mapping each keyword to a cluster label. Alternatively, use HDBSCAN for density-based clustering that handles noise well, but affinity propagation runs faster on small to medium datasets and gives more interpretable labels.
After clustering, the script outputs a simple JSON file with the cluster assignments and a CSV with an added `cluster_id` column. Write these files back to the repository in an `output/` directory. The GitHub Actions workflow then commits and pushes the results automatically. You now have a living document of keyword groups that updates every time you add new keywords to the input folder. No manual intervention. No broken spreadsheets.
But the real power is in the secondary automation this enables. With a structured list of clusters, you can trigger downstream workflows. For example, use another free action to generate a content brief from each cluster’s top terms using a language model API like GPT-4o-mini (costs pennies per run). Or pipe the cluster JSON into a Jekyll static site generator to create a keyword silo map for your site architecture. Or feed it into a custom Python script that cross-references cluster IDs with existing URL rankings to identify content gaps. Each of these is a small, composable micro-automation that runs for free on GitHub’s generous public repository minutes.
One critical detail to avoid breaking the bank is managing the caching of embeddings. The `sentence-transformers` model files are about 90 MB. Downloading them every run wastes time and counts against your runner’s bandwidth. Set up a GitHub Actions cache keyed on the model name and the OS. The first workflow run downloads and caches the model; subsequent runs load it from cache in under a second. Similarly, cache the keyword embeddings themselves so you only recompute when the input changes. This keeps the workflow under the free tier’s sixty-minute monthly limit even if you run it daily.
For solo marketers, the biggest win is not the money saved. It is the elimination of cognitive overhead. You stop thinking about which keywords belong to which bucket and start thinking about which clusters to target next. The tool becomes a second brain that reorganizes your keyword universe every night while you sleep. When you open your repo the next morning, a fresh cluster map waits for you, ready to inform your next piece of pillar content or internal linking strategy.
This approach also future-proofs your process. As your keyword set grows from hundreds to tens of thousands, you can swap the clustering algorithm for a more scalable one, or replace the local runner with a free Google Colab session triggered by a webhook. The architecture is modular. The core loop—data ingestion, embedding, clustering, output—remains the same. You never get locked into a proprietary tool because every component is open source and runs on infrastructure you control.
The only real investment is fifteen minutes to set up the repository, write the two script files, and configure the workflow YAML. That is time well spent for anyone who values their sanity and their keyword research equally. Stop grouping keywords by hand. Let the machines cluster; you cluster the strategy.


