零到全栈 · 课程
模块 6.2:让网页真的会分析文字
在安装了两个库之后,接下来我们把它们应用起来,通过
/api/analyze对外提供服务——只改接口的“内部”,不动接口的“约定”。
今天只动一处
上一节我们安装了 pypinyin 和 snownlp,并且也在 REPL 模式下验证了这两个库可用。今天把它们装进项目——通过 /api/analyze 对外提供服务。
这一节只需要动 analyze 函数的内部。 API 的访问地址不动、方法不动、请求体不动、返回的字段同样也一个不加一个不减。
看一下现状。此前做好的接口形状长这样——分析值全是写死的占位:
@app.post("/api/analyze")
def analyze(req: AnalyzeRequest):
return {
"text": req.text,
"score": 0.5,
"label": "偏平静",
"pinyin": "(模块 6 再说)",
}
分数永远 0.5,标签永远“偏平静”,拼音干脆写着“模块 6 再说”。而现在,就已经是模块 6 了,所以今天我们就把这几个值都搞活。
动手换芯
打开 backend/main.py。先在文件顶部把两位新成员请进来(import 照例放顶部):
from pypinyin import lazy_pinyin, Style
from snownlp import SnowNLP
lazy_pinyin 是上一节我们体验过的;Style 是它的搭档——用它让拼音带上声调。
再看占位版返回的四个字段:text、score、label、pinyin。
text 本来就是真的(原样回传用户输入),剩下三个是假的。我们先挑两个能直接从库里拿到的变量来下手——score 和 pinyin。
把 /api/analyze 这个 API 的代码改为如下这样:
@app.post("/api/analyze")
def analyze(req: AnalyzeRequest):
text = req.text
score = round(SnowNLP(text).sentiments, 2) # 真模型打的分
return {
"text": text,
"score": score,
"label": "偏平静", # ← 先留着,下面处理
"pinyin": " ".join(lazy_pinyin(text, style=Style.TONE)), # 真拼音,带声调
}
主要的变化有两处,一处是用 SnowNLP 计算来一个 score,把这个 score 的值给到了我们 return 的 score;另一处是用 lazy_pinyin 这个函数计算了一些拼音,把它给到了我们 return 的 pinyin。具体语法不理解也没事,我们知道发生了什么就行。
现在只剩 label 还占着位。它和前两个不一样——目前这两个库里并没有一个函数直接能够吐出“偏积极”或者“偏平静”这三个字。label 是给人看的结论,得由我们从 score 这个数字换算出来:接近 1 说“偏积极”,接近 0 说“偏消极”,中间地带算“中性”。
这段“数字 → 结论”的翻译逻辑,值得单独拎成一个小函数,放在 analyze 上面(elif 就是“else if”,我们在 5.3 手搓路由时见过这种连排判断):
def score_label(score):
if score >= 0.6:
return "偏积极"
elif score <= 0.4:
return "偏消极"
else:
return "中性"
这两个阈值(0.6 / 0.4)不是什么标准答案,是拿一批句子实测分数之后拍板的产品决定——分数是模型给的,但“多少分算积极”由我们说了算,也可以调成别的。
有了它,把 analyze 里那行占位的 "label": "偏平静" 换成 "label": score_label(score),这个 API 就已经完全写好了:
@app.post("/api/analyze")
def analyze(req: AnalyzeRequest):
text = req.text
score = round(SnowNLP(text).sentiments, 2)
return {
"text": text,
"score": score,
"label": score_label(score),
"pinyin": " ".join(lazy_pinyin(text, style=Style.TONE)),
}
其实这样来看,代码也没有多几行,但是能力已经强了许多——它真的可以做计算了。
见证效果
改完代码之后,接下来我们仍然分别启动前后端服务。
用开发模式启动前端程序:
cd ~/zero-to-tech
npm run dev
用开发模式启动后端程序:
cd ~/zero-to-tech/backend
source .venv/bin/activate
fastapi dev
直接访问 http://localhost:3000,进入文字实验室。
在输入框里打一句话,点“开始分析”:
- 拼音真的出来了——带着声调,多音字也对;(顺带一提:句子里的逗号会原样留在拼音里,因为
lazy_pinyin只翻译汉字,非汉字原样透传,这是正常的。) - 分数是模型打的——试试这几句(实测过的分数,跑出来应该一致):
| 输入 | score | label |
|---|---|---|
| 我特别喜欢这部电影 | 0.95 | 偏积极 |
| 今天的风很轻,适合把想法写下来 | 0.95 | 偏积极 |
| 太失望了,再也不来了 | 0.00 | 偏消极 |
此前欠下的账,全部都清了。
为什么前端不用改?
大家有没有注意到一件事,就是这一节,我们的前端一点都没有改。为什么呢?
这是因为虽然接口的实现变了,但是接口的约定没变:路径还是 /api/analyze,方法还是 POST,请求体还是 {"text": ...},返回还是那四个字段。变的只是约定背后的具体实现方案。
我们前面讲 API 的时候说过,“API 的调用方不需要知道服务端内部怎么实现”;今天我们站在服务方这一侧,可以体会到这句话的另一面:
只要守住约定,内部随便换。 调用方不知道、也不需要知道。
顺手看一眼 http://localhost:8000/docs,我们可以发现文档也纹丝没动,因为约定没变。
我们哪怕用 Java 把后端重写一遍,或者我们换一个新的模型来计算情感,只要这个 API 的约定不变,前端都不用改。
模型的边界
接下来我们再多试试这个情感模型。试试这两句:
| 输入 | score | label | 问题 |
|---|---|---|---|
| 今天下午三点开会 | 0.26 | 偏消极 | 一句毫无感情的话,被判了“偏消极” |
| 呵呵,真是太棒了呢 | 0.94 | 偏积极 | 阴阳怪气,它当了真 |
翻车了。为什么?
因为 snownlp 的情感模型,主要是在商品评论语料上训练出来的——它擅长判断“像评论的句子”(好评差评那种),但“下午三点开会”这种中性陈述、以及反讽阴阳怪气,都在它的训练经验之外。
这不代表 snownlp 太差,而是所有模型的共性:
模型没有“常识”,只有“训练时见过的世界”。 用任何模型之前,先弄清它的边界——知道它哪里不准,比迷信它的分数重要得多。
这句话在大模型时代照样成立——ChatGPT、DeepSeek 也有各自的边界(幻觉就是一种),只是边界更远、更隐蔽。
放眼看看:从小模型到大模型
说到大模型——也许我们已经想到了:情感分析这件事,今天完全可以调用 LLM 的 API 来做(就像 5.1 调用 DeepSeek 那样,把句子发过去,让它打分)。那为什么我们不用?
把本地小模型和云端大模型 API 这两条路摆在一起:
| 本地小模型(snownlp) | 大模型 API(如 DeepSeek) | |
|---|---|---|
| 花钱 | 免费 | 按量计费 |
| 联网 | 不需要 | 必须 |
| 速度 | 本地毫秒级 | 网络往返+推理,秒级 |
| 准确度 | 够用,边界明显 | 强得多,连反讽都懂 |
| 隐私 | 数据不出自己的机器 | 用户的文本要发给第三方 |
再往远看一步:大模型也不只有“调云端 API”这一条路。像 DeepSeek 这类开源大模型,权重是公开的,可以下载到自己的机器上跑——业内叫“本地部署”或“私有化部署”:数据不出门、也不按次付费,代价是得自己备一台够劲的机器,还得自己维护。
而且这类开源模型通常有好几种尺寸:参数量常写成 1.5B、7B、70B、671B 这样(B = 十亿)——尺寸越大越聪明,但也越吃显卡、越慢,具体部署哪一种 size 的模型,得根据自己手上的机器的配置来挑。
没有绝对的好,只有合不合适。我们的文字实验室是教学项目,免费、快、离线的本地库完全够用。从 snownlp 这样的小模型,到自己部署的开源大模型,再到云端顶配 API,是一条连续的谱系,真做产品时按预算、隐私、精度选其中一段;而不论选哪段,对我们这个项目来说都只是“再换一次芯”的事——壳,还是不用动。
我们的这个零到全栈的系列课程不会在模型这条路上继续走下去了,但是如果我们不满足 snownlp 的能力,想要把后端改为大语言模型,完全可以基于学到的知识继续改。
结语
这一节比较轻松,按照这个系列课程原本的设计思路,到这一步,我们算是把这个系列课程的开发部分搞定了,接下来就要讲部署、安全、运营了。
但是考虑到评论区中关于数据库的讨论比较多,所以我们这个模块的后续几节要开始讲一些数据库的入门知识了。依赖数据库的能力,我们的文字实验室还将会拥有记忆能力,这样,用户查过的句子就不至于用完即弃了。
下一节,给它记忆。