← Back to Journal
· 8 min read

SERP Difficulty Scoring: How to Score Keyword Difficulty in Python Without Ahrefs

Ahrefs keyword difficulty is a DR-based black box that fails hardest exactly where it matters — brand keywords. Here's a four-layer SERP difficulty scoring model in Python: structure analysis, brand fingerprints, entry difficulty, and link budgets. Real commands, real output, real limitations.

TL;DR

SERP difficulty scoring beats DR-based keyword difficulty because it scores what actually ranks — the pages, not the domains. This article walks through a four-layer Python implementation: SERP structure scoring (page types, position weights, signal modifiers), brand keyword detection (triple fingerprint), entry difficulty (rescoring only the slots you can compete for), and link budgets (how many referring domains you actually need). All data from free tiers: Serper.dev SERP + RDAP domain age.

Every keyword difficulty tool sells you one number. Ahrefs KD. SEMrush KD%. They look authoritative. They’re also the most expensive blindfold in SEO.

Two failure modes get people burned:

Failure 1: DR blindness. Ahrefs KD is derived heavily from the referring domains of ranking pages. A DR-90 site’s blog post from 2019 with zero fresh links gets counted as a DR-90 wall. You skip keywords you could win.

Failure 2: brand keywords that look easy. The opposite. A brand query like “semrush pricing” returns the company’s own thin pricing pages — structurally weak-looking results. A naive structure score calls it trivially easy. You spend a month writing content that will never outrank the official page, because Google treats the query as navigational.

A useful difficulty score has to handle both. This is the four-layer SERP difficulty scoring model I run from the terminal — built on free data, and honest about where it’s wrong.

The Data Layer: What You’re Scoring

The inputs are deliberately boring:

  • SERP data — top 10 organic results for the keyword, via Serper.dev (2,500 free searches/month)
  • Domain age — registration year via RDAP (free, unmetered)
  • A known-platform list — reddit.com, youtube.com, medium.com, wikipedia.org and friends get fixed authority scores

One API call per keyword. No Ahrefs subscription.

From the SERP, each result gets classified by page type:

  • homepage — the root domain is ranking (someone’s whole site is pointed at this query)
  • dedicated — a page built specifically for this topic
  • inner — a page that happens to mention the topic (a blog post covering something adjacent, a forum thread)

This matters because of the 哥飞-style page-type discounting model: homepages count at full strength, dedicated pages at 65%, casual inner pages at 45%. A SERP full of inner pages is a door left open. A SERP full of homepages is a wall.

Layer 1: Structure Score — Base + Signal Modifiers

The core score is a position-weighted average of result strength. Top positions weigh more (position 1 counts 1.00, position 10 counts 0.37). Each result’s strength is authority × type multiplier × position weight, and the averaged SERP strength becomes the base score (max 60).

Then signal modifiers adjust it — homepages crowding, niche maturity, weak domains in the top 5:

SignalConditionModifier
homepage_crowded7+ homepages+18
homepage_heavy5+ homepages+10
no_homepages0 homepages-5
mature_niche5+ mature dedicated pages+14
established_niche3+ mature dedicated pages+7
brand_dominatedbrand sitelinks + 3+ platforms+12
old_domainsavg domain age 15yr++6
weak_top5weak domains in top 5up to -15
new_domain_proofany domain under 2yr old-5
top3_fortressall top-3 strong+6

Here’s a real run — best project management software, a keyword most people would assume is unwinnable:

python3 -m zens_ink.kd "best project management software" --markdown
# KD Analysis: best project management software

**Score: 13.3/100** — Very Easy (极易)
- Base: 20.3 / modifiers: -7
- Recommendation: 蓝海词,优先创建内容抢占先机

## Link Budget (estimate)

| Track | Low | Mid | High |
|---|---|---|---|
| Editorial (quality links) | 10 | 16 | 24 |
| Directory (easy links) | 34 | 56 | 84 |

## SERP Structure

- Homepages: 0/10 · Dedicated: 7/10 · Inner: 2/10
- Platforms: 2/10 · Avg domain age: 22.0yr
- Weak domains in top-5: 3 · New domains (<2yr): 0

## Signal Modifiers

- no_homepages: -5
- established_niche: +7
- old_domains: +6
- weak_top5: -15

## Top 10 Breakdown

| # | Domain | Type | Authority | Year |
|---|---|---|---|---|
| 1 | www.icagile.com | inner | 0.35 | — |
| 2 | project-management.com | dedicated | 0.66 | 2000 |
| 3 | www.reddit.com | inner | 0.90 | — |
| 4 | www.paymoapp.com | dedicated | 0.35 | — |
| 5 | www.wrike.com | dedicated | 0.35 | — |
...

Read what the SERP is actually saying: no homepages, three structurally weak domains sitting in the top 5, and Reddit ranking at position 3. The score drops to 13.3. Is “best project management software” really a blue-ocean keyword? Probably not entirely — hold that thought for the limitations section — but the structure genuinely shows more daylight than a DR-based tool would ever admit. The #1 result is an inner page of a certification body. That slot is contestable.

Layer 2: Brand Fingerprints — Catching the Trap Keywords

Layer 1 has a fatal flaw, and it’s failure mode 2 from the intro. Brand queries return the brand’s own pages, which look structurally weak. The score collapses. You march into a wall.

The fix is a triple-fingerprint brand check:

  • F1 official_domain — a same-name official domain sits in the top 3 (exact, prefix, or near-plural match)
  • F2 brand_dominance — one brand’s domain family occupies 2+ of the 10 slots
  • F3 platform_ecosystem — app store listings plus platform density, or 3+ platform pages

Two or more fingerprints = brand keyword. Direct ranking is flagged as nearly impossible, and the tool pivots to telling you what actually works: derivative queries.

Real run:

python3 -m zens_ink.kd "notion templates" --json
{
  "keyword": "notion templates",
  "kd": 43.4,
  "label_en": "Medium",
  "is_brand_keyword": true,
  "brand_fingerprints": ["official_domain", "brand_dominance", "platform_ecosystem"],
  "brand_domain": "notion.com",
  "domains": ["www.notion.com", "www.reddit.com", "www.notioneverything.com",
              "www.pinterest.com", "www.notion.com", "www.youtube.com"]
}

All three fingerprints fire. notion.com takes two slots directly, a template marketplace takes the eco-adjacent slot, and the platforms fill the rest. The output doesn’t just say “hard” — it says why (notion.com owns the intent) and what to do instead: notion templates alternative, notion vs [competitor], notion templates review.

The brand_dominated modifier in Layer 1 also uses this signal for non-brand queries. Compare notion vs obsidian — a comparison query where the brand pages don’t own the intent (forums, review sites, and Reddit rank): fingerprints don’t fire, brand flag stays false, and the keyword scores as a normal Easy opportunity with a brand_dominated: +12 nudge. Same brand names in the query, completely different competitive reality.

Layer 3: Entry Difficulty — The Number You Can Act On

When a brand keyword is detected, there’s a second score that matters more than the first: entry difficulty. The logic: strip out the brand’s own pages and the platform fixed slots (Reddit, YouTube will rank for everything; you will never displace them), then rescore only the competitive positions — the slots an outside site could realistically take.

The most instructive example I’ve found. Watch what happens with a brand query whose official pages look structurally helpless:

python3 -m zens_ink.kd "semrush pricing" --json
{
  "keyword": "semrush pricing",
  "kd": 1,
  "label_en": "Very Easy",
  "is_brand_keyword": true,
  "brand_fingerprints": ["official_domain", "brand_dominance"],
  "brand_domain": "semrush.com",
  "kd_entry": 3.7,
  "kd_entry_label_en": "Very Easy"
}

The naive structure score is 1 out of 100. One. Without the fingerprint layer, this is the most dangerous number a difficulty tool can print — semrush.com’s own pricing pages dominate the SERP, they’re thin dedicated pages, and a structure-only model reads them as pushovers. You would confidently build a page that never ranks.

The brand check catches it: two fingerprints, flagged. And then the entry score gives you the real read — the third-party slots (review sites, comparison pages answering “is semrush worth it”) are genuinely winnable at 3.7/100. The actionable conclusion isn’t “easy keyword” and isn’t “impossible keyword” — it’s “impossible head-on, easy from the side.” Target semrush pricing review or semrush vs [alternative] content, not a semrush pricing clone page.

This is why a single number was never enough. The same keyword legitimately has two difficulty scores, and which one applies depends on what page you’re planning to write.

A difficulty score tells you how hard. A link budget tells you how much. The final layer interpolates the score against the public Ahrefs KD→referring-domains curve for top-10 entry, with two tracks:

  • Editorial — links you earn with content worth linking to
  • Directory — easy links (listings, profiles), which the curve says need roughly 3.5x more volume for the same effect

Real run on a derivative keyword with a real budget:

python3 -m zens_ink.kd "shopify alternatives" --json
{
  "keyword": "shopify alternatives",
  "kd": 48.3,
  "label_en": "Medium",
  "brand_fingerprints": ["platform_ecosystem"],
  "link_budget": {
    "editorial": {"low": 79, "mid": 132, "high": 198},
    "directory": "≈3.5x editorial (easy links need more volume for the same effect)"
  },
  "domains": ["www.reddit.com", "www.reddit.com", "www.quora.com",
              "www.bigcommerce.com", "lovable.dev", "www.youtube.com",
              "www.salesforce.com", "blog.shift4shop.com", "www.forbes.com"]
}

Medium difficulty, one fingerprint (platforms, but no official-domain lock), and a concrete decision now replaces a vibes check: are you willing to build ~130 referring domains to enter this top 10? For an indie builder, that’s a no — and you just saved a quarter. For a funded team, it’s a Tuesday. The score ends the argument either way.

Where This Model Is Wrong

Intellectual honesty section, because every difficulty tool is wrong somewhere and most won’t tell you where:

  1. Authority is a proxy, not a measurement. There’s no backlink data. Authority comes from the known-platform list and domain age. Wrike scored 0.35 (“unknown”) in the first example because RDAP didn’t return a registration year — wrike.com is a major SaaS brand, and the model lowballed it. Treat sub-20 scores on commercial head terms with suspicion; the weakness detection can over-fire when RDAP gaps stack up.

  2. No sitelinks signal. Serper.dev’s organic results don’t return a sitelinks field, so brand detection uses domain-family occupancy as the proxy. It works (the semrush and notion cases above fired correctly), but a brand with a single strong official page and no family occupancy can slip through on F1 alone — which is why F1 needs a companion fingerprint to trigger the flag.

  3. Volume is optional and often zero. The Bing volume lookup returns no data for long-tail developer keywords, so volume modifiers frequently don’t apply. The score still works — it’s built on structure, not volume — but very-high-volume detection is best-effort.

  4. It’s still an estimate. The link budget says “estimates for planning, not a ranking guarantee” on every report because it’s true. Google ranks pages, not spreadsheets.

Fitting It Into a Workflow

Difficulty scoring is one station in a pipeline, not the whole train:

  1. Discover — mine long-tails from Google Autocomplete with keyword_research or Reddit blue-ocean mining
  2. Validate demand — check search volume via the Bing Webmaster API
  3. Score difficulty — this model (zens_ink.kd), which subcommands into structure score, brand check, entry score, and link budget
  4. Group winnerscluster qualified keywords into topical authority maps
  5. Check intent — make sure the SERP wants your content type at all (SERP analysis, search intent)

The whole chain runs on free tiers with zero dependencies:

pip install git+https://github.com/respectevery01/zens-ink-seo-package.git
export SERPER_API_KEY="your_key"

python3 -m zens_ink.kd "your keyword here" --markdown

The --markdown flag emits the self-contained report you saw above — designed to drop straight into an agent’s context or an archive. There’s also --zh for Chinese SERPs (gl=cn, hl=zh-CN), which matters because Baidu-era Chinese keyword tools and Google SERPs disagree wildly on what ranks.

The Point

Keyword difficulty stopped being a single number the moment brand queries and page-type structure entered the picture. “semrush pricing” is simultaneously a 1/100 and an impossible keyword — and both answers are correct for different pages you might write. A scoring model earns its keep by telling you which question to ask, not by hiding the ambiguity behind two decimal places of DR-derived confidence.

That’s the difference between a difficulty score and a difficulty decision.

FAQ

What is SERP difficulty scoring?

SERP difficulty scoring rates a keyword by analyzing the structure of its current top-10 search results — how many homepages vs dedicated pages vs inner pages rank, platform density, domain age, and weak spots — instead of averaging domain authority scores like Ahrefs KD. Because it scores the pages you'd actually compete against, it catches cases DR-based tools miss, like a DR-90 site's thin 2019 blog post occupying a weak slot.

How do you detect brand keywords in SERP analysis?

With a triple-fingerprint check: F1 — a same-name official domain in the top 3 results; F2 — one brand's domain family occupying 2+ slots; F3 — platform ecosystem density (app stores, brand community pages). Two or more fingerprints marks the keyword as a brand query where direct ranking is nearly impossible, and the tool switches to scoring derivative-entry difficulty (alternative/vs/review pages) instead.

How accurate is keyword difficulty without Ahrefs?

It's different, not less accurate. Ahrefs KD correlates with referring domains of ranking pages; SERP structure scoring measures whether the slots are structurally winnable. The main weaknesses are proxy authority (domain age + a known-platform list, no backlink data) and RDAP gaps that default unknown domains to a low authority score. Treat any difficulty number — paid or free — as a planning estimate, not a guarantee.

How much does it cost to run SERP-based keyword difficulty analysis?

One SERP API call per keyword. Serper.dev's free tier gives 2,500 searches/month, which scores ~2,500 keywords. Domain age via RDAP is free and unmetered. The whole pipeline runs at $0/month for typical indie keyword research volumes.

Want to run this analysis on your own site?

ZensInk Pro automates this pipeline. One command, from seed keywords to content plan.

Get Pro →