资讯动态

Python爬虫+NLP情感分析:从微博热搜到情绪趋势可视化

发布时间:2026/9/16 7:17:32 来源:尧图企业网站定制
不用怀疑微博热搜本身就是一座绝佳的“情绪矿场”。它不是新闻联播式的宏大叙事而是真实网民注意力在每一天每一分钟的投射。我最近做的一个项目就是先写一个Python爬虫定时把微博热搜榜抓回来再对每一条热搜话题做NLP情感分析最后把“情绪得分随时间的变化”画成可视化图表生成一份可以直接丢到浏览器里看的HTML报告。整个过程不复杂代码量也不算大但把爬虫、NLP、可视化这三块经典技能串成了一个能跑通的小闭环。这篇博文把我从接口选择、反爬处理、情绪计算到图表渲染的完整思路和踩坑记录全部写出来适合手上有点Python基础、想把爬虫和NLP结合起来做点实物的朋友参考。1. 这个项目到底做了什么先说我交付出来的东西是什么样。每天定时拉一次或者多次微博热搜榜单把每条热搜的话题名、排名、标签、热度、抓取时间存下来紧接着用NLP模型给每条热搜打一个情绪分范围是0到1越接近1代表情绪越正向反之越偏向负面或消极。把这些散落的分数按时间窗口聚合就能看到“今天上午10点热搜整体情绪偏高晚上8点明显低落”之类的大众情绪波动曲线。最后用可视化库把原始数据和趋势曲线渲染出来生成一个独立的HTML文件双击就能打开不需要起任何服务。这个项目解决什么问题呢说白了就是让“热搜”从一张就事论事的榜单变成一个可以观察公众情绪状态的采样器。比如某个娱乐事件上热搜时相关话题的评论情绪可能是负面的但整个榜单的平均情绪并不一定低而当社会新闻集中出现时整体情绪分往往会快速走低。你一眼就能从曲线里看出端倪而不是靠感觉拍脑袋。适合谁来参考呢如果你是Python爬虫刚刚入门、想做点有实际产出的练习项目这个项目很合适因为接口相对简单不用处理复杂的动态页面。如果你已经会爬虫但从来没碰过NLP这也是一个很温和的切入点——不需要训练模型不需要GPU用现成的中文情感分析库就能跑出结果。当然打算做数据可视化的同学也能从里面挑出不少图表配置的现成代码。2. 技术选型和整体设计思路动手之前我在技术选型上纠结了一阵子核心纠结点有三个数据源用哪个接口、NLP用什么工具、可视化用什么库。现在把当时的思考过程原样写出来算是给同样要做类似项目的你一个参考。2.1 数据源优先选公开接口而不是动态页面微博热搜在PC端的页面是动态加载的直接拿requests请求首页HTML拿不到完整数据必须用浏览器自动化或者分析XHR接口。浏览器自动化比如Selenium在本地调试一两分钟没问题但要定时跑、无人值守跑就很容易出岔子。所以我第一反应就是找到微博的热搜JSON接口直接请求返回结构化数据。实际测试下来移动端页面和某些内部API都能返回干净清爽的JSON数组解析难度比解析HTML低一个量级。如果你不想碰微博的接口毕竟偶尔会调整还有一个更省事的数据源可以换就是各大新闻客户端的热榜接口比如今日头条热榜、百度热搜榜它们同样有公开的JSON接口。我在项目里把抓取逻辑封装成了统一的类换数据源时只需要改URL和字段映射这样即使微博接口变了整套流程也能快速切到备胎上。热门新闻榜虽然和微博热搜的口味不完全一样但用来做情绪趋势分析完全够用。2.2 NLP方案SnowNLP够用就够了别一上来就上大模型微博热搜本质上是短文本每条就是一个十几字的话题名。对这种长度极短、上下文缺失的文本很多人第一反应就是上BERT或者大模型其实没必要。我在选型时主要考虑的是第一整个项目要能跑在一个普通笔记本上不能依赖远程API第二处理速度要快一次抓50条热搜要在几秒内完成情感打分第三我需要的是相对稳定的趋势信号而不是对单条文本精准定性的学术级结果。最后选了SnowNLP。它是纯Python实现的中文文本处理库封装了情感分析、分词、关键词提取等功能。SnowNLP的sentiments接口直接返回0到1之间的情感倾向分不需要自己写模型逻辑。它底层是一个基于商品评论语料训练出来的朴素贝叶斯分类器对短句的适应性尚可虽然打分绝对值不能深究但作为趋势参考很稳定。实测下来一天内连续抓取十几次热搜把分数画成曲线能明显看到不同时段的情绪差异这就达到项目目的了。以后如果你想玩更高级的可以保留SnowNLP作为baseline再找几天数据分别用BERT和ChatGLM跑一遍做对比。但要记住永远别在第一步追求最贵的方案先拿最顺手的方案跑通全流程。2.3 可视化用Pyecharts生成静态HTML报告现在的可视化选择至少有四个层次Matplotlib、Seaborn、Pyecharts、大屏专业工具比如DataV、帆软。我最后选的是Pyecharts理由非常直接它生成的是HTML文件里面的图表是ECharts渲染的支持鼠标悬停看数值、缩放、拖拽这比Matplotlib生成的静态PNG好看太多同时它是自包含的HTML不需要部署服务器拿到哪都能打开特别适合每天自动生成一份报告然后扔到邮箱附件的场景。当然Pyecharts也有坑比如图表默认宽度不适合手机查看、中文字体偶尔会虚这些我都会在第5节里讲到。但你如果只是自己看、发个朋友圈完全够用。大屏工具虽然炫酷但配置成本和学习曲线明显偏高单人小项目不划算。2.4 并发和代理到底要不要上很多人在微博热搜爬虫项目里看到“爬虫”两个字就想上多线程、分布式、代理池恨不得搞出一套企业级采集系统。我的经验是这个场景真不需要。微博热搜接口是一个极轻量的榜单接口一次请求返回的数据撑死几十KB频率控制在几分钟一次即可。你面对的瓶颈完全不在吞吐量而在请求频率控制——访问太频繁容易触发封禁太稀疏又错过情绪变化的关键点。我实测下来对同一个接口每隔5分钟拉一次持续抓两天使用正常浏览器的User-Agent头在没有登录状态下基本是安全的。如果微博临时加强风控最常见的表现是接口返回一段需要安全验证的JSON脚本这时候不需要慌把抓取频率降到15分钟一次再等一段时间就能恢复。真正需要代理池和多线程的场景是采集全网大量用户主页、爬评论流那种大规模任务才配得上分布式采集架构。做小项目时懂得克制才是最大的技术。3. 数据采集从接口到结构化数据数据采集是整个项目的地基这部分直接影响后面分析的输入质量。先说结论微博热搜实际接口返回的是JSON数组每条热搜是一个包含话题名、热度信息、排名、标签字段的字典。抓取流程就是“构造请求 → 拿JSON → 解析字段 → 存库”。下面拆开讲。3.1 找到可用的热搜JSON接口我实际用的接口格式是https://weibo.com/ajax/side/hotSearch返回结构中包含realtime数组。但微博接口经常改版如果这个接口失效了还有一个更老的移动端接口可以兜底https://mobile.weibo.com/api/container/getIndex?containerid106003type%3D25%26t%3D3%26disable_hot%3D1%26filter_type%3Drealtimehot用的过程中接口偶尔会失效但基本思路不会变先打开微博网页版按F12打开开发者工具切到Network面板勾选Fetch/XHR筛选再手动刷新热搜页面观察哪个请求返回了包含热搜话题名的JSON那个就是你要的接口。这种“用开发者工具逆向接口”的能力比背下任何固定URL都重要值得多练几次。3.2 请求头伪装与JSON解析接口找到之后直接用requests请求可能会返回一个需要登录的提示页这是反爬策略。我的处理办法是把请求头尽量伪装成正常浏览器访问重点是User-Agent和Referer两个字段。import requests headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1, Referer: https://weibo.com/, Accept: application/json, text/plain, */*, } url https://weibo.com/ajax/side/hotSearch resp requests.get(url, headersheaders, timeout10) data resp.json() for item in data[data][realtime]: note item.get(note, ) label item.get(label_name, ) rank item.get(rank, 0) num item.get(num, 0) print(rank, label, note, 热度参与值:, num)不建议直接用PC版的Chrome UA实测下来移动端UA的稳定性更好可能是因为移动端接口被官方优先维护。请求头不一定要全部抄齐全但UA和Referer两样缺一不可。我遇到过只带UA忘记带Referer的情况接口偶尔返回200但内容是空列表排查了半天才发现是Referer缺失导致的。3.3 字段清洗热搜词里的“爆、热、新”都是信号原始JSON里每个热搜条目包含多个字段需要根据自己的目标做取舍。我在项目里保留了四个字段排名、话题名、标签、热度参与值。其中label_name就是我们平时在热搜榜上看到的“爆”、“沸”、“热”、“新”等标它们虽然不参与情感打分但是一种绝佳的“情绪烈度”指标。我会在后续分析里把“爆/沸”当成高关注度样本单独统计观察它们在一天内的分布曲线。热度参与值num是微博自己算出来的参与指数注意它并不是真实讨论量只是一个经过放大的相对值。我在对比不同热搜时只把它当作“话题热度权重”使用绝不解读成真正的阅读量。在情绪聚合时也不用它做加权因为情绪分和热度是两回事强行加权反而会把“小众但极端”的情绪信号淹没。3.4 存储方案CSV可能比数据库更适合个人小项目数据量不大时我用CSV存储根本不存在性能瓶颈。一天抓48次每30分钟一次每次最多50条热搜一天也就2400行数据。这个量级用pandas处理毫无压力CSV文件还能直接用Excel打开方便人工核对。用CSV的关键是要把文件初始化和追加写分的清清楚楚第一次爬取写表头和内容之后每次都用追加模式写入避免全局覆盖。import csv, time, os filename weibo_hotsearch.csv fieldnames [ts, rank, label, note, num] def save_items(items): new_file not os.path.exists(filename) with open(filename, a, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesfieldnames) if new_file: writer.writeheader() for it in items: writer.writerow(it)注意编码一定要用utf-8-sig不是utf-8否则用Excel打开CSV时中文就会变成乱码。这个坑我踩过不止一次。如果你的数据量真的积累到几百万行以上再考虑迁移到SQLite或者MySQL在那之前CSVparquet就是效率最高的方案。3.5 抓取频率和增量去重设计给定时任务做抓取频率时我考虑的是“能观察到情绪变化的最小间隔”。热搜榜并不是每秒都在变话题的更替通常以小时为单位所以我选择的是每30分钟抓一次。如果你想看到更细腻的波动可以缩短到10分钟但注意全天24小时不间断的高频抓取对账号IP压力更大风险会明显上升。另一个容易被忽视的坑是增量去重。同一话题在一天内会反复出现在热搜榜上比如“某某电影官宣”可能从早挂到晚。如果不做去重趋势图里同一话题会重复计数情绪聚合结果被这个“钉子户”反复影响。我的做法是保存前先判断note 当天日期是否已存在如果存在就跳过新抓取语句只更新原有记录的热度值和时间戳。这样既能保留话题首次出现时间又能更新它的热度变化轨迹。4. NLP情绪分析从热搜文本到情绪分数数据存好只是第一步接下来才是这个项目最有趣的部分让机器判断每条热搜话题是偏正面还是偏负面。这一套流程里文本预处理比模型选择更重要短期文本的打分结果也千万不要过度解读。4.1 文本预处理去掉话题符、网址、表情、多余空格微博热搜的话题名通常直接是“xxx”不带#话题#外壳但偶尔还会有一些如“#xxx#”格式的数据混进来。另外有些热搜里的关键词会带特殊标记如“荐”、“新”这些视觉标签本质上是榜单运营行为不是内容文本的一部分需要剔除掉否则会让情绪分析器混淆。我写的清理函数大概长这样import re def clean_text(text: str) - str: # 去掉话题符号 text text.replace(#, ).strip() # 去掉URL text re.sub(rhttps?://\S, , text) # 去掉运营标签爆、沸、热、新、荐等 for tag in [爆, 沸, 热, 新, 荐]: text text.replace(tag, ) # 去掉emoji和特殊符号 text re.sub(r[\U0001F300-\U0001FAFF\u2600-\u27BF], , text) return text.strip()这里最后一部去emoji的坑特别值得说。热搜话题里偶尔会带着⚽、这类emoji它们本身语义丰富但对SnowNLP来说就是一堆噪声。如果不清理同样的文本因为带了笑脸emoji情绪分可能从0.6跳到0.8这显然是不合理的。所以我在统一规则下把所有emoji全部丢弃保证情绪打分依据的是文本本身而不是排版符号。4.2 基于SnowNLP的情感打分预处理之后打分本身非常直白from snownlp import SnowNLP def sentiment_score(text: str) - float: text clean_text(text) if not text: return 0.5 return SnowNLP(text).sentiments我在使用中给情绪段位做了一个可读性映射大于0.6算正向小于0.4算负向中间算中性。但必须诚实提醒你SnowNLP的绝对值存在明显偏差。比如它是在商品评论语料上训练的天生对“好吃”、“好用”敏感对“怒斥”、“问责”这类话题词的辨别力一般。同样一句话放到不同上下文里打分结果可能有差异。所以我在项目里从来不拿单条分数说事只看一天内的相对变化和整体分布。4.3 情绪趋势怎么从零散分数变成时间序列单次抓取50条热搜每条一个情绪分直接画散点图是看不出规律的。正确做法是把一整天切成时间窗口每个窗口内做聚合统计。我用的粒度是“每次抓取作为一个窗口”因为我的采样间隔本身就是等宽的——每30分钟一个点把该次抓取的所有热搜情绪分取平均数和方差。更细一步如果我们不仅关心平均水平还关心“分化程度”可以同时计算正向占比。也就是每次抓取中分数大于0.6的话题数量占总话题数的比例。情绪均值回答“大众整体爽不爽”正向占比回答“有多少话题让人舒坦”两个维度配合着看尤其在突发负面事件时正向占比会比均值下降得更明显反应更灵敏。import pandas as pd df pd.read_csv(weibo_hotsearch.csv, parse_dates[ts]) df[score] df[note].apply(sentiment_score) df[date_hour] df[ts].dt.floor(30min) trend df.groupby(date_hour).agg( score_mean(score, mean), score_std(score, std), positive_ratio(score, lambda x: (x 0.6).mean()) ).reset_index()这里的date_hour字段是关键。如果不把时间戳对齐到半小时整点那么8:31和8:59抓的两批数据在画图时就会被认成两个独立的时间点哪怕它们大概率指的是同一批热搜曲线也会变得抖动严重。4.4 把短文本情绪分析的局限说清楚老实说用SnowNLP做热搜情绪分析结果在方法论上是有很多槽点的。热搜话题往往高度浓缩表面中性的措辞比如“某球队出局”也具有强烈的情绪指向但模型只能看到字面。而且微博热搜是经过筛选的公开榜单它反映的是平台编辑选择和流量分发机制不是全网真实的情绪分布。这个偏置必须在交付报告时标注清楚。我的态度是这个项目并不追求“科学严谨的情绪普查”而是提供一个低成本的、可重复观测的情绪趋势工具。在同一套规则、同一时段连续跑很多天之后你自然就能建立起自己的基线认知——某天的分数曲线比平时低0.1就算异常波动。这种相对比较才是这类轻量级情绪分析的正确用法而不是试图给每条热搜定性为“正面”或“负面”。5. 可视化呈现让数据自己讲故事分析做得再到位落在干巴巴的数字表格上就没有说服力。我习惯把每一天的分析结果渲染成一个图文并茂的HTML报告包括三块内容热搜情绪趋势折线图、正负占比堆叠图、热搜话题词云。全部用Pyecharts实现一次性渲染保存。5.1 用Pyecharts画情绪趋势折线图核心代码大概下面这样用Line类创建折线图把上一步计算出来的score_mean作为纵轴时间戳作为横轴。我额外加了两个辅助元素一条0.5的中性参考线和score_std填充的区间范围带用AreaStyle实现让瞥一眼就能看到波动的幅度。from pyecharts.charts import Line from pyecharts import options as opts line ( Line(init_optsopts.InitOpts(width1200px, height500px)) .add_xaxis(trend[date_hour].dt.strftime(%H:%M).tolist()) .add_yaxis(情绪均值, trend[score_mean].round(3).tolist(), is_smoothTrue, markline_optsopts.MarkLineOpts(data[opts.MarkLineItem(y0.5)])) .set_global_opts(title_optsopts.TitleOpts(title微博热搜情绪趋势), tooltip_optsopts.TooltipOpts(triggeraxis)) ) line.render(trend_line.html)5.2 中文分词和词云制作除了折线图词云是这个项目里最出效果的一张图。词云本身不负责判断情绪但它能告诉你当前热度最高的中文词汇是什么刚好可以拿去验证情绪分析的解读是否合理。词云的生成需要先分词推荐使用jieba库import jieba from collections import Counter from pyecharts.charts import WordCloud def build_wordcloud(df): texts .join(df[note].tolist()) words jieba.lcut(texts) # 简单停用词过滤 stop_words set([热搜, 微博, 发布, 回应, 视频, 最新]) counter Counter(w for w in words if len(w.strip()) 1 and w not in stop_words) data [(word, count) for word, count in counter.most_common(60)] wc WordCloud() wc.add(, data, word_size_range[20, 80]) wc.render(wordcloud.html)这里有个小坑调用WordCloud.add时如果你不传shapediamond等参数默认形状是云朵效果还行。但如果你用Pyecharts内置的词云形状配置会发现有些形状需要额外引入JS库在离线打开的HTML里会加载失败。我的建议是就保留默认形状别在形状上花太多时间毕竟信息传递比形状重要。5.2.1 解决Windows下词云中文乱码如果你在Windows上跑词云或者用某些Pyecharts版本直接渲染中文标签有可能会遇到中文变成方块的问题。这通常是因为Pyecharts内部组件找不到中文字体。解决问题的办法不是去修改全局字体配置而是设置一个明确的字体路径在WordCloud.add里不直接支持指定字体但可以修改opts.InitOpts里的graphic或者干脆在运行时把默认字体配置成Microsoft YaHei。如果你懒得折腾还有一个更省事的思路词云图全部用“分词后的词条”配词频显示而不是原始长文本这样大部分环境下字体问题会自动消失。5.3 把多张图表拼成一份HTML报告Pyecharts单张图表render出来的HTML文件是完整的独立页面可以做轮播切换但一次想展示多个图表时拼页面成了问题。我的方案是直接用Page容器把多个图表对象组合起来一次性渲染from pyecharts.charts import Page page Page(layoutPage.SimplePageLayout) page.add(line) page.add(bar) page.add(wordcloud) page.render(report.html)如果愿意更进一步可以在Page之外套一层自己的HTML模板把标题、统计摘要、数据说明都加进去做成日报的样子。我自己的报告标题是“微博热搜情绪日报”头部放抓取时间和热搜总数下面依次是情绪趋势折线、正负面占比柱状图、词云。这样一份报告发给朋友或者放到博客里都比较像样。5.4 多日对比的可视化扩展等数据积累几天后可以做一个多日对比的扩展页。做法很简单每日数据各算一份情绪均值序列画到同一张折线图里用不同颜色区分。这时候有意思的现象会自己冒出来工作日和周末的情绪曲线形态通常明显不同工作日上午情绪偏低、晚间回升而周末整体波动更大。这种对比不需要任何额外爬虫只需要把历史CSV读进来按日期分组循环画线即可。6. 常见问题与排查技巧实录做这个项目的过程中我踩了不少坑有些坑在文档里根本找不到我把它们整理成一张速查表希望能帮你少走弯路。问题现象可能原因解决办法接口返回200但JSON没有数据没有带Referer或Cookie失效补全Referer头重新登录网页获取新Cookie请求几次后返回一段加密JS触发微博风控临时限制立即停止高频抓取把间隔调到15~30分钟等待恢复CSV用Excel打开中文乱码写入时用了utf-8而非utf-8-sig把编码改成utf-8-sig保存同一话题重复计数趋势图抖动缺少增量去重逻辑保存前按“话题名日期”判断是否已存在SnowNLP打分几乎都在0.5附近文本太短或预处理器误删太多检查清洗函数是否误删了有效词尝试保留两个字节以上的字符Pyecharts生成的HTML里中文字体发虚系统缺少默认中文字体配置在InitOpts中指定字体或用默认英文模式渲染后再换字体凌晨抓的数据为0热搜榜可能处于更新低谷查询时段范围只保留8:00~23:00的数据用于分析6.1 请求频率其实比UA更重要很多爬虫新人有一个误区认为只要UA伪装得足够像真人就可以肆无忌惮地高频请求。实际上在微博这类大型平台上账号行为模型会综合判断请求频率、接口调用方式、时间分布等多个维度UA只是其中一环。我在项目里锚定的抓取间隔是time.sleep(1800)也就是30分钟一次。这个频率足够低低到微博正常用户刷个手机产生的请求都比它频繁得多所以长期跑下来的封禁风险很小。6.2 热搜字段格式变化时的自动兜底微博接口偶尔会在字段上做调整比如label_name改成label或者num变成字符串类型。我在解析层做了一个防御式写法所有字段都用字典的get方法并且给每个可能变化的字段写一个提取函数万一字段缺失就返回默认值。这样做的好处是接口小改时采集任务不会直接崩溃最多某些字段变成默认值看报告时也能及时发现数据质量异常。7. 这个项目后续还能怎么扩展做完整套流程后你会发现这个框架本质上是一个“任意平台热点情绪监测”的模板。换一个数据源就是一个新的分析场景。我自己已经把它扩展成了三个变体第一个是监控B站热门排行榜分析科技区视频标题的情绪倾向第二个是抓新闻客户端的推荐频道观察每日要闻的情绪起伏第三个是滚动追踪某个特定热点的生命周期从首次上榜到完全消失绘制“话题关注度情绪分”双轴时间线。更进一步如果你对NLP还想深入可以把SnowNLP替换成预训练的BERT模型用transformers库加载中文情感分类模型这个升级并不难只需要重写sentiment_score函数内部实现外部接口保持一致。替换之后单条热搜的定性准确率会有明显提升但因为BERT的批处理特性你最好不要一条一条地调用模型而是一次把50条热搜文本全部丢进去批量推理。跑了一段时间之后我更确信这种“爬虫NLP可视化”的组合拳适合作为独立开发者的标配工具箱。它不需要昂贵的服务器不需要GPU不需要复杂的分布式架构一台普通电脑加一个定时任务就能自动产出有价值的数据观察。你要是感兴趣完全可以在我的代码基础上把数据源换成自己关心的领域——游戏、数码、体育、股市原理完全相同收获的洞察却完全属于你自己。最后分享一个小技巧情绪曲线结合当日重要事件来看比单看曲线本身有趣十倍那些突然的尖峰和低谷基本都是当天最值得回看的记忆点。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价