Entity SEO Automation: Extract and Optimize Entities with Google NLP and spaCy (Python)

Most SEO automation stops at keywords. You harvest queries, cluster them, and map them to pages — but Google hasn’t ranked purely on keyword strings for years. It ranks on entities: the people, places, products, organizations, and concepts it recognizes and connects inside its Knowledge Graph. If your content mentions “Ozempic” but never establishes its relationship to semaglutide, GLP-1 agonists, or Novo Nordisk, you’re leaving topical authority on the table that a competitor’s page is quietly claiming.

This guide builds a Python pipeline that extracts entities from your page and from the pages currently ranking for your target query, then computes an entity gap you can act on. We’ll combine spaCy for fast local extraction with Google’s Cloud Natural Language API for Knowledge-Graph-linked entities and salience scores — and, crucially, we’ll talk about where this approach quietly goes wrong.

Why entities beat keywords for topical coverage

Keyword tools tell you what people type. Entity analysis tells you what a topic is made of. Those are different problems. A page about “home espresso” can rank for hundreds of keyword variations, but Google’s confidence that the page genuinely covers the topic depends on whether it surfaces the entities a knowledgeable author would naturally include: portafilter, tamping pressure, extraction time, burr grinder, crema, PID temperature control.

Two properties make entities worth automating around:

  • They’re language-independent identifiers. Google Cloud NLP returns a Knowledge Graph MID (machine ID) for recognized entities. “USA,” “United States,” and “Estados Unidos” collapse to the same MID, so you can dedupe across phrasings and even languages.
  • They come with salience. Salience is a 0–1 score estimating how central an entity is to the document. This lets you distinguish a page that is about an entity from one that merely mentions it in passing.

That salience signal is the piece pure keyword workflows can’t give you, and it’s what turns an entity list into a prioritized action plan.

spaCy vs Google Cloud NLP: use both, for different jobs

People treat these as competitors. They’re complements, and the pipeline is stronger when you run them in sequence.

spaCy: fast, free, local, no Knowledge Graph

spaCy’s named-entity recognition is excellent for high-volume, zero-cost first passes. You can run it across thousands of URLs without an API bill. Its limitation is that it classifies entities into generic types (PERSON, ORG, GPE, PRODUCT) but does not link them to a canonical knowledge base or score salience.

import spacy

nlp = spacy.load("en_core_web_lg")

def spacy_entities(text: str) -> dict:
    doc = nlp(text)
    counts = {}
    for ent in doc.ents:
        key = (ent.text.strip().lower(), ent.label_)
        counts[key] = counts.get(key, 0) + 1
    return counts

Google Cloud NLP: Knowledge Graph MIDs and salience

When you need canonical identity and centrality, call the Cloud NLP analyzeEntities method. It returns the entity name, type, a salience score, and — for recognized entities — a metadata.mid that ties back to the Knowledge Graph.

from google.cloud import language_v1

client = language_v1.LanguageServiceClient()

def google_entities(text: str) -> list:
    document = language_v1.Document(
        content=text,
        type_=language_v1.Document.Type.PLAIN_TEXT,
    )
    response = client.analyze_entities(
        request={"document": document,
                 "encoding_type": language_v1.EncodingType.UTF8}
    )
    out = []
    for e in response.entities:
        out.append({
            "name": e.name,
            "type": language_v1.Entity.Type(e.type_).name,
            "salience": round(e.salience, 4),
            "mid": e.metadata.get("mid"),
            "wikipedia": e.metadata.get("wikipedia_url"),
        })
    return out

A practical strategy: run spaCy across your whole site to find candidate pages cheaply, then spend Cloud NLP calls only on the pages and SERP competitors that matter. This keeps costs bounded while still getting salience where it counts.

Building the entity gap: your page vs the SERP

The real value isn’t extracting your own entities — it’s comparing them to what already ranks. The workflow: pull the top-ranking URLs for a query, extract their entities, aggregate, and subtract what your page already covers.

Step 1 — Aggregate competitor entities weighted by salience

Don’t just count how many competitors mention an entity. Weight each mention by its salience on that competitor’s page, so a term that’s central to three ranking pages outranks one that’s merely name-dropped on eight.

from collections import defaultdict

def aggregate_serp(entity_lists: list) -> dict:
    scores = defaultdict(float)
    doc_freq = defaultdict(int)
    for ents in entity_lists:
        seen = set()
        for e in ents:
            key = e["mid"] or e["name"].lower()
            scores[key] += e["salience"]
            if key not in seen:
                doc_freq[key] += 1
                seen.add(key)
    return {
        k: {"weight": round(scores[k], 4), "docs": doc_freq[k]}
        for k in scores
    }

Step 2 — Subtract what you already cover

def entity_gap(serp_agg: dict, my_entities: list, min_docs: int = 2):
    mine = {e["mid"] or e["name"].lower() for e in my_entities}
    gaps = []
    for key, stats in serp_agg.items():
        if key in mine:
            continue
        if stats["docs"] < min_docs:
            continue  # ignore idiosyncratic one-off mentions
        gaps.append((key, stats["weight"], stats["docs"]))
    return sorted(gaps, key=lambda x: (x[2], x[1]), reverse=True)

The min_docs threshold is doing quiet but important work: it filters out entities that only one competitor happens to mention, which are usually noise (a brand name in a sidebar, an author’s affiliation) rather than genuine topic requirements. Requiring an entity to appear centrally on at least two ranking pages is a reasonable default; raise it for competitive SERPs.

Turning the gap into content decisions

A ranked gap list is not a to-do list yet. Three filters turn it into one:

  • Relevance sanity check. SERP entity extraction picks up navigation, cookie notices, and related-article widgets. Strip boilerplate before extraction (use a readability library to isolate main content), or you’ll “discover” that every competitor covers the entity “Privacy Policy.”
  • Intent alignment. An entity that’s central to competitors serving a different intent than yours is a trap. If you’re writing a buyer’s guide and the gap is dominated by entities from tutorial pages, you may be looking at the wrong SERP cluster entirely — a sign to revisit your keyword clustering first.
  • Existing coverage depth. “Missing” is binary; reality isn’t. An entity you mention once may still need expansion. Cross-reference salience on your own page: entities present but below, say, 0.01 salience are candidates for reinforcement, not just the truly absent ones.

Feed the surviving entities into your content brief as required-coverage items. This slots neatly beside a SERP-reading brief pipeline: entities answer “what concepts must appear,” while the brief answers “how should they be structured.” And because entity coverage is a component of perceived expertise, the gap list doubles as an input to an LLM content-quality scorer that estimates E-E-A-T before you publish.

Where entity optimization goes wrong

The failure mode is predictable because it rhymes with every SEO tactic before it: people turn a signal into a stuffing target. A few guardrails:

  • Salience is contextual, not a quota. You cannot force an entity to be salient by repeating it. Salience rises when an entity is genuinely structured into the document — headings, definitions, relationships to other entities. Mechanical insertion produces the opposite of what you want.
  • Entity presence is necessary, not sufficient. Covering every competitor entity guarantees nothing if the coverage is shallow. Treat the gap as a floor for completeness, then compete on depth, accuracy, and firsthand experience — the things a scraper can’t hand you.
  • Don’t optimize toward a degrading SERP. If the pages ranking today are thin, their aggregated entities encode thinness. Spot-check the sources; a gap derived from weak competitors is a weak gap.

Used honestly, entity extraction is one of the few automation layers that pushes you toward more substantive content rather than less — it tells you which concepts a real expert would cover, and leaves the actual expertise to you. Pair it with your keyword research pipeline and you’ve closed the loop from “what people search” to “what the topic requires.”

Frequently asked questions

Do I need Google Cloud NLP, or is spaCy enough?

spaCy alone is enough for coarse entity discovery and de-duplication across a large site at zero cost, but it can’t link entities to the Knowledge Graph or score salience. If your goal is a prioritized entity gap — ranking which concepts matter most — you need the salience and MID metadata that Cloud NLP provides. The cost-effective pattern is spaCy for breadth, Cloud NLP for the pages that matter.

What is entity salience and why does it matter for SEO?

Salience is a 0–1 score from Google’s NLP API estimating how central an entity is to a document, based on its position, frequency, and syntactic role. It matters because it separates pages that are genuinely about a topic from pages that merely mention it. In an entity gap analysis, salience lets you weight competitor coverage by centrality instead of raw counts.

Can I over-optimize by adding too many entities?

Yes. Mechanically inserting entities to hit a coverage checklist produces shallow, awkward content that neither readers nor Google reward, and it can’t raise salience the way genuine structural coverage does. Use the entity gap as a completeness floor, not a stuffing target, and always prioritize depth and accuracy over checklist completion.

How many competitor URLs should I analyze per query?

The top 5–10 organic results for the specific intent you’re targeting is usually sufficient. Beyond that you introduce noise from pages serving different intents. Always strip boilerplate (navigation, cookie banners, related-post widgets) before extraction, and require an entity to appear centrally on at least two pages before treating it as a real requirement.

Similar Posts

Leave a Reply

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