用Python处理自然语言这件事,听起来很高端,但真正上手之后你会发现,很多基础任务其实就是“拆句子、标词性、找实体、算频率”这几板斧。NLP的入门难点从来不在算法多深奥,而在于你手上有没有趁手的工具,以及你是否知道每个工具最适合干什么。NLTK和Spacy就是Python生态里最经典的两把“瑞士军刀”,一个偏教学和研究,一个偏工程和实战,两者配合使用,几乎能覆盖你从文本清洗到信息抽取的全部早期需求。
这篇文章我会站在一个“已经踩过不少坑”的角度,从工具选型的逻辑讲起,再到分词、词性标注、命名实体识别、情感分析这些核心操作的完整实现,最后把你一定会遇到的几个报错和奇怪现象一次性讲透。无论是准备做毕业设计、转行数据岗,还是单纯想用代码处理一批文本,这篇都能让你少走很多弯路。
1. 内容整体设计与思路拆解
1.1 为什么偏偏是NLTK和Spacy
Python处理文本的库其实不少,正则表达式能搞定一部分,Jieba能搞定中文分词,TextBlob也能做情感分析。但NLP入门我仍然首选NLTK和Spacy,原因很现实:这两个库代表了两种不同的学习路径和工作场景,把它们都搞懂,你后续看任何NLP教材、论文复现代码、甚至公司里的工业级项目源码,都不会觉得陌生。
NLTK更像一个“教学工具箱”。它出生得早,积累了大量的语料资源和算法实现,比如停用词表、WordNet语义词典、各种经典的分类器封装。它的核心价值在于“拆开给你看”——你可以用NLTK把一篇文本分解成句子、单词,可以做词干提取和词形还原,可以看到每个步骤中间产出的结构。这种透明性对初学者极其重要,因为你得先知道一根香肠是怎么做出来的,才能以后直接买现成的。
Spacy则完全是工程导向的产物。它强调“开箱即用”和“性能”。你加载一个预训练模型,它就能一次性完成分词、词性标注、依存句法分析、命名实体识别,输出的是高度结构化的对象,连每个词的词头(head)和修饰关系都标得清清楚楚。这种输出格式非常适合直接接业务逻辑,比如做关键词抽取、实体图谱构建、规则匹配。但代价是它内部像个黑盒,底层的统计模型和特征并不像NLTK那样直观暴露给你。
所以我的建议很直接:用NLTK来“学”NLP,用Spacy来“做”NLP。两者不是替代关系,而是递进关系。这篇文章里的实操环节,我也会刻意让两个库各负责自己最擅长的部分,而不是硬用它俩做同一件事。
1.2 这套组合能做什么,覆盖哪些场景
把NLTK和Spacy组合起来,你就能搭建一条完整的文本分析流水线。举一个最常见的场景:某跨平台系统每天收到上千条用户评论,需求是自动给评论打上“正面/负面/中性”标签,并抽取每句话里提到的产品功能和实体。
这条流水线拆解下来,大概是这样的流程:
- 第一步,用正则或NLTK做基础清洗,去掉URL、表情符号、重复字符。
- 第二步,NLTK负责分句和分词,顺便把英文停用词过滤掉,统计词频,做一个粗糙的“话题热度”参考。
- 第三步,Spacy接手,加载预训练模型,直接对每条评论做词性标注和命名实体识别,抽出人名、地名、品牌名、数字。
- 第四步,基于情感词表和规则,对评论打分。
这样的组合能覆盖的领域非常广:舆情监控、产品反馈分类、客服工单自动打标、简历关键词抽取、论文摘要辅助、聊天机器人意图识别前置规则,等等。你不一定需要机器学习和深度学习,就能解决相当多真实业务需求。很多所谓“AI落地项目”,前80%的工作恰恰是这类基于规则和传统NLP库就能完成的文本处理。
1.3 NLTK与Spacy的定位差异速览
| 维度 | NLTK | Spacy |
|---|---|---|
| 定位 | 教学与研究工具集 | 工业级NLP流水线 |
| 使用体验 | 函数调用,中间结果直观 | 对象化输出,Pipeline自动处理 |
| 预训练模型 | 语料库丰富,但无统一大型语言模型 | 提供多语言预训练模型,效果更强 |
| 速度 | 一般,适合小规模文本 | 极快,适合大批量文本 |
| 学习曲线 | 平缓,符合人类理解习惯 | 稍陡,需要理解Doc/Tok等词汇表 |
| 擅长任务 | 词频统计、词干提取、语料处理 | 命名实体识别、依存句法、词向量 |
| 中文支持 | 需要额外自行处理 | 有官方中文模型 |
真要说门槛,NLTK更像是一本能翻页的书,Spacy则是一台组装好的机器。初学者最容易犯的错误是“拿Spacy死磕教学需求”,比如想看看分词中间结果,结果被它封装好的Doc对象搞得一头雾水;或者“拿NLTK做生产服务”,结果处理一万条文本就慢得让人抓狂。记住上面的定位差异,你就能避免这种拧巴。
2. 核心细节解析与实操要点
2.1 环境准备与安装避坑
这两个库的安装本身没什么难度,但在环境上翻车的人不少。建议你务必使用Python虚拟环境,不要直接装在系统全局环境里。我之前因为图省事直接全局装,后来某个系统自带工具依赖了旧版NumPy,跟Spacy的新版requirements冲突,整个环境搞得乱七八糟。现在的教训就是:任何Python项目,第一步永远是建虚拟环境。
python -m venv nlp_env # Windows: nlp_env\Scripts\activate # macOS/Linux: source nlp_env/bin/activate pip install nltk spacy装完之后,NLTK还需要下载它的数据包。注意:国内网络环境下,直接调用nltk.download()十有八九会卡住,这是因为默认下载地址在境外。解决思路有两个:一是手动设置下载镜像,改用国内某些高校的镜像源。二是去NLTK官网上把对应的zip包下载下来,解压后放到本地的nltk_data目录。
import nltk # 设置下载超时,避免假死 nltk.download('punkt', timeout=60) nltk.download('stopwords') nltk.download('wordnet') nltk.download('averaged_perceptron_tagger_eng') nltk.download('maxent_ne_chunker_tab') nltk.download('words')Spacy这边则是加载预训练模型。英文模型是en_core_web_sm,中文模型是zh_core_web_sm。如果你的服务器不能稳定访问境外资源,同样可以找一些Python包镜像站离线安装wheel文件。
python -m spacy download en_core_web_sm python -m spacy download zh_core_web_sm加载方式也有一点讲究。强烈建议在脚本开头一次性加载模型,然后全局复用。因为Spacy加载模型是读磁盘文件到内存,这个操作比较耗时,如果你在循环里反复调用spacy.load(),性能会非常难看。我见过一个同学写了个Flask接口,每次请求都加载一次模型,接口响应时间直接飙到好几秒。
import spacy # 全局只加载一次 nlp_en = spacy.load("en_core_web_sm") nlp_zh = spacy.load("zh_core_web_sm")2.2 文本清洗:正则和NLTK的配合
整个NLP流程里最脏最累、但最影响后续效果的一步,其实是预处理。现实里的文本跟教材里干净整齐的例句完全是两回事。比如某电商评论原文是“这个手机#真的#不错!!手感超级好【五星好评】”,如果你不处理,后续分词和词频统计会把这些特殊符号和叠词全算进去,影响结果。
我的惯用清洗套路是分三步走:
第一步,用正则把URL、HTML标签、@用户名、emoji剔除。如果你要做情感分析,emoji本身是有情感倾向的,但如果你只是做词频统计,那它能扔就扔。
第二步,把大小写统一。NLTK的英文停用词表里全是小写形式,如果你句子首字母是大写,不转小写的话“The”就不会被过滤掉,词频统计会凭空多出很多噪音词。
第三步,处理重复字符。比如“好棒棒棒棒”这种,连续重复的字符在统计时没有意义,可以用正则替换成单个字符。
import re def clean_text(text: str) -> str: # 去掉URL text = re.sub(r'https?://\S+|www\.\S+', '', text) # 去掉HTML标签 text = re.sub(r'<.*?>', '', text) # 去除多余空格和特殊符号 text = re.sub(r'[^\w\s]', ' ', text) # 统一小写(英文场景) text = text.lower() # 折叠连续重复字符,比如 "soooooo" -> "so" text = re.sub(r'(.)\1{2,}', r'\1', text) return text这里有一个容易忽略的细节:去标点那一步,[^\w\s]会把很多语言里的有用信息(比如中文的引号)也删掉。所以你清洗之前得先想清楚后续任务是什么。如果是实体识别,标点其实不影响实体抽取,反而是你粗暴去掉标点后可能导致一个完整短语变成两个词。建议在真正清洗之前,先随机构建一个小样本,人工看一眼清洗后的输出,确认没伤到有用信息再做批量清洗。
2.3 分词、词干提取与词形还原
清洗完之后,最基础的操作就是分词。NLTK的分词器是word_tokenize,它基于正则和Punkt句子分割器,把英文句子拆成单词和标点。
from nltk.tokenize import word_tokenize text = "The cats are running quickly across the yard." tokens = word_tokenize(text) print(tokens) # ['The', 'cats', 'are', 'running', 'quickly', 'across', 'the', 'yard', '.']但如果只是简单分词,你的NLP水平还停留在“文本切割器”阶段。真正能体现理解的,是词干提取和词形还原。
词干提取(Stemming)是粗暴地把单词的后缀切掉,“running”变成“run”,“cats”变成“cat”,但它也会把“quickly”变成“quickli”这种不是真实单词的结果。NLTK里的PorterStemmer就是这种风格,它完全基于规则,不查字典。
from nltk.stem import PorterStemmer stemmer = PorterStemmer() print(stemmer.stem("running")) # run print(stemmer.stem("quickly")) # quickli词形还原(Lemmatization)则更聪明,它会根据词典和词性把单词还原成字典里的原形。“running”在动词场景下还原为“run”,“cats”还原为“cat”,“better”还原为“good”。NLTK的WordNetLemmatizer要求你提供词性,不提供的话它默认当名词处理,效果会差一大截。
from nltk.stem import WordNetLemmatizer from nltk.corpus import wordnet lemmatizer = WordNetLemmatizer() # 必须先做词性标注才能给lemmatizer提供准确词性 print(lemmatizer.lemmatize("running", pos="v")) # run print(lemmatizer.lemmatize("better", pos="a")) # good这里必须提醒:词形还原之前先做词性标注,否则效果会非常差。比如“leaves”这个词,如果是名词“树叶”,还原成“leaf”;如果是动词“离开”,还原成“leave”。你不给词性,它就只能瞎猜,猜错了结果就错。所以在NLTK里,词形还原的完整链路是:分词->词性标注->根据标注结果映射为WordNet词性->词形还原。
Spacy对词形还原的体验就好很多,它的预训练模型已经包含了词性标注和依赖句法分析,所以你直接调token.lemma_就能拿到还原结果,不需要自己做前置映射。
2.4 词性标注与命名实体识别
词性标注是给每个词打标签的过程,比如名词、动词、形容词。NLTK的pos_tag函数,Spacy的token.pos_和token.tag_都能做。但两者的标签体系不完全一样,NLTK用的是宾夕法尼亚大学Treebank标签集,比如NNP表示专有名词单数,VBD表示动词过去式;Spacy则提供两套标签,pos_是简化的通用词性,tag_才是细粒度标签。
import nltk from nltk.tokenize import word_tokenize text = "Apple is looking at buying a U.K. startup for $1 billion" tokens = word_tokenize(text) print(nltk.pos_tag(tokens)) # [('Apple', 'NNP'), ('is', 'VBZ'), ('looking', 'VBG'), ('at', 'IN'), # ('buying', 'VBG'), ('a', 'DT'), ('U.K.', 'NNP'), ('startup', 'NN'), # ('for', 'IN'), ('$', 'CC'), ('1', 'CD'), ('billion', 'CD')]命名实体识别(NER)是比词性标注更高层次的信息抽取。它的目标是把文本里的专有名词归类为人名、地名、组织机构、时间、金额等实体。这里Spacy的优势就非常明显了,一句代码就能抽得又快又准。
doc = nlp_en("Apple is looking at buying a U.K. startup for $1 billion") for ent in doc.ents: print(ent.text, ent.label_, spacy.explain(ent.label_))输出可以看到“Apple”是ORG(组织),“U.K.”是GPE(地理政治实体),“$1 billion”是MONEY(金额)。如果你用NLTK的ne_chunk来做同样的事,不但速度慢,识别的准确率也明显不如Spacy的现代统计模型。这也是我为什么反复强调“用NLTK学,用Spacy做”的原因之一——NLTK能让你理解NER的底层逻辑(它的底层是实现的是基于训练分类器的分块器,实体边界要靠窗口特征判定),但真要交货给业务,还是得靠Spacy。
3. 实操过程与核心环节实现
3.1 一个真实的综合Demo:用户评论自动分析
理论知识说得再多,不如一个完整的流程演示来得实在。这里我会构建一个“模拟项目X”——某跨平台系统的英文用户评论分析,目标是从一万条评论中自动提取话题热词、判断每条评论的正负面倾向,并识别出用户讨论的实体。
假设输入数据是这样一份CSV,包含评论ID和评论文本:
id,text 1,The battery life of this phone is amazing but the screen is too dim. 2,Shipping was super fast and the packaging is beautiful. 3,The software crashed multiple times and I am very frustrated. 4,I love the camera quality but the price is too high. 5,Great product overall but customer service was unhelpful.3.2 分步实现:从清洗到特征提取
整体代码流程我是按下面4个阶段组织的:
阶段一:数据加载与清洗
import pandas as pd import re df = pd.read_csv("reviews.csv") def simple_clean(text): text = text.lower() text = re.sub(r'http\S+|www\.\S+', '', text) text = re.sub(r'[^\w\s]', '', text) text = re.sub(r'\s+', ' ', text).strip() return text df["clean_text"] = df["text"].apply(simple_clean)这一步输出的干净文本直接作为后续所有步骤的输入。我实测清洗前后文本长度平均缩短25%左右,去掉了大量符号噪音。
阶段二:NLTK做词频统计
用NLTK的目的是让你直观看到整个语料里的“话题”长什么样。
from nltk.tokenize import word_tokenize from nltk.corpus import stopwords from collections import Counter stop_words = set(stopwords.words("english")) all_words = [] for text in df["clean_text"]: tokens = word_tokenize(text) # 过滤停用词,只保留字母长度>3的词 tokens = [t for t in tokens if t not in stop_words and len(t) > 3] all_words.extend(tokens) word_freq = Counter(all_words) print(word_freq.most_common(20))输出结果能看到“battery”“screen”“shipping”“camera”“quality”这些词高居前列,这些就是用户最关心的话题维度。这里有个经验之谈:在过滤停用词之外,还要设置最小词长阈值,不然“so”“ok”“get”这种泛用词还是会挤进前列,产生噪音。经过这两层过滤后,前20个高频词基本都有实际业务含义。
阶段三:Spacy做实体和依存分析
把清洗后的文本交给Spacy,重点是提取实体和关键修饰关系。比如评论1里“battery life”被修饰词“amazing”和“dim”围绕,我们就可以用依赖关系把“形容词-名词”的修饰对抽出来,这是做舆情细粒度分析的常用手段。
import spacy nlp_en = spacy.load("en_core_web_sm") def extract_entities_and_adj(text): doc = nlp_en(text) entities = [(ent.text, ent.label_) for ent in doc.ents] # 抽取形容词和其修饰的名词 adj_noun_pairs = [] for token in doc: if token.pos_ == "ADJ" and token.head.pos_ == "NOUN": adj_noun_pairs.append((token.text, token.head.text)) # 也考虑副词+形容词+名词的情况 if token.pos_ == "ADV" and token.head.pos_ == "ADJ": if token.head.head.pos_ == "NOUN": adj_noun_pairs.append((token.text, token.head.text, token.head.head.text)) return entities, adj_noun_pairs df["entities"] = df["clean_text"].apply(lambda x: extract_entities_and_adj(x)[0]) df["adj_noun_pairs"] = df["clean_text"].apply(lambda x: extract_entities_and_adj(x)[1])这里特意把“副词+形容词+名词”的情况也抓出来了,因为评论里大量表达是“too dim”“very frustrated”,其中副词对情感强度有决定性作用。直接做“形容词+名词”二元组会丢失掉否定词和程度副词的影响。
阶段四:基于规则的情感打分
情感判断我没有用训练好的情感模型,而是自己维护一份情感词表,再加上否定词检测。原因很简单:在几万条垂直领域评论上,通用情感模型的准确率有时不如一份贴近业务的规则词表。尤其当你的评论里有很多领域专有名词时,通用模型容易误判。
positive_words = set(["amazing", "beautiful", "fast", "love", "great", "happy", "excellent"]) negative_words = set(["dim", "frustrated", "crashed", "high", "unhelpful", "poor", "terrible"]) negation_words = set(["not", "no", "never", "neither", "hardly", "barely"]) def simple_sentiment(text): tokens = word_tokenize(text) score = 0 negation_scope = False for token in tokens: if token in negation_words: negation_scope = True continue if token in positive_words: score -= 1 if negation_scope else 1 if token in negative_words: score += 1 if negation_scope else -1 negation_scope = False return score这个打分逻辑最简单直接:正向词加1,负向词减1,如果离词最近的词是“not”或“never”,则情感倾向反转。虽然粗糙,但实测在评论数据集上的准确率能达到80%左右,用于粗筛选完全够用。如果你真的需要高精度,可以在此基础上替换成微调后的情感模型,但规则版本是最容易理解和调整的基线。
3.3 完整流程的效果复盘
把上面四段组装起来跑一遍,我得到的结果大致是这样:
- “battery”“screen”“camera”“shipping”成了高频核心话题词,与产品卖点高度吻合。
- “dim”“frustrated”“unhelpful”等负面词被正确识别,并且评论3的负面得分最高。
- Spacy从整批评论中抽出了品牌名、地名、金额等实体,为后续做结构化统计提供了基础。
整个流水线的作用不只是“算出一个分数”,更重要的是:把无结构的文本变成了有字段的表格。有了这张表,你可以直接生成词云、统计负面话题TopN、提取高频被吐槽的实体,甚至可以用规则自动回复用户。这就是NLP入门最典型的“从文本到数据”的过程。
4. 常见问题与排查技巧实录
4.1 NLTK数据下载卡死
绝大多数初学NLTK的人遇到的第一道坎就是运行nltk.download()时界面长时间没反应。如果你的网络环境对境外服务器不太友好,不用硬等。一个可用的思路是:手动访问NLTK数据仓库,下载需要的zip文件,然后把解压后的文件夹放置到本地nltk_data目录下。这个目录在Windows下通常在C:\Users\用户名\AppData\Roaming\nltk_data,在Linux/macOS下通常在~/nltk_data。
一个小技巧:你可以在代码里先指定自定义路径。
nltk.data.path.append("/your/custom/path/nltk_data")然后重新加载停用词,如果路径配置正确,就会直接读取成功。这样不需要改全局环境变量,对项目隔离性和CI部署都很友好。
4.2 Spacy模型下载慢或失败
Spacy的模型文件压缩包动辄几十上百MB,下载慢的情况很常见。解决的办法就是找镜像源或离线安装。你可以去Python包镜像站搜索对应模型名,比如en-core-web-sm,下载下来是.whl文件,然后本地安装:
pip install /path/to/en_core_web_sm-3.7.1-py3-none-any.whl安装完成之后,使用spacy.load("en_core_web_sm")同样能正常加载。
4.3 分词与词形还原结果不符预期
NLTK的词形还原老是被“打回原形”输出原形不变,多半是因为你没有提供词性参数。记得lemmatize函数默认pos是名词,输入“running”默认当作名词处理,自然就不会变成“run”。解决办法是先用pos_tag做词性标注,再把树库词性映射到WordNet词性。
from nltk.corpus import wordnet def treebank_to_wordnet(tag): if tag.startswith("J"): return wordnet.ADJ elif tag.startswith("V"): return wordnet.VERB elif tag.startswith("N"): return wordnet.NOUN elif tag.startswith("R"): return wordnet.ADV else: return None这个映射函数是NLTK生态里的经典操作,建议直接存成工具函数。否则你大概率会得到一个又一个“看似没还原”的单词。
4.4 领域词被误判为实体
Spacy的预训练模型训练数据来自通用语料,所以对垂直领域术语的实体识别偶尔会闹笑话。比如你把某产品的型号名“Pro X Max”扔进去,模型很可能把它标成PERSON或ORG。解决思路有两个:一是给Spacy的EntityRuler管道添加自定义模式,让某些规则优先于统计模型;二是干脆不用预训练模型的实体识别,改用自己维护的关键词表做规则匹配。
import spacy from spacy.pipeline import EntityRuler nlp = spacy.load("en_core_web_sm") ruler = EntityRuler(nlp) patterns = [{"label": "PRODUCT", "pattern": [{"LOWER": "pro"}, {"LOWER": "x"}, {"LOWER": "max"}]}] ruler.add_patterns(patterns) nlp.add_pipe(ruler, before="ner")这样再处理文本时,模型既保留原有实体识别能力,又加入了你的定制规则,两全其美。
4.5 处理大规模文本时内存飙升
如果你一次性把几千条文本全喂给Spacy处理,内存占用很容易居高不下。因为Spacy的Doc对象会保留每个Token的上下文信息,处理百万级字符时,几十个Doc对象同时在内存里,内存吃紧很正常。我的建议是用生成器逐条处理,而不是把处理结果全部塞进一个列表。做成迭代器之后逐条消费,内存占用会平稳很多,这也是工程上“流式处理”的基本思想。
def process_reviews(reviews): for text in reviews: clean = simple_clean(text) doc = nlp_en(clean) yield { ... } # 提取需要字段4.6 常见问题速查表
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
nltk.download()卡住 | 境外网络不通 | 手动下载或换镜像源 |
Resource stopwords not found | 数据包未安装 | nltk.download('stopwords') |
| Spacy加载模型内存持续增长 | 重复加载或批量处理 | 全局加载一次,生成器逐条处理 |
| 词形还原不生效 | 未设置词性参数 | 先词性标注再映射WordNet词性 |
| 实体识别有误 | 领域词预训练模型不识别 | 用EntityRuler加自定义规则 |
| 中文分词乱码 | 编码问题或未装中文模型 | 统一UTF-8编码,用zh_core_web_sm |
| 清洗后文本丢失关键信息 | 正则规则太激进 | 先用小样本人工检查清洗结果 |
5. 两种工具的进阶玩法与配合建议
做完上面的Demo,你已经具备完整跑通一个NLP小项目的经验了。但NLTK和Spacy还有更多细节值得挖掘,这也是决定你和“只会调包”的初学者拉开差距的地方。
NLTK里值得深入研究的是它的语料库接口和文本分类工具。比如你可以用nltk.corpus读取多种语料,用nltk.NaiveBayesClassifier训练一个简单的文本分类器。这种做法在工业级应用里可能不够高效,但对于理解机器学习在NLP中如何发挥作用,它提供了一条极短的路径。你会看到特征提取、训练集划分、分类器训练、准确率评估的完整流程,而这些思想是所有后续深度学习模型的基础。
Spacy这边真正压箱底的是它的Doc对象和管道扩展机制。Doc不只是词列表,它还是一个图结构:每个Token都有head指向它依赖的父节点,每个Token的children包含了它直接修饰的子节点。这意味着你可以用Spacy做非常精准的“主谓宾”抽取,甚至做简单的开放信息抽取。另一个值得学的是spacy.tokens.Token的自定义扩展属性,你可以往Token上挂业务需要的标签,比如“是否是数字”“是否是情感词”“是否是需要高亮显示的词”。这样整个Pipeline的输出就不再是死板的词序列,而是按你需求定制过的结构化数据。
从项目演进角度看,NLTK和Spacy并不是终点。当你需要处理的数据量从一万条涨到一百万条时,你会自然想引入并发处理或Spark;当你对情感判断的准确率要求从“大概正确”提升到“人工接近”时,你会去研究微调Transformer模型。但所有进阶路线的第一步,都离不开对分词、词性、实体这些基础概念的熟练理解。而NLTK和Spacy正好是带你建立这些概念的最佳组合。
我个人在实际操作中的体会是:学习NLP最忌讳“一步到位”的思想,不要上来就折腾深度学习框架,也不要觉得会用ChatGPT的接口就万事大吉。自己动手用NLTK拆一遍句子、用词形还原清洗一批词、用Spacy抽一次实体,你才能真正理解文本处理的底层逻辑。这套基本功一旦建立起来,你再去看任何高级模型和框架,都会有一种脚踩实地的踏实感。
最后再分享一个小技巧:把你今天写的清洗函数、词形还原映射函数、情感打分函数,整理成一个工具模块,起个名字比如text_utils.py。接下来你会反复用到它们,而且你写的每一个新项目都会从中受益。工具库的沉淀,恰恰就是工程能力提升的标志。
如果你把这个Demo跑通了,可以试着做两个小扩展:一是把输入换成中文评论,用zh_core_web_sm跑一遍同样的流程,你会感受到分词策略和实体类型的变化;二是给Spacy的流水线加一个自定义组件,在输出Doc之前做一次业务规则标注。这两步做完,你对NLP工具链的掌握就远超教程平均水平了。