Home› Blog› SEO› How to Run a Content Gap Analysis on 5 Competitors
SEO

How to Run a Content Gap Analysis on 5 Competitors

Run a content gap analysis against five competitors in five steps, with real counts: 672 keywords cut to 95 pages, plus a tested script.

Vance Moore
Vance Moore
October 14, 2026
7 min read
1,499 words
How to Run a Content Gap Analysis on 5 Competitors
Quick answer. A content gap analysis finds topics your competitors rank for that you do not cover. Pick five competitors, export their top-10 keywords, drop off-topic and already-covered terms, group what is left by competitor URL, then rank the groups by demand. In our run, 672 keywords became 95 pages to study in about three hours.

A content gap analysis only helps if it ends in a short list of pages you will write. Most do not, because the raw output is thousands of keywords. We ran the process below on our own site against five competitors on 2026-10-11 and show the real count at every step, so you can compare yours with ours.

Five-step diagram: pick 5 domains, merge 672 top-10 keywords, filter to 231 open gaps, group into 95 URLs, choose 10 briefs
The counts in the first four boxes are measured from our run; the 10 briefs in the last box is the target.

Before you start

  • Time: about three hours. Skill: comfortable with spreadsheets; Python 3 helps.
  • Your data: a Search Console export of queries for the last three months (Performance, Queries, export).
  • Competitor data: a ranking-keyword export per competitor from any rank tool. We used the DataForSEO Labs ranked keywords endpoint (US, English); all five exports cost under one US dollar in API fees.
  • You will end with: ten ranked page briefs, each tied to a competitor page you can read.

Step 1: Pick five competitors by who ranks

Search the five to ten queries your buyers use and note which domains hold the top 10. Keep the five that appear most often. Expect uneven sizes: ours ranged from 461 to 8,176 ranking keywords, and that spread shapes every later count.

Bar chart of total ranking keywords: Single Grain 8,176, Directive 6,625, Siege Media 6,123, TripleDart 1,102, Animalz 461
Competitor sizes differ about 18 times, so compare top-10 keywords, not totals.

Expected result: five domains and the queries that surfaced them. Common mistake: choosing brands you admire instead of domains that share your results pages. Honest caveat: we picked our five as B2B SaaS content and SEO peers rather than from a results-page overlap run, so our numbers illustrate the method and do not prove our choice.

Step 2: Export and merge their top-10 keywords

Export every keyword where each competitor ranks in positions 1 to 10, with search volume and the ranking URL. Stack the five files into one table with a competitor column. Keep the URL, because Step 4 depends on it. Our merge held 679 rows and 672 unique keywords. Animalz had 222 top-10 keywords and Single Grain 25.

One limit applies. For four of our five, we pulled each domain’s 1,000 highest-volume keywords at any position, not every keyword, so large-site counts are floors. Single Grain’s 25 mostly reflects that cut.

Two charts: top-10 keyword counts per competitor, and 666 keywords shared by one competitor, 5 by two, 1 by three
Agreement is rare: only 6 of 672 keywords ranked top 10 for more than one competitor.

So do not require agreement. If you keep only terms several competitors share, you keep one keyword. Expected result: one CSV with five competitors. Common mistake: deduplicating by keyword and losing the URLs.

Step 3: Filter for fit and subtract what you cover

Raw competitor lists are mostly noise. Keep terms you could credibly serve, then remove anything you already rank or earn impressions for. This script runs Steps 3 and 4 on two CSV files: competitor_rankings.csv and our_queries.csv. We executed it on our data.

import csv, re, collections

FIT = re.compile(r"b(seo|content|b2b|saas|agency|agencies|abm|link building|backlink|geo|aeo)b")
SKIP = re.compile(r"b(new york|nyc|ny|chicago|miami|dallas|houston|boston|austin|atlanta|seattle|denver|london|near me)b")

rows = [r for r in csv.DictReader(open("competitor_rankings.csv", encoding="utf8")) if int(r["position"]) <= 10]
mine = {r["keyword"] for r in csv.DictReader(open("our_queries.csv", encoding="utf8"))}

unique = {r["keyword"] for r in rows}
fit = {k for k in unique if FIT.search(k) and not SKIP.search(k)}
open_kw = fit - mine

clusters = collections.defaultdict(list)
for r in rows:
    if r["keyword"] in open_kw:
        clusters[r["url"]].append((int(r["volume"]), r["keyword"]))

print("unique top-10 keywords:", len(unique))
print("pass fit filter:", len(fit), "| not already ours:", len(open_kw), "| competitor URLs:", len(clusters))
ranked = sorted(clusters.items(), key=lambda kv: -max(kv[1])[0])
for url, kws in ranked[:5]:
    top = max(kws)
    print(f"{top[0]:>6}  {len(kws):>2} kw  {top[1][:34]:<34} {url.split('//')[1][:60]}")

What this does: filters by topic words, removes city terms, subtracts exact-match queries you already have, and groups the rest by URL. Output from our run:

unique top-10 keywords: 672
pass fit filter: 234 | not already ours: 231 | competitor URLs: 95
  8100   4 kw  content creation firm              www.siegemedia.com/strategy/best-content-creation-services
  5400  14 kw  business to business marketing age www.siegemedia.com/strategy/best-b2b-content-marketing-agenc
  5400   2 kw  b2b marketing company              directiveconsulting.com/blog/the-best-b2b-marketing-agencies
  5400   4 kw  b2b marketing firm                 directiveconsulting.com/blog/top-25-b2b-enterprise-marketing
  4400   1 kw  internet marketing agency          directiveconsulting.com/blog/18-top-internet-marketing-agenc

Common error: a UnicodeDecodeError on Windows means the file was opened without encoding="utf8".

Bar chart: 672 merged keywords, 234 pass the fit filter, 231 not already ours, sitting on 95 competitor URLs
The fit filter removed 438 of 672 keywords (65%); only 3 of the 234 were exact matches for our Search Console queries.

Expected result: well under half of the raw list survives. Common mistake: trusting the topic words blindly. Read 30 random survivors and tighten the word list. Matching is exact, so near variants slip through.

Step 4: Group the gaps by competitor URL

One page often ranks for many variants, so each URL is one topic to judge, not many. Our 231 keywords sit on 95 URLs. Fifty-four URLs rank for a single keyword. Twenty-one URLs carry 140 of 238 keyword rows (59%), so read those first.

Column chart of 95 competitor URLs by gap keywords per URL: 54 have one, 16 have two, 21 have four or more
21 of 95 URLs hold 59% of the gap keyword rows.
Largest gap clusters by search volume (volume is the maximum on the URL, not a sum)
Competitor URL Gap keywords Top keyword Monthly searches
siegemedia.com/strategy/best-content-creation-services 4 content creation firm 8,100
siegemedia.com/strategy/best-b2b-content-marketing-agencies 14 business to business marketing agency 5,400
directiveconsulting.com/blog/the-best-b2b-marketing-agencies-for-technology-companies-in-2026 2 b2b marketing company 5,400
directiveconsulting.com/blog/top-25-b2b-enterprise-marketing-agencies 4 b2b marketing firm 5,400

Source: DataForSEO Labs ranked keywords, US, 2026-10-11, n = 95 URLs.

Expected result: a few dozen groups. Common mistake: adding volumes across variants. They share the same searchers, so use the largest number.

Step 5: Score the groups and pick ten

Score each group on three columns: demand (largest monthly volume), breadth (keywords on the URL) and fit (can you add something the competitor page lacks, such as first-party data or a format they skip). The first two come from the data. Fit is your judgment.

Scatter chart of 95 competitor URLs: gap keywords on the URL against maximum monthly searches, with three labeled outliers
The highest-volume page has 4 keywords; the broadest pages have 14 but very different volume.

Worked example, measured: a Siege Media agency list ranks top 10 for six technical SEO variants (services, service, agency, company, consultants, companies), with 2,400 monthly searches at most. None of the six is an exact query in our Search Console export. We do publish a technical SEO guide for SaaS, yet the six terms are missing from our rankings. That is one gap, and it is an intent mismatch: a guide against a ranked agency list.

Common mistake: picking by volume alone. Our biggest cluster is a ranked list of content agencies, a format where an agency ranking itself has a conflict of interest. Judge format fit before volume.

What did our own gap analysis show?

Bar chart of 231 open gap keywords by bucket: agency and service terms 125, other 45, content topics 36, link building 13, technical SEO 8, definitions 4
Agency and service terms make up 54% of the open gap; definition-style queries are 4 of 231.

Over 90 days to 2026-10-08, our site had 15,337 queries and 236,756 impressions in Search Console, with 59 clicks. We are not short of queries. We are short of queries that match the pages competitors win with, mostly ranked lists and service pages. That is a finding about these five domains on one date, and it is not a forecast.

Troubleshooting

Common problems and fixes
Symptom Likely cause Fix
Survivors include off-topic terms Topic word list too broad Add a skip list and read 30 random rows
Almost no overlap between competitors Normal (666 of 672 were unique to one) Do not filter on agreement
A competitor shows very few top-10 rows Export capped by volume Export by position or paginate
Gap list includes topics you cover Exact-match comparison Also check Search Console by page

Checklist

  • Five domains chosen from shared results pages.
  • Top-10 keywords exported with URLs and volume.
  • Fit filter applied, 30 survivors read by hand.
  • Your Search Console queries subtracted.
  • Gaps grouped by competitor URL.
  • Ten groups scored on demand, breadth and fit.

FAQ

How many competitors should a content gap analysis include?

Five keeps the merge readable. In our data, 666 of 672 keywords belonged to a single competitor, so more domains add reading, not agreement.

How often should I repeat it?

We rerun ours quarterly, and after each batch of new pages ships. That is our practice, not a tested benchmark.

Do I need a paid tool?

No. A Search Console export and any ranking-keyword export work. Ours cost under one US dollar in API fees, plus about three hours. For a related method, see our Search Console API workflow and what AI competitor analysis uncovers.

Disclosure: MV3 Marketing sells SEO and content services, so we benefit if you hire us for this work. To have us run it, book a call.

Vance Moore
Vance Moore LinkedIn
Chief Growth Officer, MV3 Marketing

Vance Moore is Chief Growth Officer at MV3 Marketing and leads its team of SEO professionals, engineers and auditors.

Ready to audit your organic growth opportunity?

$2,500 flat. 5 business days. Six deliverables tied to pipeline , not rankings. No retainer required.

Get the Organic Growth Audit →

Turn Your Organic Channel into a Revenue Engine.

The MV3 SEO Audit maps your full organic opportunity in 5 business days.

Get the Audit →