Last modified: Oct 24, 2026
Named Entity Recognition with spaCy
Named Entity Recognition (NER) is a core task in Natural Language Processing. It finds and labels real-world objects inside text. These objects include people, places, dates, and companies.
spaCy is a fast and popular Python library for NLP. It ships with ready-to-use NER models. You can extract entities in just a few lines of code.
This guide walks you through the full process. You will install spaCy, load a model, extract entities, and visualize results. You will also learn how to customize the pipeline for your own data.
What Is Named Entity Recognition?
NER scans a sentence and marks spans of text as specific categories. A span is a continuous piece of text. Each span gets a label like PERSON, ORG, or GPE.
Consider the sentence "Apple opened a store in Berlin last year." A good NER model returns "Apple" as an organization and "Berlin" as a geopolitical entity.
This is useful in many domains. Search engines use NER to understand queries. News platforms use it to tag articles. Data teams use it to pull structured facts from messy text.
Why Use spaCy for NER?
spaCy is built for production use. It is fast, memory-efficient, and well documented. Its models are trained on large corpora and perform well out of the box.
The library offers a clean API. You process text once, then read entities, tokens, and other annotations from the same document object. There is no need to run the model again.
spaCy also supports custom training. If your domain has special entities, you can fine-tune a model on your own labeled data.
Install spaCy and Download a Model
Start by installing the library with pip. Then download the small English model, which is ideal for learning and quick tests.
pip install spacy
python -m spacy download en_core_web_sm
The model name follows a pattern. en is the language, web is the training source, and sm means small. Larger models like en_core_web_md and en_core_web_lg are more accurate but slower.
If you already work with Python packages, the setup is similar to other libraries. For example, you may have seen steps to install protobuf in Python before. The pattern is the same: install, download assets, then import.
Load a Model and Process Text
Loading a model is simple. The spacy.load function returns a pipeline object. This object holds the tokenizer, tagger, parser, and NER component.
import spacy
# Load the small English pipeline
nlp = spacy.load("en_core_web_sm")
# Process a sentence
doc = nlp("Tim Cook visited Paris to meet the team at Apple.")
# Print each entity and its label
for ent in doc.ents:
print(ent.text, ent.label_)
Tim Cook PERSON
Paris GPE
Apple ORG
Each item in doc.ents is a span. It has a text value and a label. The label comes from the model's entity scheme.
Understand the Common Entity Labels
spaCy models use a fixed set of labels. Here are the ones you will see most often.
PERSON covers people, including fictional ones. ORG covers companies, agencies, and institutions. GPE covers countries, cities, and states.
LOC covers non-GPE locations like mountains and rivers. DATE and TIME cover temporal expressions. MONEY and PERCENT cover numeric values.
You can list all labels for a model at runtime. This helps when you build validation rules.
import spacy
nlp = spacy.load("en_core_web_sm")
# Show every label the NER component knows
print(nlp.get_pipe("ner").labels)
('CARDINAL', 'DATE', 'EVENT', 'FAC', 'GPE', 'LANGUAGE', 'LAW', 'LOC', 'MONEY', 'NORP', 'ORDINAL', 'ORG', 'PERCENT', 'PERSON', 'PRODUCT', 'QUANTITY', 'TIME', 'WORK_OF_ART')
Access Extra Entity Details
Entities carry more than text and label. You can read character offsets and the underlying token span. This matters when you map entities back to the source text.
import spacy
nlp = spacy.load("en_core_web_sm")
doc = nlp("Apple was founded in 1976 by Steve Jobs.")
for ent in doc.ents:
# Start and end character positions in the original text
print(ent.text, ent.label_, ent.start_char, ent.end_char)
Apple ORG 0 5
1976 DATE 20 24
Steve Jobs PERSON 28 38
These offsets are useful for highlighting text in a UI. They also help when you store entities in a database and need to link them to source documents.
Visualize Entities with displaCy
spaCy includes a built-in visualizer called displaCy. It renders entities as colored highlights. This is great for debugging and for demos.
import spacy
from spacy import displacy
nlp = spacy.load("en_core_web_sm")
doc = nlp("Microsoft hired Satya Nadella in Hyderabad in 2014.")
# Render entities in a browser or notebook
displacy.serve(doc, style="ent")
The displacy.serve function starts a local web server. Open the printed URL to see the highlighted text. In Jupyter, use displacy.render with jupyter=True instead.
Process Many Documents Efficiently
Calling the pipeline in a loop works, but it is not the fastest way. The nlp.pipe method batches documents and uses multiple processes. This is the recommended approach for large datasets.
import spacy
nlp = spacy.load("en_core_web_sm")
texts = [
"Barack Obama spoke in Chicago.",
"Tesla opened a factory in Berlin.",
"The meeting is set for Monday in London.",
]
# Batch processing for better throughput
for doc in nlp.pipe(texts, batch_size=64):
print([(ent.text, ent.label_) for ent in doc.ents])
[('Barack Obama', 'PERSON'), ('Chicago', 'GPE')]
[('Tesla', 'ORG'), ('Berlin', 'GPE')]
[('Monday', 'DATE'), ('London', 'GPE')]
Batching reduces overhead. When you stream results to another system, you can also combine this with message queues. For example, you can produce and consume Kafka messages in Python to pass entity results between services.
Disable Unused Components for Speed
You often only need NER. The parser and tagger add work you may not require. You can disable them at load time to speed up processing.
import spacy
# Load only the components needed for NER
nlp = spacy.load("en_core_web_sm", disable=["parser", "lemmatizer"])
doc = nlp("Amazon opened a new office in Tokyo.")
print([(ent.text, ent.label_) for ent in doc.ents])
[('Amazon', 'ORG'), ('Tokyo', 'GPE')]
This small change can cut processing time significantly. It is a simple win for high-volume pipelines.
Add Custom Rules with EntityRuler
Statistical models miss domain-specific names. Product codes, internal team names, and ticker symbols are common examples. The EntityRuler component lets you add pattern-based rules.
import spacy
nlp = spacy.load("en_core_web_sm")
# Create a rule-based component
ruler = nlp.add_pipe("entity_ruler", before="ner")
# Define patterns for custom entities
patterns = [
{"label": "PRODUCT", "pattern": "CloudSync Pro"},
{"label": "PRODUCT", "pattern": [{"LOWER": "data"}, {"LOWER": "hub"}]},
]
ruler.add_patterns(patterns)
doc = nlp("We deployed CloudSync Pro and the Data Hub last week.")
print([(ent.text, ent.label_) for ent in doc.ents])
[('CloudSync Pro', 'PRODUCT'), ('Data Hub', 'PRODUCT'), ('last week', 'DATE')]
The before="ner" argument matters. It ensures your rules run first, so the statistical model does not override them.
Train a Custom NER Model
Rules have limits. When you have enough labeled data, training a model gives better recall. spaCy supports this through its training config system.
You need examples in the form of text and entity spans. Each span includes start and end character positions. Then you convert the data to spaCy's binary format and run the training command.
import spacy
from spacy.training import Example
nlp = spacy.blank("en")
ner = nlp.add_pipe("ner")
ner.add_label("PRODUCT")
# One training example with character offsets
train_data = [
("I love CloudSync Pro.", {"entities": [(7, 20, "PRODUCT")]}),
]
# Convert raw data into Example objects
examples = []
for text, annots in train_data:
doc = nlp.make_doc(text)
examples.append(Example.from_dict(doc, annots))
optimizer = nlp.initialize()
for i in range(20):
losses = {}
nlp.update(examples, sgd=optimizer, losses=losses)
print(i, losses)
This is a minimal loop for learning. For real projects, use the spacy train command with a config file. It handles evaluation, checkpoints, and best-model selection.
Training data quality matters more than model size. Keep annotations consistent and review them often.
Common Pitfalls and Tips
NER models are not perfect. They can split names or miss rare entities. Always test on real text from your domain before shipping.
Watch for overlapping rules. If two patterns match the same span, spaCy resolves conflicts by priority. Test carefully when you combine the ruler and the statistical model.
Keep text clean but do not over-clean. Removing punctuation can break offsets and confuse the tokenizer.
Store results in a structured format. JSON lines work well for large batches. If your pipeline feeds a database, you can later query Oracle Database from Python to join entities with business records.
Putting It All Together
Here is a compact script that combines the main ideas. It loads a model, adds rules, processes text in batches, and prints structured results.
import spacy
# 1. Load a fast pipeline with only needed components
nlp = spacy.load("en_core_web_sm", disable=["parser", "lemmatizer"])
# 2. Add custom rules before the statistical NER
ruler = nlp.add_pipe("entity_ruler", before="ner")
ruler.add_patterns([
{"label": "PRODUCT", "pattern": "CloudSync Pro"},
])
# 3. Process multiple documents in batches
texts = [
"Maria joined Acme Corp in Madrid.",
"We shipped CloudSync Pro to Berlin.",
]
for doc in nlp.pipe(texts, batch_size=32):
# 4. Print simple JSON-friendly output
print([{"text": e.text, "label": e.label_} for e in doc.ents])
[{'text': 'Maria', 'label': 'PERSON'}, {'text': 'Acme Corp', 'label': 'ORG'}, {'text': 'Madrid', 'label': 'GPE'}]
[{'text': 'CloudSync Pro', 'label': 'PRODUCT'}, {'text': 'Berlin', 'label': 'GPE'}]
This pattern scales well. You can wrap it in a service and feed results to other systems. Many teams send enriched text to a broker, and you can install kafka-python in Python to connect your NER service to a stream.
Conclusion
Named Entity Recognition with spaCy is fast to start and easy to extend. You load a model, process text, and read labeled spans in a few lines.
Start with the small model and built-in labels. Add the EntityRuler for domain terms. Move to custom training when you have labeled data and need better accuracy.
Batch your documents, disable unused components, and validate on real text. With these habits, you can turn raw text into clean, structured entities that power search, analytics, and automation.