| name | spacy-nltk |
| description | Natural Language Processing for text analysis, corpus linguistics, and production NLP pipelines. spaCy provides fast production-grade tokenization, POS tagging, NER, dependency parsing, and custom model training. NLTK provides classical corpus linguistics, linguistic analysis, VADER sentiment, collocation analysis, and access to standard linguistic corpora. Use when: processing and analyzing text data, extracting named entities (people, orgs, locations, dates), dependency parsing and syntactic analysis, building text classification pipelines, performing corpus-level linguistic analysis (frequency, collocations, readability), sentiment analysis, lemmatization and stemming, working with multilingual text, training custom NER or text classifiers, or any task requiring structured understanding of natural language beyond simple string operations. |
spaCy & NLTK — Natural Language Processing
Two complementary libraries that together cover the full NLP stack. spaCy is the production engine: fast, opinionated, pipeline-based. NLTK is the linguistics toolkit: deep, flexible, corpus-rich. Use them together — spaCy for processing, NLTK for analysis.
Library Decision Matrix
USE spaCy WHEN: USE NLTK WHEN:
───────────────────────────── ─────────────────────────────
Processing speed matters Linguistic analysis depth matters
Named Entity Recognition (NER) Corpus linguistics (collocations, freq)
Dependency parsing Working with standard corpora (Brown, Penn)
Production pipelines VADER sentiment analysis
Custom model training Stemming (Porter, Snowball)
Multilingual text POS tag evaluation against gold sets
Document/entity similarity Readability metrics
Pipeline: tokenize→tag→parse→NER Classical grammar analysis
Embedding-based operations Educational / research NLP
Reference Documentation
spaCy docs: https://spacy.io/usage
spaCy API: https://spacy.io/api
NLTK docs: https://www.nltk.org/
NLTK book: https://www.nltk.org/book/
Search patterns: spacy.load, nlp(text), doc.ents, nltk.word_tokenize, FreqDist
Core Principles
spaCy: Pipeline Architecture
spacy.load() returns an nlp object — a processing pipeline. Calling nlp(text) runs the full pipeline (tokenizer → tagger → parser → NER) and returns a Doc. Everything — tokens, sentences, entities, syntax — is extracted in a single pass. This is the key performance advantage.
NLTK: Function-Based Analysis
NLTK works at the function level. You call individual functions: word_tokenize(), pos_tag(), FreqDist(). No shared state or pipeline. More granular control; less automatic.
Linguistic Hierarchy
Text → Sentences → Tokens → Morphemes. Both libraries operate at these levels but with different strengths: spaCy owns the token→entity→syntax layer; NLTK owns the corpus→frequency→collocation layer.
spaCy Models Are Language-Specific
en_core_web_sm is English small. de_core_news_md is German medium. Model size affects accuracy: sm (speed) → md (balanced) → lg (accuracy + word vectors). Download before use: python -m spacy download en_core_web_sm.
Quick Reference
Installation
pip install spacy nltk
python -m spacy download en_core_web_sm
python -m spacy download en_core_web_md
python -m spacy download en_core_web_lg
import nltk
nltk.download('punkt_tab')
nltk.download('averaged_perceptron_tagger_eng')
nltk.download('stopwords')
nltk.download('vader_lexicon')
nltk.download('brown')
nltk.download('wordnet')
Standard Imports
import spacy
import nltk
from collections import Counter
import pandas as pd
Basic Pattern — spaCy Full Pipeline
import spacy
nlp = spacy.load('en_core_web_sm')
doc = nlp("Apple is headquartered in Cupertino, California. Tim Cook is the CEO.")
for token in doc:
print(f"{token.text:15s} POS={token.pos_:6s} lemma={token.lemma_:15s} dep={token.dep_}")
print("\nEntities:")
for ent in doc.ents:
print(f" {ent.text:20s} → {ent.label_}")
print("\nSentences:")
for sent in doc.sents:
print(f" {sent.text}")
Basic Pattern — NLTK Linguistic Analysis
import nltk
from nltk.tokenize import word_tokenize
from nltk.tag import pos_tag
from nltk.probability import FreqDist
from nltk.corpus import stopwords
text = "Apple is headquartered in Cupertino, California. Tim Cook is the CEO."
tokens = word_tokenize(text)
tagged = pos_tag(tokens)
print("Tagged:", tagged)
words = [t for t in tokens if t.isalpha()]
stops = set(stopwords.words('english'))
cleaned = [w.lower() for w in words if w.lower() not in stops]
freq = FreqDist(cleaned)
freq.most_common(10)
Critical Rules
✅ DO
- Load spaCy model ONCE, reuse
nlp object — spacy.load() is expensive. Load at module level, call nlp() per document.
- Use
doc.ents for NER, never regex first — spaCy's statistical NER outperforms regex for ambiguous entities. Use regex only as fallback for structured patterns (emails, phone numbers).
- Use
token.lemma_ over token.text — Lemmatization normalizes "running"→"run", "better"→"good". Essential before frequency analysis.
- Use
nlp.pipe() for batch processing — Processes documents in parallel. 10–100x faster than looping nlp() per document.
- Disable unused pipeline components —
nlp.select_pipes(disable=['parser']) if you only need NER. Significant speedup.
- Use
token.is_stop, token.is_punct, token.is_alpha — spaCy's built-in filters. Cleaner than manual regex.
- Use NLTK's
FreqDist and collocations for corpus-level analysis — Built for this exact purpose.
- Prefer
md or lg models for similarity tasks — sm models have no word vectors; .similarity() will fail or return meaningless scores.
❌ DON'T
- Don't reload
nlp inside loops — Catastrophic performance. Load once.
- Don't use spaCy
sm models for .similarity() — No word vectors in small models. Returns 0 or garbage.
- Don't mix spaCy and NLTK tokenizers on the same text — Different tokenization rules produce different token boundaries. Pick one per pipeline.
- Don't assume
doc.ents covers everything — NER recall is ~80–90%. Post-process with rules for domain-specific entities.
- Don't use
nltk.word_tokenize in production loops — Slow per-call overhead. Use spaCy for throughput.
- Don't skip model validation — Always check entity/POS accuracy on a sample of your domain before trusting at scale.
Anti-Patterns (NEVER)
import spacy
results = []
for text in documents:
nlp = spacy.load('en_core_web_sm')
doc = nlp(text)
results.append(doc.ents)
nlp = spacy.load('en_core_web_sm')
results = [list(nlp(text).ents) for text in documents]
nlp = spacy.load('en_core_web_sm')
doc1 = nlp("machine learning")
doc2 = nlp("deep learning")
print(doc1.similarity(doc2))
nlp = spacy.load('en_core_web_md')
doc1 = nlp("machine learning")
doc2 = nlp("deep learning")
print(doc1.similarity(doc2))
results = []
for text in documents:
doc = nlp(text)
results.append(doc)
results = list(nlp.pipe(documents, batch_size=64, n_process=4))
import re
entities = re.findall(r'\b[A-Z][a-z]+\b', text)
doc = nlp(text)
entities = [(ent.text, ent.label_) ent doc.ents]
spaCy — Core Operations
Token Properties
import spacy
nlp = spacy.load('en_core_web_sm')
doc = nlp("The quick brown fox jumped over 3 lazy dogs in 2024.")
for token in doc:
print(
f"text={token.text:12s} "
f"lemma={token.lemma_:12s} "
f"pos={token.pos_:6s} "
f"tag={token.tag_:6s} "
f"dep={token.dep_:8s} "
f"stop={token.is_stop} "
f"punct={token.is_punct} "
f"alpha={token.is_alpha} "
f"num={token.is_digit}"
)
Named Entity Recognition
import spacy
nlp = spacy.load('en_core_web_sm')
text = """
Google acquired Waze in 2013 for approximately $1.15 billion.
The deal was announced by Sundar Pichai in Mountain View, California.
"""
doc = nlp(text)
for ent in doc.ents:
print(f"{ent.text:25s} → {ent.label_:10s} (start={ent.start_char}, end={ent.end_char})")
people = [ent.text for ent in doc.ents if ent.label_ == 'PERSON']
orgs = [ent.text for ent in doc.ents if ent.label_ == 'ORG']
money = [ent.text for ent in doc.ents if ent.label_ == 'MONEY']
for ent in doc.ents:
start = max(0, ent.start - 3)
end = min(len(doc), ent.end + )
context = doc[start:end].text
()
Dependency Parsing
import spacy
nlp = spacy.load('en_core_web_sm')
doc = nlp("The CEO of Apple announced a new product.")
for token in doc:
print(f"{token.text:12s} ← dep={token.dep_:10s} ← head={token.head.text}")
def extract_svo(doc):
"""Extract (subject, verb, object) triples from dependency parse."""
triples = []
for token in doc:
if token.pos_ == 'VERB':
subj = [t.text for t in token.children if t.dep_ in ('nsubj', 'nsubjpass')]
obj = [t.text for t in token.children if t.dep_ in ('dobj', 'attr', 'pobj')]
if subj and obj:
triples.append((subj[0], token.lemma_, obj[0]))
return triples
doc = nlp("John likes pizza. Mary wrote a paper. The company acquired a startup.")
for s, v, o in extract_svo(doc):
print(f" {s} —[{v}]→ {o}")
Batch Processing and Performance
import spacy
import time
nlp = spacy.load('en_core_web_sm')
documents = ["Text one.", "Text two.", "Text three."] * 1000
docs = list(nlp.pipe(documents, batch_size=64, n_process=4))
with nlp.select_pipes(disable=['parser']):
docs = list(nlp.pipe(documents, batch_size=128))
start = time.time()
docs = list(nlp.pipe(documents, batch_size=64))
elapsed = time.time() - start
print(f"Processed {len(documents)} docs in {elapsed:.2f}s "
f"({len(documents)/elapsed:.0f} docs/sec)")
NLTK — Corpus Linguistics
Corpus Analysis
import nltk
from nltk.corpus import brown, gutenberg
from nltk.probability import FreqDist
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
import pandas as pd
stops = set(stopwords.words('english'))
genre_freq = {}
for genre in brown.fileids()[:5]:
words = [w.lower() for w in brown.words(genre) if w.isalpha() and w.lower() not in stops]
genre_freq[genre] = FreqDist(words)
for genre, freq in genre_freq.items():
print(f"\n{genre}:")
print(f" {freq.most_common(10)}")
for name in gutenberg.fileids():
words = gutenberg.words(name)
sents = gutenberg.sents(name)
print(f"{name:35s} words={len(words):>8,} sents= "
)
Collocation Analysis
import nltk
from nltk.collocations import BigramCollocationFinder, TrigramCollocationFinder
from nltk.collocations import BigramAssocMeasures, TrigramAssocMeasures
text = """Machine learning is a subset of artificial intelligence that gives systems
the ability to learn from data. Deep learning is a subset of machine learning
that uses neural networks."""
words = nltk.word_tokenize(text.lower())
bi_finder = BigramCollocationFinder.from_words(words)
bi_colocs = bi_finder.nbest(BigramAssocMeasures.pmi, 10)
print("Top bigram collocations (PMI):")
for pair in bi_colocs:
print(f" {pair[0]:15s} {pair[1]}")
tri_finder = TrigramCollocationFinder.from_words(words)
tri_colocs = tri_finder.nbest(TrigramAssocMeasures.pmi, 5)
print("\nTop trigram collocations:")
for triple in tri_colocs:
print(f" {' '.join(triple)}")
from nltk.corpus import brown
corpus_words = [w.lower() for w in brown.words() if w.isalpha()]
finder = BigramCollocationFinder.from_words(corpus_words)
finder.apply_freq_filter(5)
finder.apply_word_filter( w: (w) < )
top_colocs = finder.nbest(BigramAssocMeasures.pmi, )
VADER Sentiment Analysis
import nltk
from nltk.sentiment.vader import SentimentIntensityAnalyzer
sia = SentimentIntensityAnalyzer()
text = "I absolutely love this product! It's the best thing I've ever bought."
scores = sia.polarity_scores(text)
print(scores)
def classify_sentiment(text):
compound = sia.polarity_scores(text)['compound']
if compound >= 0.05: return 'positive'
elif compound <= -0.05: return 'negative'
else: return 'neutral'
import pandas as pd
texts = [
"This movie was fantastic!",
"Terrible experience, never again.",
"The weather is okay today.",
"I'm so disappointed with the service.",
"Amazing food and great atmosphere!"
]
results = pd.DataFrame({
'text': texts,
'scores': [sia.polarity_scores(t) for t in texts],
'sentiment': [classify_sentiment(t) for t in texts]
})
results[['compound', , , ]] = pd.json_normalize(results[])
(results[[, , ]])
WordNet — Lexical Database
import nltk
from nltk.corpus import wordnet as wn
for synset in wn.synsets('bank'):
print(f" {synset.name():25s} → {synset.definition()}")
dog = wn.synset('dog.n.01')
print("Dog hypernyms:", [h.name() for h in dog.hypernyms()])
animal = wn.synset('animal.n.01')
print("Animal types:", [h.name() for h in animal.hyponyms()][:10])
cat = wn.synset('cat.n.01')
dog = wn.synset('dog.n.01')
car = wn.synset('car.n.01')
print(f"cat ↔ dog: {cat.wup_similarity(dog):.2f}")
print(f"cat ↔ car: {cat.wup_similarity(car):.2f}")
Text Preprocessing Pipeline
import spacy
import re
import pandas as pd
nlp = spacy.load('en_core_web_sm')
def preprocess(text: str,
lowercase: bool = True,
remove_punct: bool = True,
remove_stops: bool = True,
lemmatize: bool = True,
remove_numbers: bool = True) -> list[str]:
"""
Full preprocessing pipeline using spaCy.
Returns list of cleaned tokens.
"""
doc = nlp(text)
tokens = []
for token in doc:
if remove_punct and token.is_punct: continue
if remove_stops and token.is_stop: continue
if remove_numbers and token.like_num: continue
if not token.is_alpha: continue
word = token.lemma_ if lemmatize else token.text
word = word.lower() if lowercase else word
tokens.append(word)
return tokens
text = "The 3 Quick Brown Foxes jumped over 12 lazy dogs in New York!"
print(preprocess(text))
df = pd.DataFrame({: [
,
,
]})
df[] = df[].apply(preprocess)
df[] = df[].apply( t: .join(t))
(df[[, ]])
Text Classification Pipeline
import spacy
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
nlp = spacy.load('en_core_web_sm')
def preprocess(text):
doc = nlp(text)
return ' '.join([t.lemma_.lower() for t in doc if t.is_alpha and not t.is_stop])
texts = ["I love this product"] * 50 + ["terrible, worst ever"] * 50 + ["it was okay"] * 50
labels = ['pos'] * 50 + ['neg'] * 50 + ['neu'] * 50
texts_clean = [preprocess(t) for t in texts]
X_train, X_test, y_train, y_test = train_test_split(
texts_clean, labels, test_size=0.3, random_state=42, stratify=labels
)
tfidf = TfidfVectorizer(max_features=5000, ngram_range=(1, 2))
X_train_v = tfidf.fit_transform(X_train)
X_test_v = tfidf.transform(X_test)
clf = LogisticRegression(max_iter=, random_state=)
clf.fit(X_train_v, y_train)
y_pred = clf.predict(X_test_v)
(classification_report(y_test, y_pred))
new_texts = [, , ]
new_clean = [preprocess(t) t new_texts]
new_vec = tfidf.transform(new_clean)
preds = clf.predict(new_vec)
probs = clf.predict_proba(new_vec)
text, pred, prob (new_texts, preds, probs):
()
spaCy Custom NER Training
import spacy
from spacy.tokens import Doc
import random
import json
TRAIN_DATA = [
("Apple reported $394B in revenue.", {"entities": [(0, 5, "COMPANY"), (17, 23, "REVENUE")]}),
("Google acquired Waze for $1.15B.", {"entities": [(0, 6, "COMPANY"), (16, 20, "COMPANY"), (25, 31, "REVENUE")]}),
("Microsoft invested $10B in OpenAI.", {"entities": [(0, 9, "COMPANY"), (20, 24, "REVENUE"), (28, 34, "COMPANY")]}),
]
def train_custom_ner(train_data, model_name='en_core_web_sm', n_iter=30, output_path='custom_ner'):
nlp = spacy.load(model_name)
if 'ner' not in nlp.pipe_names:
ner = spacy.pipeline.entity_ruler(nlp)
else:
ner = nlp.get_pipe()
labels = ()
_, ents train_data:
_, _, label ents[]:
labels.add(label)
label labels:
ner.add_label(label)
unaffected_pipes = [name name nlp.pipe_names name != ]
nlp.select_pipes(disable=unaffected_pipes):
optimizer = nlp.begin_training()
i (n_iter):
random.shuffle(train_data)
losses = {}
text, annotations train_data:
doc = nlp.make_doc(text)
example = spacy.tokens.Doc(nlp.vocab, words=[t.text t doc])
losses.update(nlp.update([doc], [annotations], losses=losses, sgd=optimizer))
()
nlp.to_disk(output_path)
()
nlp
Word and Document Similarity
import spacy
import numpy as np
nlp = spacy.load('en_core_web_md')
doc = nlp("machine learning deep learning artificial intelligence")
tokens = list(doc)
import pandas as pd
words = ['machine', 'learning', 'deep', 'artificial', 'intelligence']
docs = [nlp(w) for w in words]
sim_matrix = np.array([[d1.similarity(d2) for d2 in docs] for d1 in docs])
df_sim = pd.DataFrame(sim_matrix, index=words, columns=words)
print(df_sim.round(2))
documents = [
"Machine learning is a subset of artificial intelligence.",
"Deep learning uses neural networks for pattern recognition.",
"The stock market crashed yesterday afternoon.",
"Financial markets experienced significant volatility.",
"Cats and dogs are popular household pets."
]
doc_objs = [nlp(text) for text in documents]
print("\nDocument similarity matrix:")
for i, d1 in enumerate(doc_objs):
for j, d2 in enumerate(doc_objs):
if i < j:
sim = d1.similarity(d2)
if sim > :
()
query = nlp()
scored = [(doc, text, query.similarity(doc)) doc, text (doc_objs, documents)]
scored.sort(key= x: x[], reverse=)
()
_, text, score scored[:]:
()
Practical Workflows
1. Entity-Centric Document Analysis
import spacy
import pandas as pd
from collections import defaultdict, Counter
nlp = spacy.load('en_core_web_sm')
def analyze_entities(texts: list[str]) -> dict:
"""
Full entity analysis across a corpus:
→ entity counts, co-occurrences, per-document entity sets.
"""
entity_counter = Counter()
entity_by_type = defaultdict(Counter)
cooccurrence = Counter()
doc_entities = []
docs = list(nlp.pipe(texts, batch_size=64))
for doc in docs:
doc_ents = [(ent.text, ent.label_) for ent in doc.ents]
doc_entities.append(doc_ents)
for text, label in doc_ents:
entity_counter[text] += 1
entity_by_type[label][text] += 1
unique_ents = list(set(e[0] for e in doc_ents))
for i in range(len(unique_ents)):
for j in range(i + 1, len(unique_ents)):
pair = tuple(sorted([unique_ents[i], unique_ents[j]]))
cooccurrence[pair] += 1
return {
: entity_counter,
: (entity_by_type),
: cooccurrence,
: doc_entities
}
():
()
ent, count results[].most_common():
()
()
etype, counter results[].items():
()
ent, count counter.most_common():
()
()
(e1, e2), count results[].most_common():
()
2. Corpus Linguistics Report
import nltk
from nltk.tokenize import word_tokenize
from nltk.probability import FreqDist
from nltk.collocations import BigramCollocationFinder, BigramAssocMeasures
from nltk.corpus import stopwords
import pandas as pd
import math
def corpus_report(texts: list[str], title: str = "Corpus") -> pd.DataFrame:
"""
Comprehensive corpus linguistics analysis:
→ vocabulary richness, frequency distribution,
collocations, readability estimate.
"""
stops = set(stopwords.words('english'))
all_words = []
all_sents = []
for text in texts:
tokens = word_tokenize(text)
all_words.extend(tokens)
all_sents.extend([s.strip() for s in text.split('.') if s.strip()])
clean_words = [w.lower() for w in all_words if w.isalpha() and w.lower() not in stops]
total_words = len(all_words)
total_sents = len(all_sents)
unique_words = len(set(w.lower() for w in all_words if w.isalpha()))
freq = FreqDist(clean_words)
ttr = unique_words / total_words total_words >
avg_sent_len = total_words / total_sents total_sents >
cttr = unique_words / math.sqrt( * total_words) total_words >
finder = BigramCollocationFinder.from_words(clean_words)
finder.apply_freq_filter()
colocs = finder.nbest(BigramAssocMeasures.pmi, )
()
()
()
()
()
()
()
()
()
()
word, count freq.most_common():
bar = * (count / freq.most_common()[][] * )
()
()
pair colocs:
()
freq
3. Multi-Stage NLP Pipeline
import spacy
import pandas as pd
from collections import Counter
nlp = spacy.load('en_core_web_sm')
def full_nlp_pipeline(texts: list[str]) -> pd.DataFrame:
"""
Single pass through corpus:
tokenize → POS tag → NER → sentence split → sentiment (VADER) → extract features.
Returns one row per document with all extracted features.
"""
from nltk.sentiment.vader import SentimentIntensityAnalyzer
sia = SentimentIntensityAnalyzer()
rows = []
docs = list(nlp.pipe(texts, batch_size=64))
for text, doc in zip(texts, docs):
entities = [(ent.text, ent.label_) for ent in doc.ents]
ent_types = Counter(label for _, label in entities)
pos_counts = Counter(token.pos_ for token in doc)
sents = list(doc.sents)
sentiment = sia.polarity_scores(text)
tokens = [t for t in doc if t.is_alpha and not t.is_stop and not t.is_punct]
unique_lemmas = set(t.lemma_.lower() for t in tokens)
rows.append({
'text': text,
: (doc),
: (sents),
: (entities),
: entities,
: (unique_lemmas),
: (unique_lemmas) / (tokens) tokens ,
: sentiment[],
: ( sentiment[] >=
sentiment[] <= - ),
: pos_counts.get(, ),
: pos_counts.get(, ),
: pos_counts.get(, ),
: ent_types.get(, ),
: ent_types.get(, ),
: ent_types.get(, ),
})
pd.DataFrame(rows)
4. Keyword and Keyphrase Extraction
import spacy
from collections import Counter
import math
nlp = spacy.load('en_core_web_sm')
def extract_keywords(texts: list[str], top_n: int = 20) -> list[tuple[str, float]]:
"""
TF-IDF keyword extraction using spaCy lemmas.
Returns terms with highest TF-IDF scores across the corpus.
"""
doc_terms = []
for text in texts:
doc = nlp(text)
terms = [t.lemma_.lower() for t in doc if t.is_alpha and not t.is_stop and not t.is_punct]
doc_terms.append(terms)
n_docs = len(doc_terms)
df = Counter()
for terms in doc_terms:
for term in set(terms):
df[term] += 1
tfidf_sum = Counter()
for terms in doc_terms:
tf = Counter(terms)
doc_len = len(terms) if terms else 1
for term, count in tf.items():
tf_score = count / doc_len
idf_score = math.log(n_docs / ( + df[term]))
tfidf_sum[term] += tf_score * idf_score
tfidf_avg = {term: score / df[term] term, score tfidf_sum.items() df[term] >= }
ranked = (tfidf_avg.items(), key= x: x[], reverse=)
ranked[:top_n]
Performance and Scaling
Processing Speed Benchmarks
import spacy
import time
nlp = spacy.load('en_core_web_sm')
docs_10k = ["Short sentence with entities like Google and Apple."] * 10000
start = time.time()
processed = list(nlp.pipe(docs_10k, batch_size=128, n_process=4))
elapsed = time.time() - start
print(f"Full pipeline: {len(docs_10k)} docs in {elapsed:.1f}s ({len(docs_10k)/elapsed:.0f} docs/s)")
with nlp.select_pipes(disable=['parser']):
start = time.time()
processed = list(nlp.pipe(docs_10k, batch_size=128, n_process=4))
elapsed = time.time() - start
print(f"NER only: {len(docs_10k)} docs in {elapsed:.1f}s ({len(docs_10k)/elapsed:.0f} docs/s)")
tokenizer = nlp.tokenizer
start = time.time()
tokenized = [tokenizer(text) for text in docs_10k]
elapsed = time.time() - start
print(f"Tokenize only: {len(docs_10k)} docs in {elapsed:.1f}s ({len(docs_10k)/elapsed:.0f} docs/s)")
Memory-Efficient Streaming
import spacy
nlp = spacy.load('en_core_web_sm')
def stream_entities(filepath: str, batch_size: int = 256):
"""
Stream NER from a large file without loading all text into memory.
Yields (line_number, entities) tuples.
"""
def text_generator():
with open(filepath) as f:
for line in f:
yield line.strip()
for i, doc in enumerate(nlp.pipe(text_generator(), batch_size=batch_size)):
entities = [(ent.text, ent.label_) for ent in doc.ents]
if entities:
yield i, entities
Common Pitfalls and Solutions
Sentence Segmentation Requires Parser
import spacy
nlp = spacy.load('en_core_web_sm')
with nlp.select_pipes(disable=['parser']):
doc = nlp("Hello world. This is sentence two.")
sents = list(doc.sents)
nlp.add_pipe('sentencizer')
with nlp.select_pipes(disable=['parser']):
doc = nlp("Hello world. This is sentence two.")
sents = list(doc.sents)
doc = nlp("Hello world. This is sentence two.")
sents = list(doc.sents)
Entity Boundaries and Spans
import spacy
nlp = spacy.load('en_core_web_sm')
doc = nlp("Barack Obama visited the United States Capitol.")
text_slice = doc.text[7:13]
span = doc.char_span(7, 12, label="PERSON")
if span:
print(span.text, span.label_)
for ent in doc.ents:
start = max(0, ent.start - 2)
end = min(len(doc), ent.end + 2)
window = doc[start:end]
print(f"{ent.label_:10s} {ent.text:20s} context: \"{window.text}\"")
NLTK Data Not Found
import nltk
from nltk.tokenize import word_tokenize
word_tokenize("Hello world")
nltk.download('punkt_tab')
nltk.download('averaged_perceptron_tagger_eng')
nltk.download('stopwords')
nltk.download('vader_lexicon')
nltk.download('wordnet')
nltk.download('brown')
nltk.download('gutenberg')
Multilingual Text
import spacy
nlp_en = spacy.load('en_core_web_sm')
nlp_de = spacy.load('de_core_news_sm')
nlp_fr = spacy.load('fr_core_news_sm')
nlp_es = spacy.load('es_core_news_sm')
from langdetect import detect
MODELS = {
'en': nlp_en,
'de': nlp_de,
'fr': nlp_fr,
'es': nlp_es,
}
def process_multilingual(text: str):
lang = detect(text)
nlp = MODELS.get(lang, nlp_en)
doc = nlp(text)
return {
'language': lang,
'entities': [(ent.text, ent.label_) for ent in doc.ents],
'sentences': [sent.text for sent in doc.sents]
}
spaCy and NLTK are complements: spaCy is the processing engine, NLTK is the analysis toolkit. The standard workflow is spaCy for the pipeline (tokenize → tag → parse → NER) and NLTK for the linguistics (frequency, collocations, corpus analysis, sentiment). Together they cover everything from quick entity extraction to deep corpus-level research.