先说说这个项目值不值得做。爬虫、情感分析、词云可视化,这三个词单独拎出来都是烂大街的教程主题,但合在一起做一个完整闭环,能跑通的案例其实不多。张雪峰的微博是个很有意思的分析对象——他的内容以升学规划、考研指导、就业建议为主,语言风格鲜明,情绪表达直接,评论区互动性也强。这种高情绪浓度的数据源,用来练手情感分析再合适不过了。而且整个流程走下来,你等于把Python爬虫、文本清洗、NLP情感判断、数据可视化这一整套技能全串了一遍,比零散刷教程有用得多。
这篇文章我会从零开始,把整个项目的技术选型、爬虫实现、情感分析原理、词云生成细节全部拆开讲,代码直接给到能跑的程度。过程中踩过的坑也一并列出来,尤其是微博反爬、中文分词、字体乱码这几个老生常谈但新手必炸的点。
1. 项目全景:先看清楚要做什么,再动手写代码
1.1 一句话拆解这个项目的核心链路
整个项目本质上是一条数据处理流水线:数据采集 → 数据清洗 → 情感打分 → 可视化呈现。爬虫负责拿到原始微博文本,这一步解决的是“有没有数据”的问题;情感分析解决的是“这些数据表达了什么情绪倾向”的问题;词云解决的是“这些数据里反复出现的关键主题是什么”的问题。三条环环相扣,缺一个都不完整。
说实话,这个项目的门槛并不高,代码量也不大,核心逻辑大概两百行左右。但它覆盖的知识面非常广,涉及的每个环节都有独立的坑。比如爬虫阶段要处理登录态和接口签名,情感分析阶段要面对中文网络用语的分词难题,词云阶段要解决字体渲染问题。这些坑如果你不实际走一遍,看再多教程也记不住。
1.2 技术选型:为什么是 Python + SnowNLP + WordCloud
技术选型大概是我被问得最多的问题之一。很多人一上来就上 scrapy 加大模型,把项目搞得非常重。其实对于这种个人分析类项目,我的建议是:
| 模块 | 我用的方案 | 备选方案 | 选型理由 |
|---|---|---|---|
| 爬虫框架 | requests + 模拟接口调用 | scrapy、playwright | 接口直连效率最高,requests 足够轻量 |
| 数据存储 | pandas DataFrame | SQLite、CSV | 数据量不大,pandas 处理方便 |
| 情感分析 | SnowNLP | 百度AI开放平台、大模型API | 本地跑不依赖外部服务,免费且方便批量处理 |
| 分词 | jieba | hanlp、pkuseg | 生态好,停用词表好找,社区案例多 |
| 词云生成 | wordcloud | pyecharts、stylecloud | wordcloud 定制性强,mask 图形支持效果好 |
| 可视化 | matplotlib + wordcloud | plotly、ECharts | matplotlib 和 wordcloud 配合最默契 |
这套组合的特点是轻量、全本地化、不花钱。情感分析那里我特意没用大模型 API,原因有两个:一是微博文本量大,逐条调 API 时间和成本都不小;二是大模型做情感分析确实准,但可解释性差,你很难说清楚它为什么给出这个判断。SnowNLP 虽然精度糙一点,但它基于情感词典和朴素贝叶斯,每个打分结果你都能倒推逻辑,在后期的阈值调整环节反而更方便。
注意:SnowNLP 默认模型是在电商评论语料上训练的,直接拿去跑微博文本会有偏差,这点后面我会专门讲怎么校准。
1.3 项目整体流程与预期产出
流程理解到位了,代码写起来就顺畅了。我用下面这张图梳理了整体流程(不用代码画,直接脑补):
第1步:目标分析。明确要爬取的用户,分析微博的接口规律,找到数据接口。
第2步:数据采集。构造请求参数,带上登录 Cookie 请求接口,拿到 JSON 数据后提取文本、时间、点赞数、转发数等字段。
第3步:数据清洗。去重、去空值、去短文本(比如只有表情或标点的微博)、去链接和 @ 用户,得到一个干净的文本列表。
第4步:情感分析。遍历文本列表,调用 SnowNLP 得出每一条微博的情感倾向分数。
第5步:词云生成。对清洗后的文本做 jieba 分词,过滤停用词后输入 WordCloud 生成词云图。
第6步:结果解读。结合情感时间序列和词云热点,总结目标账号的内容特征和情绪趋势。
这个流程看起来简单,但每个步骤里的细节都足够你写一篇文章。接下来我从爬虫这个第一道关卡开始拆。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据采集:微博爬虫从接口分析到代码落地
2.1 微博的数据接口是怎么被扒出来的
爬虫的第一步不是写代码,而是看懂网页数据从哪来。打开张雪峰的微博主页,随便向下滚动几次动态加载,开发者工具里的 Network 面板会记录到大量的 XHR 请求。排除掉那些埋点、推荐、广告接口之后,你会发现其中有一个请求格式特别工整,返回值结构极其清晰,ID 和文本字段一目了然。
这里我直接说结论:微博移动端的主页微博加载接口是。
code复制URL: https://m.weibo.cn/api/container/getIndex
关键参数: type=uid, value={用户uid}, containerid={containerid}, since_id={分页游标}
其中 containerid 是最关键的参数,它的格式是 107603 加上用户 uid。比如你的目标用户 uid 是 1234567890,那 containerid 就是 1076031234567890。这个规则是固定的,网上很多老教程写的是需要先访问 profile 页面才能拿到 containerid,实际上只要你知道了 uid,直接拼出来就行。
since_id 是分页游标,第一次请求不传,返回的 JSON 里会有 data.cardlistInfo.since_id 字段,取出来拼到下一次请求里,循环往复,就能一页一页拿到全部微博。
提示:不建议用 PC 端接口去拉,PC 端的反爬做得很严,请求头校验多还容易触发验证码。移动端接口 m.weibo.cn 相对宽松,数据结构也更干净,适合做个人项目。
2.2 requests 模拟请求,手写一个极简爬虫
确定了接口和参数规则,代码就很简单了。核心逻辑就是用 requests.Session 保持一个会话,带上你的登录 Cookie 去请求数据,然后解析 JSON,提取列表。
python复制import requests
import json
import time
import random
import pandas as pd
class WeiboScraper:
def __init__(self, cookie_str):
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X)',
'Referer': 'https://m.weibo.cn/',
'X-Requested-With': 'XMLHttpRequest',
})
self.session.headers['Cookie'] = cookie_str
def get_user_home(self, uid, since_id=None):
params = {
'type': 'uid',
'value': uid,
'containerid': f'107603{uid}',
}
if since_id:
params['since_id'] = since_id
url = 'https://m.weibo.cn/api/container/getIndex'
resp = self.session.get(url, params=params, timeout=10)
resp.raise_for_status()
return resp.json()
def parse_weibo_list(self, json_data):
cards = json_data.get('data', {}).get('cards', [])
results = []
for card in cards:
if card.get('card_type') != 9:
continue
mblog = card.get('mblog', {})
texts = self._clean_html(mblog.get('text', ''))
results.append({
'id': mblog.get('idstr', ''),
'text': texts,
'created_at': mblog.get('created_at', ''),
'reposts_count': mblog.get('reposts_count', 0),
'comments_count': mblog.get('comments_count', 0),
'attitudes_count': mblog.get('attitudes_count', 0),
})
return results
def _clean_html(self, html_text):
"""粗暴但有效的HTML标签清理"""
import re
text = re.sub(r'<.*?>', '', html_text)
text = re.sub(r'http\S+', '', text)
return text.strip()
这里有个非常关键的细节:微博正文返回的是 HTML 片段,不是纯文本。比如 <a href="/n/某用户">@某用户</a> 这种格式很常见,表情 也会带着标签一起返回。如果你直接存这份 HTML 文本,后面分词和词云的时候会得到一堆 <、>、span 之类的垃圾 token,清洗阶段非常痛苦。所以我专门封装了一个 _clean_html 方法,第一步先用正则把标签剥掉,再把 URL 链接去掉。
2.3 Cookie 获取和反爬规避,这两个坑早晚要踩
代码写好了,怎么跑起来才是关键。微博现在登录态非常严格,直接 requests 不带 Cookie 去请求,大概率拿到的是一份需要登录的提示文案,JSON 结构解析不了。
Cookie 获取方式:用浏览器打开 m.weibo.cn,登录你的账号,按 F12 打开开发者工具,Network 里随便复制一条请求的 Cookie 头,粘贴到代码里的 cookie_str 参数就行。
注意 Cookie 不是永久有效的,实测下来快的话几小时就失效,慢的话能撑一两天。我建议做一个检测函数,每次请求前先用轻量接口验证一下登录态,比如请求用户信息接口,如果返回 code 为 -100 或者其他错误码,就提醒重新更新 Cookie。否则你以为程序在跑,实际上全在报错,浪费时间。
反爬规避这块,核心就三个字:别太勤。微博的接口限制不像淘宝京东那么凶残,但也不是无限放水。实测下来,每两次请求之间至少要睡 2 到 4 秒,并且要加随机波动。如果连续请求超过几百次,依然会触发滑块验证,这时候基本就要停手了。
python复制def crawl_all_weibos(self, uid, max_pages=50):
all_weibos = []
since_id = None
for page in range(max_pages):
json_data = self.get_user_home(uid, since_id)
parsed_list = self.parse_weibo_list(json_data)
if not parsed_list:
break
all_weibos.extend(parsed_list)
card_list_info = json_data.get('data', {}).get('cardlistInfo', {})
since_id = card_list_info.get('since_id')
if not since_id:
break
time.sleep(random.uniform(2, 4))
print(f'第 {page+1} 页,累计 {len(all_weibos)} 条')
return pd.DataFrame(all_weibos)
把 max_pages 设成 50 是因为我实测这个账号一页大概能拉到 20 条左右,50 页就是 1000 条。这个数据量对情感分析和词云来说完全够用了,再多也只是边际收益递减。
3. 情感分析:让机器判断微博的情绪倾向
3.1 情感分析技术路线:词典派和深度学习派怎么选
拿到文本数据之后,下一关就是情感分析。这里先普及一下背景。目前主流的情感分析技术路线分为两派:基于情感词典的规则派和基于标注数据的深度学习派。
情感词典流派的核心逻辑是:先维护一个带情感分值的情感词表,比如“开心”+2分、“难过”-2分,再叠加否定词、程度副词的权重修正,最后把所有词的分值累加起来,根据正负号判断情绪。优点是解释性强、不依赖标注数据;缺点是处理不了复杂语境和反讽。
深度学习流派则是在大规模标注语料上训练一个分类器,模型自己学习到词语组合和语境信息。优点是准确率高,能捕捉“这也太厉害了吧”这种反讽语气;缺点是需要标注语料,调参和部署成本也更高。
对个人项目来说,我的选型逻辑是跑一遍先看效果。SnowNLP 底子上是词典流派加朴素贝叶斯分类的组合,训练集是京东商品评论,对“好用”“快递快”“质量好”这类电商文本表现良好,但对微博这种短文本加网络用语的环境,准确率会打个折扣。不过它免费、轻量、本地跑,还有内置的 sentiment 接口,非常适合这个项目的前期验证。
3.2 SnowNLP 实战:批量打分与阈值设定
直接看代码:
python复制from snownlp import SnowNLP
def sentiment_analyze(text):
if not text:
return None
s = SnowNLP(text)
return s.sentiments
df['sentiment_score'] = df['text'].apply(sentiment_analyze)
就这么几行,每个文本都能得一个 0 到 1 之间的分值。0 到 0.4 判定为负面情绪,0.4 到 0.6 判定为中性情绪,0.6 到 1 判定为正面情绪。
但我要说的是,这个阈值看起来合理,直接用却不一定行。因为 SnowNLP 的评分分布可能整体偏高,你用 0.6 做正面的下限,可能80%的微博都被判成正面,分析结果毫无区分度。所以拿到分数之后,第一件事不是急着解读,而是要看一下整体的分布情况:
python复制df['sentiment_score'].describe()
如果 75 分位数已经到了 0.8,说明模型打分明细偏乐观,需要把正负面的判定阈值整体调高。我的经验是先画个直方图看分布形态,再取中位数和四分位数来定阈值,这样每一条结果才是有区分的。
提示:情感倾向分数是一个连续值,不要死盯着“正/负/中性”三分类。把分值本身作为一个特征去分析趋势(比如一条微博有没有引发高赞、转发量跟情绪有没有相关),比单纯分类更有价值。
3.3 批量分析与情感时间线
有了每条微博的情感分数,就能做很多有意思的分析了。比如把微博按月份聚合并计算平均情感分,画出情感走势线,看这个账号在不同时间段的情绪变化。这里有个重要细节:微博的 created_at 字段是中文格式的,比如“昨天 12:30”、“08-01”、“2019-05-01”这种,需要先做个日期解析,转换成标准时间格式。
python复制import datetime
def parse_weibo_time(time_str):
now = datetime.datetime.now()
if '刚刚' in time_str:
return now
elif '分钟前' in time_str:
minute = int(re.findall(r'\d+', time_str)[0])
return now - datetime.timedelta(minutes=minute)
elif '小时前' in time_str:
hour = int(re.findall(r'\d+', time_str)[0])
return now - datetime.timedelta(hours=hour)
elif '昨天' in time_str:
return now - datetime.timedelta(days=1)
else:
return datetime.datetime.strptime(time_str, '%Y-%m-%d')
然后就能用 pandas 的 resample 按月统计了:
python复制df['created_time'] = df['created_at'].apply(parse_weibo_time)
df.set_index('created_time')['sentiment_score'].resample('M').mean().plot()
这样你就能直观地看到,哪些月份这个账号整体情绪高亢,哪些月份低气压。再结合具体微博文本,基本就知道那段时间发生了什么重大节点事件。
4. 词云可视化:把高频关键词变成有美感的信息图
4.1 分词、去停用词,这步偷懒图就废了
词云的第一步是分词。中文没有天然的空格分隔,必须用工具把句子切成一个个有意义的词。jieba 是目前最普及的方案,玩起来很简单,但它给你的是原始分词结果,里面会掺杂大量“的”“了”“在”“是”“我”这类停用词。如果不做过滤直接扔给词云,你的图会被这些无意义的虚词霸占,真正有信息量的关键词反而排不上号。
网上有现成的中文停用词表,我一般用哈工大的停用词表加百度的停用词表合并,再手动加一批微博特定停用词。这个步骤不能省,也不能只用一份词表,因为微博上还有大量 http 链接碎片和 @ 用户名,这些都需要过滤。
python复制import jieba
import re
stopwords = set()
for line in open('stopwords.txt', 'r', encoding='utf-8'):
stopwords.add(line.strip())
# 手动补充微博特定停用词
stopwords.update({
'转发微博', '回复', '举报', '图片', '网页链接', '全文',
'分享图片', '评论配图', '收起', '展开'
})
def segment_text(text):
words = jieba.lcut(text)
clean_words = []
for word in words:
word = word.strip()
if not word:
continue
if len(word) <= 1:
continue
if word in stopwords:
continue
if re.match(r'^[a-zA-Z0-9]+$', word):
continue
clean_words.append(word)
return ' '.join(clean_words)
这里有个新手特别容易踩的坑:len(word) <= 1 的过滤条件不能省。中文里单字大多是无意义的虚词,但也不排除“涨”“跌”“好”这种有含义的单字动词。jieba 切出来的单字很多是残留的标点或者符号碎片,直接删掉不会损失太多信息。如果你想保留有含义的单字,就必须叠加一个单字词表白名单,那样逻辑就复杂了。对于词云这种看重图像整体效果的场景,一刀切删掉单字更省事。
4.2 WordCloud 中文乱码与蒙版制作
WordCloud 是 Python 词云库里的经典,但中文用户很容易栽在字库上。这个库默认用英文的 DejaVu Sans 字体,根本不认识中文字符。直接生成词云图,中文全部变成方框或者消失。
解决办法是给 WordCloud 指定一个支持中文的字体文件路径。Windows 下可以试 C:\Windows\Fonts\msyh.ttc 微软雅黑,macOS 下是 /System/Library/Fonts/PingFang.ttc。保险起见,我一般会从系统字体目录里复制一份到项目目录,写死相对路径,可以避开各种环境差异。
蒙版(mask)是让词云呈现自定义形状的关键。比如你想做一个人形轮廓的词云,可以先下载一张人物剪影 PNG,然后用 numpy 读取它的 alpha 通道作为 mask。注意 mask 图形必须是黑底白图,白色区域代表词云要排布的区域,黑色区域代表留白。
python复制from wordcloud import WordCloud, ImageColorGenerator
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
mask = np.array(Image.open('mask.png'))
wc = WordCloud(
font_path='msyh.ttc',
width=1200,
height=800,
mask=mask,
background_color='white',
max_words=150,
max_font_size=200,
random_state=42,
collocations=False
)
wc.generate(text)
plt.imshow(wc, interpolation='bilinear')
plt.axis('off')
plt.show()
max_font_size 和 random_state 这两个参数值得专门说一下。max_font_size 控制的是图中最大字体的大小,如果数据里的某个词高频到离谱,你不限制的话它会无限大,直接冲出画面。random_state 是随机数种子,固定住它就能保证每次生成出来的词云布局完全一致,方便在微调参数时做对比。
4.3 collocations 和背景色的细节
WordCloud 默认会计算二元词组(bigrams),也就是说“考研”和“专业”可能会合并成“考研专业”。这个行为对某些场景是好的,但对词云来说经常帮倒忙,因为合并后的词组概率计算会稀释排名。把 collocations=False 关掉,各词按自己的频率展示,图面干净很多。
背景色建议选 white 或者 rgba(255,255,255,0)。如果你后续要叠加到自定义背景图上,那就用透明背景。另外 scale 参数对输出图分辨率有很大影响,默认是 1,输出图片会有点糊,可以设成 2 甚至 3,视觉质量会提升不少,代价只是生成时间稍微长一点,完全可以接受。
5. 数据清洗与成果整合:如何让结果打动外行
5.1 把情感分析和词云结果拼到一个项目里
跑完前面的模块,我们手上至少有三份产出:一份带情感分数的微博明细表、一条情感时间趋势线、一张词云图。这三份产物如果只是单独摆在那,很容易让人觉得就是三个 demo 的拼盘。真正加分的做法是做一个成果整合,把它们拼进同一个维度去讲故事。
我的做法是输出一份汇总 Markdown 报告,结构大概是:
- 数据概况:爬取多少条微博、时间跨度、平均转发/评论/点赞量。
- 情感分布:正面/中性/负面占比饼图。
- 情绪趋势:按月度情感均值折线图,标注波峰波谷。
- 高频主题:词云图,结合 top20 关键词表格说明原因。
python复制# 导出清洗后的完整数据
df.to_csv('zhang_weibo_clean.csv', index=False, encoding='utf-8-sig')
# 打印整体统计
print('总微博数:', len(df))
print('时间范围:', df['created_time'].min(), '到', df['created_time'].max())
print('平均情感分:', round(df['sentiment_score'].mean(), 4))
print('正面微博占比:', round((df['sentiment_score'] > 0.6).mean(), 4))
print('负面微博占比:', round((df['sentiment_score'] < 0.4).mean(), 4))
注意:CSV 导出的时候,编码一定要用
utf-8-sig而不是utf-8。因为 Excel 默认用 GBK 打开 CSV 文件,直接用utf-8导出的话,数据里所有中文全会乱码。这个坑几乎每个人都会踩一次。
5.2 情感分析结果的偏差校准
跑完第一版结果后,你必须做一次抽样验证。粗略的验证方法是:从正面、中性、负面三类里各随机抽五十条,人工读一遍,看机器判定的跟你的主观感觉是否一致。这个环节能救回很多分析结论。
比如我第一次跑的时候,发现有一条微博内容是“我劝你千万别考研”,SnowNLP 给出了 0.8 的高分。原因很好理解,模型看到了“考研”这个词,在电商评论语料里这大概率是商品名称,没有倾向性,然后又看到了“劝你”这种疑似正面的动词语境,综合下来给了正面分。但人工读一下,这个语境明显是劝阻性的,应该算负面或中性。这种模型偏差没法从代码层面自动修复,但你可以在报告里加一段说明,把这种模型局限交代清楚,反而显得更专业。
如果一定要提升准确率,可以用爬取到的文本做半自动标注,然后重新训练 SnowNLP 的分类器。SnowNLP 支持传入训练样本做贝叶斯训练,但这个流程比较重,个人项目不推荐。更实际的办法是用一个更粗粒度的判断逻辑,比如把 0.3 以下算强负面、0.7 以上算强正面,只分析两端信号强的数据。
5.3 词云关键词背后的业务洞察
词云图本身是好看的,但它的价值不能停留在“好看”上。生成词云之后,我习惯把 top20 高频词拉出来,做一个简单的关键词归因分析。比如在这个项目里,词云里必然会出现“考研”“专业”“学校”“就业”“家长”“孩子”这一类的词,这些数据是可以直接读出信息来的——哪些话题是这个账号的流量密码,哪些词是跟高互动率强绑定的。
你还可以做一个进阶操作:把微博按情感分组,正面组跑一个词云,负面组跑一个词云,然后并排对比。这样能很直观地看到,哪些话题出现时这个账号的情绪是正面的,哪些话题出现时是负面的。比如“录取”“上岸”“学生”这些词大概率出现在正面词云,而“别报”“劝退”“谨慎”则集中在负面词云。这种对比分析是很能打动读者的,也是区分“会跑代码”和“会做分析”的分水岭。
6. 常见问题排查与避坑指南
这个项目代码量不大,但运行过程中会碰到各种幺蛾子。我把高频问题整理成一份速查表,按出现频率排序,你可以直接对照排查。
| 故障现象 | 可能原因 | 解决方式 |
|---|---|---|
| 请求返回 code 为 -100 或页面跳转登录 | Cookie 失效或没带上 | 重新从浏览器复制 Cookie 替换 |
| 只爬到第一页,第二页开始数据重复 | since_id 没有正确更新或者接口返回的 since_id 为空 | 打印返回的 JSON,检查 cardlistInfo 字段 |
| 爬了几百条后触发滑块验证 | 请求频率太高 | 暂停爬虫,手动过滑块,把 time.sleep 加大到 5-10 秒 |
文本里出现大量 <a href=...> |
没有做 HTML 标签清理 | 在解析阶段就调用 _clean_html |
| 情感分数几乎全在 0.9 以上 | SnowNLP 模型对短文本打分偏高 | 调整判定阈值或抽样人工校准 |
| 词云图上有大量“的”“了”“我” | 停用词表没加或者太简单 | 引入哈工大停用词表并手动补充微博领域词 |
| 词云中文全部是方框 | 没有指定 font_path | 设置一个可用的中文字体路径 |
| 词云文字重叠或排布混乱 | collocations 没关或者 mask 不规则 | 设置 collocations=False,换更规整的 mask |
| CSV 中 Excel 里中文乱码 | 编码用了 utf-8 | 导出用 utf-8-sig |
| 分页循环死循环停不下来 | since_id 一直有值但是重复 | 加一个去重判断,发现重复就 break |
6.1 爬虫阶段的两个高发问题
一个是用户信息的 containerid,网上有些教程会教你先访问用户主页,从接口响应里提取 containerid。但我想说的是,这纯属绕远路。你只要在浏览器里打开那个人的微博主页,地址栏里的数字就是 uid,拼上 107603 前缀就是 containerid,根本不用多一步请求。
另一个是微博接口返回的数据里存在置顶微博这种特殊卡片。它的 card_type 可能是 9,但 id 和普通微博不同,且会重复出现。如果不去重,你统计到的微博总数会比实际高。我在项目里加的逻辑是:
python复制df = df.drop_duplicates(subset=['id'])
这个去重可以顺便把因为分页重叠导致的重复微博也过滤掉,一举两得。
6.2 情感分析偏差的典型样本
做情感分析最容易翻车的绝不只有分词,还有语境判断。给几个我实际遇到过的例子:
第一条:“这位同学录取了北大,我真的酸了”。SnowNLP 的评分大概是 0.6 左右,中性偏正面,但“酸了”这个词在网络上表达的是一种羡慕和自嘲,单看情感词典很难把握。这种情况下,要想准确识别,需要引入更细粒度的文本理解模型,至少是带上下文语义编码的那种。
第二条:“我骂醒了一个假装努力的学生”。这句话里“骂醒”是贬义词,但整体语境是正面的劝导行为。SnowNLP 大概率会给一个中间分数。这种文本其实是很难自动判断的。
所以我对情感分析结果的定位是:宏观趋势参考,不是微观精确判断。用它下结论的时候,我会加一句“模型在反讽和处理网络新词时存在局限”,这句话既诚实,也提前打了预防针。
6.3 词云生成时的字体与尺寸问题
如果你在 Linux 服务器上跑这个项目,不会有 Windows 自带的那一堆字体。这时候要先检查系统字库:
bash复制fc-list :lang=zh
如果输出为空,那就要先安装中文字体,然后重新生成字体缓存。Debian/Ubuntu 系的命令是:
bash复制apt-get install fonts-wqy-microhei
安装之后路径一般是 /usr/share/fonts/truetype/wqy/wqy-microhei.ttc。把这个路径填进 font_path 即可。
另外一个小技巧:想输出高清大图,除了调 width/height 之外,还可以调 scale=3。这样一张 800x600 的图实际输出的像素是 2400x1800,印刷级别都够了。不过要注意 scale 过高时内存占用会急剧增加,我实测 scale=5 时一张图吃掉了快 2GB 内存,别轻易尝试。
7. 写在最后:这个项目还能怎么扩展
这个项目跑通之后,你可以顺手做几个低成本的扩展方向。第一个是批量对比,不只爬张雪峰,把同领域的其他几个大V也爬下来,做横向对比,看谁的内容情绪更极端、谁的评论区互动更高。第二个是评论情感分析,微博正文只是发布者的观点,评论区的情绪才是粉丝的真实反馈。把评论爬下来,做一次情感分析,能更立体地还原账号的影响力和舆论反馈。第三个是更准的情感模型微调,把爬下来的文本标注一小部分,用 Bert 或者 ChatGLM 做 Fine-tuning,准确率可以往上提一大截,但成本和门槛就不一样了。
就我个人实际跑完整个项目的体会来说,最值得花时间的不是代码本身,而是数据清洗和结果解读这两步。代码在网上抄都能抄到,但你知道什么时候该过滤、过滤什么、结果代表什么,这才是别人拿不走的经验。尤其是情感分析的阈值校准和词云停用词表的迭代,这两个地方做得好不好,直接决定你的最终成果是像样还是不像样。
最后再分享一个小技巧:代码写完之后,一定要把随机种子固定住。别小看这行代码,它能让你的情感分析和词云结果可以稳定复现,否则你每次跑出来的图长得都不一样,回头想调参数对比效果,就成了玄学。
到这里,整个项目就算完整走完了。从爬虫到情感分析再到词云可视化,整条链路的数据分析能力,才是这个项目最有价值的部分。
