想做自然语言处理NLP方向的尝试NLTK和Spacy基本是绕不开的两个名字。我在新闻文本分析和医疗问诊类文本的小项目里来回切换了很久才真正弄清楚这两套工具各自的脾气。这篇内容不是什么高深理论就是一个从零上手的实操记录NLTK哪些地方顺手、Spacy哪些设计更现代、环境怎么配、数据下载卡住怎么办、到底该用哪个。适合刚接触NLP、想在具体文本上动手跑一跑的初学者也适合已经在写代码但没系统比较过这两个库的人。1. 入门NLP第一道选择题为什么同时把NLTK和Spacy放在一起学1.1 两个库的出身就决定了它们的性格NLTK全称Natural Language Toolkit诞生于2001年是宾夕法尼亚大学计算机与信息科学系牵头做的教学工具包。它本质上是一个算法陈列馆把词性标注、分词、句法分析、情感分析、文本分类这些经典方法都实现了一遍配套还带了一堆语料库比如影评情感语料movie_reviews、布朗语料库brown、词汇关系数据库wordnet。它的定位非常明确让你通过修改参数、调用底层算法把NLP的每个环节看个通透。Spacy则完全是另一个路数。它2015年发布从一开始就奔着生产环境可用去设计。它不追求把几十种算法堆给你而是提供一套经过预训练的、端到端的处理流水线输入一段文本输出词性、依存关系、命名实体、词向量等结构化结果。模型是用神经网络训练的速度比NLTK那些经典统计方法快得多而且API设计得非常统一做工程的人会感觉很舒服。我常打一个比方NLTK像一个工具齐全的手工作坊适合你慢慢拆解每一个零件的原理Spacy是一条现代化的装配流水线你只管把原材料丢上去末端出来的就是可用的成品。既然是入门两者其实都得碰一碰——先用NLTK弄明白语言处理到底在做什么再用Spacy感受工业级处理该有多快。1.2 两个库的关键差异一张表说清楚维度NLTKSpacy核心定位教学与研究工具包生产级工业库模型方式传统统计模型需要单独下载数据包预训练神经网络模型可整体下载处理速度中等偏慢语料大时明显吃力快适合批量和在线处理中文支持基本靠外部工具如jieba配合官方中文模型分词标注实体一条龙学习价值高能看到算法细节中黑盒居多但API清晰扩展性灵活适合做实验强支持自定义管道组件和训练看完这张表就容易理解为什么教程很少只讲其中一个了。学概念去翻NLTK做真正能上线的功能去写Spacy。两个都装上互相补充才是入门阶段性价比最高的组合。2. 环境准备装库不难难的是NLTK数据下载这一步2.1 基础安装与模型下载先装两个库Python 3.8以上都没问题。pip install nltk spacySpacy需要单独下载预训练模型英文用en_core_web_sm中文用zh_core_web_sm。新手最容易在这步翻车——直接跑spacy.load(en_core_web_sm)会报错要先执行下载命令python -m spacy download en_core_web_sm python -m spacy download zh_core_web_sm下载慢的话也可以去Spacy的GitHub Releases里找对应的whl文件下载后pip install本地文件。不过通常直接用官方命令就能搞定。2.2 NLTK数据包下载慢的根源与解决办法NLTK这边的情况更特殊。它的大部分功能不像Spacy那样随库自带模型而是要单独执行nltk.download()拉取数据包。常见的基础资源有这么几个import nltk nltk.download(punkt) # 分句与分词模型 nltk.download(punkt_tab) # 新版NLTK需要的分词表 nltk.download(stopwords) # 停用词表 nltk.download(averaged_perceptron_tagger) # 词性标注模型 nltk.download(wordnet) # 词汇数据库词形还原要用 nltk.download(maxent_ne_chunker) # 命名实体识别 nltk.download(words)很多人在这一步卡住尤其是punkt这个包默认下载源在海外的数据仓库上国内网络环境下经常几KB每秒甚至直接超时。第一次跑nltk.download()弹出来的那个图形界面进度条半天不动十个人里有九个以为是电脑死机了。解决思路其实不复杂核心是让NLTK走本地数据目录。我实操下来最稳的方案是手动下载再放置。去NLTK官方的nltk_data仓库找到需要的数据包每个包对应一个压缩文件手动下载后解压放到一个固定目录里比如~/nltk_data然后在代码里指定路径import nltk nltk.data.path.append(/你的路径/nltk_data)或者直接设置环境变量让NLTK自动识别export NLTK_DATA/你的路径/nltk_data注意目录内部结构必须是packages/里的子目录层级比如tokenizers/punkt、corpora/stopwords。放错了层级NLTK会一直报找不到资源。查看当前NLTK的搜索路径用这条命令print(nltk.data.path)2.3 不同NLTK版本的数据包名称坑这里有个特别容易踩的版本坑。老教程里都写nltk.download(punkt)但NLTK升级到3.8之后官方把分词器的数据拆分了新增了一个punkt_tab资源包。你只下载punkt代码可能还会报Resource punkt_tab not found。所以我在前面特意把两个都列上了最好一次下载全省得后面反复折腾。另外词性标注器也有类似问题某些新版本里资源名从averaged_perceptron_tagger变成了带后缀的averaged_perceptron_tagger_eng。遇到这类报错最直接的办法是在nltk.download()弹窗里搜一下资源名或者用nltk.download(all)把通用数据都拉全不推荐体积大且有些冷门包用不上。3. NLTK实操把文本拆碎再贴上标签3.1 分句与分词最基础的两个动作NLTK的分词分句依赖punkt它本质上是一个无监督的统计模型学会了英文里的句号、缩写、冒号的节奏。先看一段英文新闻文本的处理from nltk.tokenize import word_tokenize, sent_tokenize text Apple Inc. is looking at buying U.K. startup for $1 billion. The company announced the deal on Monday. sents sent_tokenize(text) for s in sents: print(s) words word_tokenize(text) print(words)输出会把两句话拆开分词结果里Apple、Inc.、U.K.都会作为独立token被保留而不是被句点误拆。这就是punkt的厉害之处。不过要提醒一句word_tokenize默认按英文规则处理中文文本直接喂进去效果很差中文要么配jieba要么直接上Spacy的中文模型。这个点放到后面实战里细说。3.2 词形还原与词干提取降到原形再分析文本分析前常要做规范化处理。NLTK提供了两类手段词干提取Stemming和词形还原Lemmatization。词干提取是暴力截断比如running变成run、better变成better它不管词是否合法词形还原则会根据词性和词典知识把better还原成good、was还原成be更优雅但速度稍慢。from nltk.stem import PorterStemmer, WordNetLemmatizer stemmer PorterStemmer() lemmatizer WordNetLemmatizer() for w in [running, ran, better, studies]: print(w, -, stemmer.stem(w), |, lemmatizer.lemmatize(w, posv))注意词形还原默认把词当名词处理想还原动词就得传posv。这个细节不看文档的话很容易踩坑还原出来的结果跟你预期完全对不上。3.3 停用词、词性标注与命名实体初探停用词处理是用stopwords包做的from nltk.corpus import stopwords stop_words set(stopwords.words(english)) filtered [w for w in word_tokenize(text.lower()) if w.isalpha() and w not in stop_words] print(filtered)这里我顺便加了个isalpha()过滤把数字和标点符号剔除省得后面统计词频时一堆噪声。词性标注用的是perceptron模型from nltk import pos_tag tokens word_tokenize(text) tagged pos_tag(tokens) print(tagged)输出形式是(Apple, NNP)这样的二元组NNP是专有名词NN是普通名词VBG是动名词。NLTK用的是宾州树库的标签集刚开始看着头大用多了就习惯了。再往上走可以基于词性做简单的命名实体识别用ne_chunkfrom nltk import ne_chunk chunked ne_chunk(tagged) print(chunked)它会输出一棵嵌套树标注出Apple是ORGANIZATION、U.K.是GPE地理政治实体。不过说实话maxent_ne_chunker的准确率放在今天已经明显落后做大项目的实体识别我还是用Spacy这里仅仅是展示NLTK的经典处理链路。4. Spacy实操一条龙语言处理流水线4.1 加载模型与Doc对象Spacy的使用习惯和NLTK完全不同。它加载一个模型后所有处理都通过nlp这个管道对象完成import spacy nlp spacy.load(en_core_web_sm) doc nlp(Apple Inc. is looking at buying U.K. startup for $1 billion. The company announced the deal on Monday.)一个doc就是处理完的文本对象里面每个token都带上一堆结构化属性text是原文lemma_是词形还原结果pos_是粗粒度词性tag_是细粒度词性is_stop判断是否停用词is_alpha判断是否纯字母。列几个看看for token in doc: print(token.text, token.lemma_, token.pos_, token.tag_, token.is_stop)整个过程不用像NLTK那样分步调用一次nlp()就把分词、词性、依存、实体全跑完了。这就是管道设计的优势。4.2 从词法属性到依存句法词性只是表层信息Spacy更值钱的是依存句法分析。每个token都有dep_依存关系和head父节点能直接告诉你谁修饰谁for token in doc: print(f{token.text} - {token.dep_} - {token.head.text})比如buying的主语是looking对应的句型关系startup是buying的宾语dobjU.K.是修饰startup的名词修饰语。有了这层结构你就能做一件事抽谁对谁做了什么。这个能力在信息抽取场景里非常有用比如从新闻里自动提取交易关系Apple buying U.K. startup这种三元组可以直接从依存树里拼出来。关于模型大小_sm是小型模型速度快但准确率一般_md、_lg体积更大词向量维度更高在语义相似度任务上更靠谱。入门先用_sm跑通流程后面按需升级。4.3 命名实体识别与自定义管道命名实体识别是Spacy的招牌功能。doc.ents直接给出所有实体for ent in doc.ents: print(ent.text, ent.label_)输出Apple Inc. ORG U.K. GPE $1 billion MONEY Monday DATE准确率和速度都远超NLTK的经典实现尤其是在英文通用文本上。中文模型同理nlp_zh spacy.load(zh_core_web_sm) doc_zh nlp_zh(苹果公司计划以十亿美元收购一家英国初创企业。) for ent in doc_zh.ents: print(ent.text, ent.label_)如果你想把自定义规则塞进管道Spacy允许用add_pipe注册自己的组件也可以把规则匹配器EntityRuler插进去跟模型预测结果合并。这种模型规则的混合模式是实际项目里最常用的姿势——通用实体靠模型抽特定实体比如产品型号、机构简称靠规则补。5. 两个实战场景检验工具新闻文本与医疗问诊文本5.1 新闻语料的关键信息抽取新闻文本处理是NLP最常见的落地场景之一。假设你有一批英文新闻稿想快速知道每篇在讲什么。用NLTK做词频统计再用Spacy做实体识别组合起来效率非常高。首先用NLTK清洗和统计from collections import Counter from nltk.corpus import stopwords from nltk.tokenize import word_tokenize news Apple Inc. is looking at buying U.K. startup for $1 billion. The company announced the deal on Monday. words [w.lower() for w in word_tokenize(news) if w.isalpha()] filtered [w for w in words if w not in stopwords.words(english)] print(Counter(filtered).most_common(5))词频能告诉你文本的主题词但回答不了到底发生了什么。这时候Spacy的实体和依存就派上用场了doc nlp(news) subj [token.text for token in doc if token.dep_ nsubj] for ent in doc.ents: print(ent.label_, ent.text)拿到实体列表后再结合dobj、dative等依存关系就能拼出Apple Inc.收购U.K. startup这样的事件骨架。我实际做新闻聚合类小工具时就是这么干的——先用Spacy批量抽取实体和核心动词再用NLTK做词频辅助排序速度和效果都过得去。5.2 医疗问诊文本的初探再看在线医生这类场景。很多医疗科普网站或在线问诊平台积累了海量患者描述文本比如咳嗽三天伴有发热最高体温38.5度喉咙痛服用布洛芬后症状缓解。这类文本的NLP处理和新闻完全不同。首先是术语多其次隐私敏感再者医学实体类别和通用实体不一样。先跑一下通用模型看效果text_med 咳嗽三天伴有发热最高体温38.5度喉咙痛服用布洛芬后症状缓解。 doc nlp_zh(text_med) for token in doc: if token.pos_ in (VERB, NOUN): print(token.text, token.pos_, token.dep_) for ent in doc.ents: print(ent.text, ent.label_)通用模型对38.5度能识别出大概的量词关系但对布洛芬咳嗽发热这类医学实体往往标注不准甚至直接不识别。这个现象很正常通用模型没有见过足够多的医学语料。我的处理思路是规则字典通用模型组合用Spacy的EntityRuler内置一份常见症状、药物、检查项目的词典把规则插到NER之前让规则实体优先匹配import spacy from spacy.pipeline import EntityRuler nlp_med spacy.load(zh_core_web_sm) ruler EntityRuler(nlp_med) patterns [ {label: SYMPTOM, pattern: 咳嗽}, {label: SYMPTOM, pattern: 发热}, {label: DRUG, pattern: 布洛芬}, {label: SYMPTOM, pattern: [{LOWER: {IN: [喉咙痛, 头痛]}}]}, ] ruler.add_patterns(patterns) nlp_med.add_pipe(ruler, beforener)这样再跑同一段文本咳嗽和发热就会被标成SYMPTOM布洛芬被标成DRUG准确率立竿见影。这也解释了为什么实际医疗NLP项目都不会只依赖通用模型——要么自己用医学语料微调模型要么准备一份高质量术语词典。5.3 两个库在同一任务上的协作模式这两个案例里NLTK和Spacy的分工已经很明显了。我的习惯是文本清洗、停用词过滤、词频统计这类轻量预处理交给NLTK因为它语法直观、数据包独立、改起来随意词性标注、依存句法、命名实体识别这类重结构化交给Spacy因为它一句nlp(text)就能拿全结果。有人会觉得既然Spacy什么都能做为什么还要NLTK一个很实际的理由是NLTK自带的语料库和算法实现在做学术实验、写教学案例、对比不同分词器效果时无可替代。比如你要对比PorterStemmer和SnowballStemmer的差异、要统计一个语料库的词频分布NLTK直接给函数换Spacy你得自己去写循环。两个库不是替代关系是互补关系。6. 踩坑与经验数据、编码、性能与扩展方向6.1 数据文件版本问题前面提到的punkt_tab只是数据版本坑的一个缩影。NLTK的数据包跟库本身是分开维护的经常出现库升级了、数据没跟上或者教程写的是老资源名、新版本不认的情况。我的经验是任何一次NLTK报Resource ... not found先别急着改代码第一步去确认是不是数据包没装或者名字不对。在nltk.download()的弹窗里直接搜索报错里的资源名有时候搜出来能装但名字加了版本后缀装完再把代码里的资源名改掉就行。6.2 编码问题utf-8救你两次处理中文文本时编码问题能让人崩溃。Windows默认编码是GBK直接用open()读utf-8文件会乱码。我的固定写法是with open(news.txt, encodingutf-8) as f: text f.read()输出到终端时也记得sys.stdout.reconfigure(encodingutf-8)否则Windows控制台打印中文会直接炸。这个小坑几乎每个中文NLP入门者都踩过提前写进代码里省得后面抓狂。6.3 性能对比什么时候该换Spacy我在一个约5万字的英文新闻数据集上做过简单测试。NLTK分两步跑分词词性标注耗时接近几分钟Spacy单次管道跑完同样的量用en_core_web_sm大约几十秒。如果用到lg模型或者中文模型时间会上升但总体还是Spacy占优。所以只要处理规模上百条文本我就不会再碰NLTK的标注链路了——它更适合小样本实验和教学。Spacy做批量处理时也有一点值得注意默认nlp.pipe(texts)比循环挨个调用快得多记得用pipe而不是for t in texts: nlp(t)texts [news one, news two, ...] for doc in nlp.pipe(texts, batch_size64): # 处理每一个doc pass6.4 进一步扩展的方向入门跑通NLTK和Spacy之后再有针对性地补充三块内容会很有帮助。第一是中文分词器jieba它对中文分词的支持比NLTK原生好很多配合Spacy的中文模型能覆盖大多数中文文本场景。第二是Gensim它负责主题建模和词向量是NLTK之后做文本语义分析的自然下一站。第三是HuggingFace Transformers——现在的工业级NLP基本都在用预训练模型Spacy也推出了基于Transformer的模型库NLTK在那边反而很少出现。我用NLTK和Spacy跑过不少小项目最大的体会是工具本身没有绝对优劣关键是你想要看清原理还是快速出活。入门阶段两个都别跳过NLTK帮你建立对语言处理每个环节的直觉Spacy帮你建立工程化的手感。等哪一天你做项目不再纠结选哪个库的时候说明你对NLP这行的工具地图已经大致有数了。