Python微博舆情分析系统:爬虫、情感分析与ECharts可视化实战

发布时间:2026/10/11 22:30:56
Python微博舆情分析系统:爬虫、情感分析与ECharts可视化实战 简介这是一套面向计算机专业学生与Python初学者的微博舆情分析可视化毕业设计项目围绕爬虫采集、情感分析与数据可视化三大模块展开可用于毕业设计、期末大作业或课程设计场景。资源包共146个文件约3.85MB其中19个py文件承载爬虫与情感分析核心逻辑14个html与21个js、7个css构成前端可视化界面另有6个csv数据样本、1个sql建表脚本及若干图片与字体资源整体结构完整、便于部署运行。项目代码附有详细注释新手也能看懂个人手打并获98分评价功能完善、界面美观、操作便捷。目前已有698人学习下载适合需要快速搭建舆情分析系统、理解爬虫与情感分析流程、并希望直接用于答辩展示的读者参考使用。1. 微博舆情分析系统到底在分析什么从一条热搜到一张情绪曲线一条微博发出后 30 分钟内转发、评论、点赞会形成一条陡峭的曲线这条曲线背后是情绪在扩散。所谓微博舆情分析可视化系统本质是把「非结构化的中文短文本」变成「可量化、可下钻、可实时刷新」的指标情感极性、关键词热度、地域分布、时间趋势。它要解决的不是「爬多少数据」而是「爬回来的数据怎么变成能拍板的结论」。适合谁做计算机毕业设计选这个方向的同学以及想用 Python 把 requests 爬虫、情感分析、ECharts 可视化串成一条完整链路的入门者。这套系统跑通后你能拿到一份带实时刷新能力的可视化大屏而不是一堆躺在 CSV 里的死数据。下面按「数据怎么来 → 情绪怎么算 → 图表怎么画 → 坑在哪」四步拆开讲。2. 数据从哪来微博爬虫的接口选择与反爬绕行2.1 移动端接口比网页端更稳的原因微博网页版weibo.com的 HTML 结构改版频繁用 XPath 写死路径的爬虫往往两周就翻车。常见做法是走移动端接口m.weibo.cn它返回的是 JSON字段稳定解析成本低。核心接口是https://m.weibo.cn/api/container/getIndex传containerid和page两个参数就能翻页。containerid的构造规则是100103type1q关键词其中type1表示综合搜索。这个接口不需要登录 Cookie 也能拿到前若干页适合毕业设计的数据量级几千到几万条。请求头里必须带User-Agent和Referer否则会返回 403。Referer填https://m.weibo.cn/即可。频率控制是关键单 IP 每秒超过 2 次请求大概率触发验证码。我一般会加time.sleep(random.uniform(1.5, 3.0))并在每爬 50 页后停 30 秒。import requests import time import random import json HEADERS { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1, Referer: https://m.weibo.cn/, X-Requested-With: XMLHttpRequest } def fetch_page(keyword, page): 抓取单页微博搜索 JSONkeyword 为搜索词page 从 1 开始 containerid f100103type1q{keyword} url https://m.weibo.cn/api/container/getIndex params {containerid: containerid, page: page} try: resp requests.get(url, headersHEADERS, paramsparams, timeout10) if resp.status_code 200: return resp.json() except requests.RequestException as e: print(fpage {page} failed: {e}) return None def parse_cards(data): 从 JSON 中提取微博正文、发布时间、点赞数、用户昵称 items [] cards data.get(data, {}).get(cards, []) for card in cards: if card.get(card_type) ! 9: # 9 是微博正文卡片 continue mblog card.get(mblog, {}) items.append({ text: mblog.get(text, ), created_at: mblog.get(created_at, ), attitudes_count: mblog.get(attitudes_count, 0), comments_count: mblog.get(comments_count, 0), reposts_count: mblog.get(reposts_count, 0), user: mblog.get(user, {}).get(screen_name, ) }) return items if __name__ __main__: all_items [] for p in range(1, 51): # 抓 50 页约 500 条 data fetch_page(人工智能, p) if not data: break all_items.extend(parse_cards(data)) time.sleep(random.uniform(1.5, 3.0)) with open(weibo_raw.json, w, encodingutf-8) as f: json.dump(all_items, f, ensure_asciiFalse, indent2) print(ftotal: {len(all_items)})逻辑说明fetch_page负责发请求并返回 JSONparse_cards只取card_type 9的卡片因为其他类型如广告、话题推荐没有正文。attitudes_count是点赞数后续做热度加权会用到。参数方面page上限一般在 100 左右超过后接口返回空cards所以循环里要判断data是否为空及时退出。time.sleep的区间不要低于 1 秒否则容易触发风控。2.2 正文清洗去掉 HTML 标签和表情占位符微博正文里混着a标签、span classurl-icon表情、以及#话题#符号。情感分析模型对这类噪声很敏感必须先清洗。常见做法是用正则去掉所有...标签再把#xxx#替换成xxx最后去掉用户名。import re def clean_text(raw): 清洗微博正文去 HTML 标签、话题符号、提及、多余空白 text re.sub(r[^], , raw) # 去 HTML 标签 text re.sub(r#(.?)#, r\1, text) # #话题# - 话题 text re.sub(r[\w\u4e00-\u9fa5-], , text) # 去 提及 text re.sub(r\s, , text).strip() # 合并空白 return text参数说明\u4e00-\u9fa5覆盖常用汉字范围后面的用户名可能含字母、数字、汉字和连字符。清洗后如果文本长度小于 5 个字符建议直接丢弃因为太短的文本情感倾向不可靠。3. 情感分析怎么算SnowNLP 与预训练模型的选择边界3.1 SnowNLP 够用在哪不够用在哪SnowNLP 是一个轻量级中文情感分析库安装pip install snownlp即可。它对每条文本返回 0 到 1 之间的情感分数越接近 1 越正面越接近 0 越负面。优点是零依赖、速度快单条推理在毫秒级适合毕业设计这种数据量不大、又不想配 GPU 的场景。缺点是它在网络新词和反讽句上表现一般比如「这波操作真是绝了」可能被判为正面实际是负面。from snownlp import SnowNLP def sentiment_score(text): 返回情感分数0 负面 ~ 1 正面 if not text or len(text) 5: return None s SnowNLP(text) return s.sentiments def label(score): 按阈值分三档负面 / 中性 / 正面 if score is None: return unknown if score 0.4: return negative elif score 0.6: return positive return neutral阈值 0.4 和 0.6 是我在几万条微博上试出来的经验值。如果你发现中性样本过多可以把区间收窄到 0.45~0.55如果负面样本被低估把下阈值提到 0.45。这个参数没有绝对标准取决于你的语料领域。3.2 用预训练模型提升准确率的落地路径如果导师要求准确率指标SnowNLP 可能不够。常见做法是换用uer/roberta-base-finetuned-jd-binary-chinese或bert-base-chinese做微调。但毕业设计时间有限更务实的方案是用transformers加载一个已经在中文本体上微调过的情感分类模型直接推理。from transformers import pipeline # 首次运行会自动下载模型约 400MB sentiment_pipe pipeline( sentiment-analysis, modeluer/roberta-base-finetuned-jd-binary-chinese, tokenizeruer/roberta-base-finetuned-jd-binary-chinese ) def bert_sentiment(texts, batch_size32): 批量推理返回 label 和 score 列表 results [] for i in range(0, len(texts), batch_size): batch texts[i:i batch_size] results.extend(sentiment_pipe(batch, truncationTrue, max_length128)) return results参数说明max_length128覆盖绝大多数微博长度超过会被截断。batch_size32在 8GB 显存的机器上比较稳如果显存不够降到 16。这个模型输出的是positive/negative二分类没有中性档所以你需要自己加一个置信度阈值当score 0.7时归为中性。提示如果只是做毕业设计演示SnowNLP 加规则修正比如检测「呵呵」「无语」等负面词已经能跑出可看的曲线不必一上来就上 BERT。4. 可视化大屏怎么搭ECharts 与实时刷新的最小闭环4.1 后端用 Flask 暴露三个接口可视化大屏的数据来源是后端聚合结果。我一般用 Flask 写三个接口/api/trend返回按小时聚合的情感均值/api/keywords返回词频 Top20/api/distribution返回情感三档占比。数据从 SQLite 读避免额外装数据库。from flask import Flask, jsonify import sqlite3 app Flask(__name__) def query_db(sql, args()): conn sqlite3.connect(weibo.db) conn.row_factory sqlite3.Row cur conn.execute(sql, args) rows [dict(r) for r in cur.fetchall()] conn.close() return rows app.route(/api/trend) def trend(): 按小时返回情感均值和微博数量 sql SELECT strftime(%Y-%m-%d %H:00, created_at) AS hour, AVG(sentiment) AS avg_sentiment, COUNT(*) AS cnt FROM weibo GROUP BY hour ORDER BY hour return jsonify(query_db(sql)) app.route(/api/distribution) def distribution(): 返回正面/中性/负面占比 sql SELECT label, COUNT(*) AS cnt FROM weibo GROUP BY label return jsonify(query_db(sql)) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)逻辑说明strftime把created_at截断到小时AVG(sentiment)得到该小时的平均情感分。label字段是第 3 章算出来的三档标签。debugTrue只用于开发部署时关掉。4.2 前端 ECharts 折线图与饼图的最小配置前端用 ECharts 的setOption画两张图折线图展示情感趋势饼图展示情感分布。关键点是xAxis.type categoryyAxis.type value数据通过fetch从 Flask 接口拉取。// 情感趋势折线图 fetch(/api/trend) .then(res res.json()) .then(data { const hours data.map(d d.hour); const sentiments data.map(d d.avg_sentiment); const chart echarts.init(document.getElementById(trendChart)); chart.setOption({ title: { text: 情感趋势按小时 }, tooltip: { trigger: axis }, xAxis: { type: category, data: hours }, yAxis: { type: value, min: 0, max: 1 }, series: [{ name: 平均情感分, type: line, data: sentiments, smooth: true, areaStyle: { opacity: 0.3 } }] }); }); // 情感分布饼图 fetch(/api/distribution) .then(res res.json()) .then(data { const chart echarts.init(document.getElementById(pieChart)); chart.setOption({ title: { text: 情感分布 }, tooltip: { trigger: item }, series: [{ type: pie, radius: [40%, 70%], data: data.map(d ({ name: d.label, value: d.cnt })) }] }); });参数说明smooth: true让折线更平滑areaStyle.opacity控制填充透明度。饼图的radius设成环形视觉上更像大屏。如果要实时刷新用setInterval每 30 秒重新fetch一次并调用chart.setOption更新数据即可。注意ECharts 的setOption默认是合并模式更新数据时如果 series 长度变化要传{ notMerge: true }避免残留旧数据。5. 避坑与排查爬虫、情感分析、可视化各踩过的坑5.1 爬虫返回 403 或空数据现象请求m.weibo.cn接口返回 403或者cards为空数组。原因通常是User-Agent被识别为脚本或者containerid拼错。解决把User-Agent换成真实移动端浏览器的完整字符串containerid用100103type1q关键词格式关键词不要带空格。如果还是空检查page是否超过上限。5.2 SnowNLP 首次调用报错或极慢现象第一次SnowNLP(text)卡住几秒或者报LookupError。原因是它需要加载内置词典首次加载慢是正常的。如果报LookupError说明snownlp安装不完整重新pip install snownlp --upgrade。另外不要在循环里反复创建SnowNLP对象直接对每条文本调用即可它内部有缓存。5.3 情感分数全是 0.5 左右现象跑完几千条发现大部分分数集中在 0.5曲线几乎是一条直线。原因是微博文本太短或者清洗后只剩几个字。解决过滤掉长度小于 5 的文本并在清洗时保留标点符号因为标点对情感判断有影响。如果还是集中考虑换 BERT 模型。5.4 ECharts 图表不显示或空白现象页面加载后图表区域一片空白控制台报echarts is not defined。原因是 ECharts 的script标签没加载成功或者echarts.init的 DOM 元素还没渲染。解决把script放在body底部或者用DOMContentLoaded事件包裹初始化代码。另外检查div是否设置了width和heightECharts 需要容器有明确尺寸。5.5 SQLite 写入中文乱码现象爬回来的中文存进 SQLite 后变成问号。原因是连接时没指定编码。解决sqlite3.connect(weibo.db)默认就是 UTF-8但如果用pandas.to_sql写入要确保 DataFrame 的字符串列是str类型不是bytes。写入前用df[text] df[text].astype(str)转一遍。6. 让系统更像「舆情系统」热度加权与关键词下钻前面四章跑通的是最小闭环但真正让导师觉得「像舆情系统」的是热度加权和关键词下钻这两个进阶点。热度加权的意思是一条微博的点赞数、评论数、转发数不同它对整体舆情的影响力也不同。我一般用weight 1 log(1 attitudes_count comments_count * 2 reposts_count * 3)作为权重再算加权情感均值。这样一条万赞的负面微博不会被大量零赞正面微博稀释掉。import math def weighted_sentiment(df): 按互动量加权计算情感均值 df[weight] df.apply( lambda r: 1 math.log(1 r[attitudes_count] r[comments_count] * 2 r[reposts_count] * 3), axis1 ) total_weight df[weight].sum() weighted_avg (df[sentiment] * df[weight]).sum() / total_weight return weighted_avg参数说明评论权重乘 2、转发权重乘 3是因为转发和评论的传播成本高于点赞对舆情扩散的贡献更大。这个系数可以根据你的数据分布调整如果转发量普遍很低乘 2 就够了。关键词下钻则是用jieba分词后统计词频再过滤停用词最后用 ECharts 词云或柱状图展示。停用词表可以用现成的中文停用词表也可以自己加「微博」「转发」「哈哈」这类高频无意义词。import jieba from collections import Counter STOPWORDS set([微博, 转发, 哈哈, 我们, 他们, 这个, 那个]) def top_keywords(texts, top_n20): 分词后统计词频返回 Top N words [] for text in texts: for w in jieba.cut(text): if len(w) 1 and w not in STOPWORDS: words.append(w) return Counter(words).most_common(top_n)最后说一个我自己的习惯每次跑完爬虫先df.describe()看一眼情感分的分布如果标准差小于 0.1说明模型没区分度别急着画图先回去检查清洗和模型。这个习惯帮我省过好几次返工。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询