
简介本资源是一套基于Python开发的微博数据挖掘与社交舆情分析系统源码面向计算机类专业在校学生、教师及初入职场的技术人员适用于课程设计、期末大作业、毕业设计选题及项目立项演示等实践场景。系统完整实现微博数据爬取、清洗、情感分析、热点识别与可视化展示等核心功能具备良好可拓展性与工程参考价值。压缩包共669个文件以44个Python主程序文件为逻辑核心辅以266个JavaScript前端交互脚本、142个Less样式文件及18个JSON配置文件支撑前后端分离架构另有C底层模块如proxymanager.cpp、spidermanager.cpp与Docker部署文件体现多语言协同开发能力整体包体仅4.63MB轻量高效。目前已有508人学习下载资源代码经实测稳定运行配套结构清晰、注释规范包含完整依赖说明与运行指引可直接部署调试或作为进阶学习范例。1. 这不是“爬完微博就完事”的课程作业一个能跑通、能调参、能解释结果的舆情分析系统到底长什么样很多同学拿到“基于Python实现的微博数据挖掘与社交舆情分析系统”这个课程大作业标题时第一反应是赶紧找Scrapy模板、硬套情感词典、导出Excel交差。但真正跑起来才发现——微博网页结构频繁变动Ajax加载内容抓不到评论时间戳解析错乱情绪得分全靠字面匹配最后生成的“舆情热力图”连自己都说不清横纵坐标代表什么。这不是代码写得不够多而是缺了一条贯穿始终的技术链从可稳定获取的原始数据源到可复现的文本处理流程再到有业务含义的指标定义与可视化表达。本项目面向的是需要交付可演示、可答辩、可延展的完整分析闭环的本科生与研究生核心不在于炫技式堆砌库比如强行上BERT而在于用requestsBeautifulSoupJiebaSnowNLPMatplotlib这一套成熟、轻量、调试友好的组合在有限课业时间内把“某事件在微博上的传播节奏、用户态度分布、关键意见节点”这三个真实问题讲清楚、算出来、画明白。后续所有步骤都围绕这三件事展开。2. 为什么不用Scrapy而选requestsBeautifulSoup动态渲染、反爬策略与课程项目的现实平衡2.1 微博页面结构的本质矛盾静态HTML骨架 vs 动态注入内容微博PC端首页、话题页、用户主页等核心页面其初始HTML仅包含基础框架和少量预加载数据大量微博正文、评论、转发数、时间戳均通过JavaScript异步请求XHR或WebSocket动态注入。Scrapy作为纯服务端爬虫框架默认不执行JS若直接抓取https://weibo.com/100803?k华为Mate60返回的HTML你看到的只是空容器div classcard-wrap/div而非实际微博列表。课程项目要求“可复现、易调试”意味着必须让每个环节都能在PyCharm里单步跟踪、打印中间变量、修改请求头后立刻验证响应。Scrapy的中间件机制、Pipeline异步处理、CrawlSpider规则匹配对初学者而言调试成本远高于直接控制requests会话。提示不要被“Scrapy是爬虫标配”的说法误导。课程项目中90%的微博公开数据如超话话题页、搜索结果页可通过构造带X-Requested-With: XMLHttpRequest头的AJAX请求直接获取JSON数据比解析动态渲染后的DOM更可靠、更高效。2.2 构建最小可行请求链绕过登录态、模拟真实UA、处理Cookie时效性微博对未登录用户的访问频次限制较宽松但需规避简单User-Agent封禁。以下是最小化且稳定的requests初始化方式import requests from urllib.parse import urlencode # 构造基础会话复用连接、自动处理Cookie session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9,en-US;q0.8,en;q0.7, X-Requested-With: XMLHttpRequest, # 关键声明为AJAX请求 Referer: https://weibo.com/ # 必须携带Referer否则部分接口返回403 }) # 示例获取“华为Mate60”话题下前20条微博搜索接口 search_params { containerid: 100103type1q华为Mate60, page: 1, feature: 0 } url fhttps://weibo.com/ajax/side/hotSearch?{urlencode(search_params)} response session.get(url) data response.json() # 直接得到结构化JSON无需BeautifulSoup解析这段代码的关键参数说明X-Requested-With: XMLHttpRequest告诉服务器这是前端JS发起的异步请求服务器返回JSON而非HTMLReferer: https://weibo.com/模拟从微博首页跳转而来缺失此头会导致{code:403,msg:Forbidden}session对象复用TCP连接并自动管理Cookie避免每次请求重新登录校验urlencode()确保中文关键词正确编码为UTF-8 URL格式如q%E5%8D%8E%E4%B8%BAMate60。2.3 BeautifulSoup仅用于兜底场景当JSON接口失效时的DOM解析策略尽管优先使用JSON接口但部分页面如用户主页微博列表仍需解析HTML。此时BeautifulSoup的价值在于精准定位动态插入后的最终DOM结构。例如用户主页的微博内容藏在script标签内的一段window.$render_data变量中from bs4 import BeautifulSoup import re import json # 获取用户主页HTML user_url https://weibo.com/u/1707812057 html session.get(user_url).text soup BeautifulSoup(html, lxml) # 提取嵌入式JSON数据正则比CSS选择器更可靠 script_tag soup.find(script, stringre.compile(rwindow\.\$render_data)) if script_tag: json_str re.search(rwindow\.\$render_data\s*\s*(\{.*?\});, script_tag.string, re.DOTALL).group(1) render_data json.loads(json_str) # 其中render_data[status]即为该用户最新10条微博的列表此处re.DOTALL确保跨行匹配rwindow\.\$render_data\s*\s*(\{.*?\});贪婪捕获最外层花括号内的JSON字符串比依赖不稳定class名的soup.select(.card-wrap)更健壮。课程项目中应将此逻辑封装为get_user_timeline(session, user_id)函数与JSON接口调用保持统一返回结构如[{id: xxx, text: ..., created_at: 2023-09-12 14:22:30}]便于后续模块无缝接入。3. 从原始文本到舆情指标Jieba分词、SnowNLP情感计算与领域词典增强3.1 分词不是越细越好停用词过滤、领域专有名词保留与词性约束微博文本充斥网络用语“yyds”、“绝绝子”、表情符号“”、“”、URL“https://t.cn/xxx”和用户“人民日报”。直接使用Jieba默认分词会导致大量无意义切分如将“华为Mate60”切为“华为/Mate/60”。必须定制分词策略import jieba import jieba.posseg as pseg # 加载停用词表自行准备stopwords.txt含“的”、“了”、“吗”等虚词及微博特有噪声 with open(stopwords.txt, r, encodingutf-8) as f: stopwords set(line.strip() for line in f) # 强制添加领域词提升“华为Mate60”、“鸿蒙OS”等专有名词识别率 jieba.add_word(华为Mate60, freq1000, tagnz) # nz专有名词 jieba.add_word(鸿蒙OS, freq1000, tagnz) jieba.add_word(苹果iOS, freq1000, tagnz) def clean_and_cut(text): # 清洗移除URL、用户、重复标点、空白符 import re text re.sub(rhttp\S|[\w\u4e00-\u9fa5], , text) text re.sub(r[^\w\u4e00-\u9fa5], , text) # 仅保留中文、英文、数字、下划线 text re.sub(r\s, , text).strip() # 分词并过滤停用词、单字词、标点 words [] for word, flag in pseg.cut(text): if word not in stopwords and len(word) 1 and flag ! x: # x标点符号 words.append(word) return words # 示例对一条微博分词 raw_text 华为Mate60真香鸿蒙OS太流畅了苹果iOS根本比不了 #华为# print(clean_and_cut(raw_text)) # 输出[华为Mate60, 真香, 鸿蒙OS, 流畅, 苹果iOS, 比不了]参数说明freq1000提高领域词在词频统计中的权重避免被误切tagnz指定词性为专有名词后续可按词性做不同处理如只对nz类词做情感极性分析pseg.cut()返回(word, flag)元组flag ! x过滤掉标点符号避免“”、“”进入词云正则清洗http\S|[\w\u4e00-\u9fa5]同时覆盖英文URL和中文用户名如新华社。3.2 SnowNLP情感分析的局限性与本地词典增强方案SnowNLP内置的情感词典基于新闻语料训练对微博口语化表达“笑死”、“破防了”、“栓Q”识别率低。直接调用snownlp.SnowNLP(text).sentiments可能将“这手机太卡了笑死”判为正面因“笑死”在词典中为中性偏正。解决方案是构建微博情感增强词典weibo_sentiment_dict.txt每行格式为词语\t极性分值\t词性笑死 -0.8 nz 破防了 -0.9 v 绝绝子 0.7 a yyds 0.9 nz然后重载SnowNLP的词典加载逻辑from snownlp import SnowNLP import os # 替换SnowNLP默认词典路径 original_dict_path os.path.join(os.path.dirname(SnowNLP.__file__), sentiment, positive.txt) # 将weibo_sentiment_dict.txt中极性0的词写入positive.txt0的写入negative.txt # 具体写入逻辑略需按SnowNLP要求格式每行一个词 # 使用增强后词典计算情感 s SnowNLP(这手机太卡了笑死) print(s.sentiments) # 输出0.23 → 增强后应接近 -0.6注意SnowNLP的sentiments返回0~1之间的浮点数0.5为中性低于0.5为负面高于0.5为正面。课程项目中建议将结果映射为三级标签negative0.3、neutral0.3~0.7、positive0.7避免过度解读小数点后两位差异。3.3 构建可解释的舆情指标不只是情感均值还有传播广度与态度离散度单纯计算所有微博情感得分的平均值如0.62无法回答“支持者是否集中发声反对声音是否来自少数极端账号”。必须引入两个衍生指标指标名计算公式业务含义代码示意态度离散度ADstd(sentiment_scores)衡量舆论分歧程度。AD0.2说明观点两极分化AD0.1说明高度共识np.std([s.sentiments for s in weibos])传播广度PBsum(fans_count for weibo in top_100_weibos)粗略反映事件影响力。粉丝数加权比单纯微博数更能体现声量sum(w[user_fans] for w in top_weibos[:100])其中user_fans字段需在爬取微博时一并提取微博API返回user.followers_count。这两个指标与情感均值构成三维坐标可在Matplotlib中绘制散点图X轴情感均值Y轴态度离散度点大小传播广度。课程答辩时指着图中“高情感均值低离散度大传播广度”的集群就能清晰说出“主流舆论高度一致地正向评价Mate60且声量覆盖广泛”。4. 可视化不是画图是讲清故事用Matplotlib构建舆情时间线与主题聚类图4.1 时间线图解决“舆情何时爆发持续多久”——必须处理微博时间格式不一致问题微博API返回的时间字段格式混乱新微博为2023-09-12 14:22:30旧微博为2022-05-20热门微博甚至为10分钟前。必须统一转换为datetime对象from datetime import datetime, timedelta import re def parse_weibo_time(time_str): 统一解析微博各种时间格式 now datetime.now() # 处理“X分钟前”、“X小时前” m re.match(r(\d)分钟前, time_str) if m: return now - timedelta(minutesint(m.group(1))) m re.match(r(\d)小时前, time_str) if m: return now - timedelta(hoursint(m.group(1))) # 处理“今天 HH:MM”、“昨天 HH:MM” m re.match(r今天 (\d):(\d), time_str) if m: return now.replace(hourint(m.group(1)), minuteint(m.group(2)), second0, microsecond0) m re.match(r昨天 (\d):(\d), time_str) if m: yesterday now - timedelta(days1) return yesterday.replace(hourint(m.group(1)), minuteint(m.group(2)), second0, microsecond0) # 处理标准格式“YYYY-MM-DD HH:MM:SS”或“YYYY-MM-DD” try: if len(time_str) 10: # 仅日期 return datetime.strptime(time_str, %Y-%m-%d) else: # 完整时间 return datetime.strptime(time_str, %Y-%m-%d %H:%M:%S) except ValueError: return now # 解析失败返回当前时间避免程序中断 # 对所有微博时间排序 weibos_sorted sorted(weibos, keylambda x: parse_weibo_time(x[created_at]))4.2 绘制双Y轴时间线左侧为微博数量右侧为情感均值X轴为小时粒度import matplotlib.pyplot as plt import pandas as pd # 按小时聚合数据 df pd.DataFrame(weibos_sorted) df[hour] df[created_at].apply(parse_weibo_time).dt.floor(H) hourly_stats df.groupby(hour).agg( count(id, count), sentiment_mean(sentiment, mean) ).reset_index() fig, ax1 plt.subplots(figsize(12, 6)) ax2 ax1.twinx() # 左侧柱状图每小时微博数量 bars ax1.bar(hourly_stats[hour], hourly_stats[count], alpha0.7, label微博数量, colorsteelblue, width0.8) ax1.set_xlabel(时间小时) ax1.set_ylabel(微博数量, colorsteelblue) ax1.tick_params(axisy, labelcolorsteelblue) # 右侧折线图每小时情感均值 line ax2.plot(hourly_stats[hour], hourly_stats[sentiment_mean], ro-, label情感均值, linewidth2, markersize4) ax2.set_ylabel(情感均值0~1, colorred) ax2.tick_params(axisy, labelcolorred) ax2.set_ylim(0.2, 0.8) # 锁定Y轴范围避免均值波动掩盖数量趋势 # 添加图例 lines1, labels1 ax1.get_legend_handles_labels() lines2, labels2 ax2.get_legend_handles_labels() ax1.legend(lines1 lines2, labels1 labels2, locupper left) plt.title(华为Mate60舆情时间线声量与态度演变) plt.xticks(rotation45) plt.tight_layout() plt.savefig(weibo_timeline.png, dpi300, bbox_inchestight) plt.show()此图能直观揭示关键节点例如9月12日10:00出现微博数量峰值发布会开始但情感均值在14:00才升至0.75首批评测发布说明舆论发酵存在2-3小时延迟。课程报告中应将此图与事件时间轴发布会、评测、竞品回应叠加标注体现分析深度。4.3 主题聚类图用TF-IDFKMeans发现隐藏讨论焦点仅看情感倾向不够还需回答“大家在争论什么”。对清洗后的分词结果clean_and_cut(text)输出进行TF-IDF向量化再用KMeans聚类from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.cluster import KMeans from sklearn.decomposition import PCA import numpy as np # 构建语料库每条微博为一个字符串已分词并用空格连接 corpus [ .join(clean_and_cut(w[text])) for w in weibos_sorted[:500]] # 取前500条避免内存溢出 # TF-IDF向量化max_features1000限制维度ngram_range(1,2)捕获“华为Mate60”等二元词 vectorizer TfidfVectorizer(max_features1000, ngram_range(1, 2), min_df2) tfidf_matrix vectorizer.fit_transform(corpus) # KMeans聚类k5可根据肘部法则调整 kmeans KMeans(n_clusters5, random_state42, n_init10) clusters kmeans.fit_predict(tfidf_matrix) # 降维可视化PCA到2D pca PCA(n_components2) reduced pca.fit_transform(tfidf_matrix.toarray()) # 绘制散点图颜色代表聚类 plt.figure(figsize(10, 8)) scatter plt.scatter(reduced[:, 0], reduced[:, 1], cclusters, cmaptab10, alpha0.6) plt.colorbar(scatter) plt.title(微博文本主题聚类TF-IDF KMeans) plt.xlabel(fPCA1 ({pca.explained_variance_ratio_[0]:.2%} variance)) plt.ylabel(fPCA2 ({pca.explained_variance_ratio_[1]:.2%} variance)) plt.savefig(topic_clustering.png, dpi300, bbox_inchestight) plt.show() # 打印每个聚类的关键词TF-IDF权重最高的10个词 feature_names vectorizer.get_feature_names_out() for i in range(5): cluster_keywords [] for idx in tfidf_matrix[clusters i].sum(axis0).argsort()[0, -10:].tolist()[0]: cluster_keywords.append(feature_names[idx]) print(f聚类{i}: {, .join(cluster_keywords)})输出示例聚类0: 华为Mate60, 鸿蒙OS, 流畅, 性能, 芯片 聚类1: 苹果iOS, 卡顿, 信号差, 续航短, 价格贵 聚类2: 拍照, 超感光, 夜景, 视频, 防抖这直接提炼出三个核心讨论维度产品性能对比、竞品短板攻击、影像能力聚焦。课程答辩时可据此设计PPT章节“舆论关注点分布——72%讨论聚焦影像仅15%提及价格”比泛泛而谈“用户普遍好评”更具说服力。5. 课程项目落地关键技巧环境隔离、数据缓存与答辩演示避坑指南5.1 用venv隔离环境避免PyPI包版本冲突导致的“本地能跑答辩机报错”课程项目常需在不同电脑实验室、宿舍、答辩教室运行。必须杜绝pip install全局安装采用Python内置venv创建独立环境# 在项目根目录执行Windows python -m venv venv venv\Scripts\activate.bat pip install -r requirements.txt # Linux/macOS python -m venv venv source venv/bin/activate pip install -r requirements.txtrequirements.txt内容示例指定精确版本避免SnowNLP更新后API变更requests2.31.0 beautifulsoup44.12.2 jieba0.42.1 snownlp0.12.3 matplotlib3.7.1 pandas2.0.3 scikit-learn1.2.2提示答辩前务必在目标机器上执行python -c import snownlp; print(snownlp.__version__)验证版本曾有学生因答辩机预装SnowNLP 0.13.0导致sentiments属性不存在而当场报错。5.2 设计两级缓存机制避免重复爬取保证答辩演示秒级响应微博爬取耗时且易受网络影响必须实现缓存。采用“内存缓存文件缓存”两级策略import pickle import os from functools import wraps CACHE_DIR cache os.makedirs(CACHE_DIR, exist_okTrue) def cache_to_file(cache_key): 装饰器将函数返回值序列化到文件 def decorator(func): wraps(func) def wrapper(*args, **kwargs): cache_path os.path.join(CACHE_DIR, f{cache_key}.pkl) if os.path.exists(cache_path): with open(cache_path, rb) as f: return pickle.load(f) result func(*args, **kwargs) with open(cache_path, wb) as f: pickle.dump(result, f) return result return wrapper return decorator # 使用示例爬取结果缓存 cache_to_file(huawei_mate60_weibos) def fetch_huawei_weibos(): # 实际爬取逻辑 return weibos_list # 内存缓存针对高频调用的分词、情感计算 from functools import lru_cache lru_cache(maxsize1000) def get_sentiment_cached(text): return SnowNLP(text).sentiments答辩演示时首次运行fetch_huawei_weibos()耗时30秒后续所有操作绘图、聚类、导出均从cache/huawei_mate60_weibos.pkl读取实现“打开脚本→点击运行→1秒出图”的流畅体验。5.3 答辩演示必备的3个最小化可运行文件避免答辩时手忙脚乱找入口文件。项目根目录必须包含文件名作用内容要点demo.py一键启动演示if __name__ __main__: fetch_huawei_weibos(); plot_timeline(); show_clustering()无参数、无输入双击即运行config.py集中管理所有可调参数SEARCH_KEYWORD 华为Mate60、MAX_PAGES 3、CLUSTER_NUM 5答辩老师问“能换关键词吗”时直接打开此文件修改README.md3行说明1张效果图“本系统演示华为Mate60舆情分析含时间线、情感分布、主题聚类三图。运行python demo.py即可。” 最后一行技术内容将demo.py中的fetch_huawei_weibos()函数首行添加print(正在获取微博数据...)并在plot_timeline()后添加plt.show(blockFalse); plt.pause(0.1)确保图形窗口在脚本结束前不闪退——这是答辩现场最常被忽略却最影响观感的细节。本文还有配套的精品资源点击获取