拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python微博数据挖掘与社交舆情分析系统实战指南

简介基于Python实现的微博数据挖掘与社交舆情分析系统源码面向计算机相关专业学生、教师及企业开发者适用课程设计、期末大作业或毕设起步项目。系统围绕微博数据采集、预处理、情感分析与舆情趋势研判等环节设计代码结构清晰便于二次拓展与算法替换。资源包为zip格式含669个文件压缩包大小约4.63MB。文件类型涵盖Python源码44个py、前端呈现相关JS与CSS266个js、142个less/12个css、C底层模块16个h、10个cpp、配置文件scrapy.cfg、json、yml等及若干说明文档md、txt可支撑爬虫调度、数据处理和可视化展示的完整流程。目前已有508人浏览学习适合希望快速搭建社交舆情分析原型或参考工程化项目布局的读者。除可运行的主程序外还包含Scrapy爬虫管理、代理管理等辅助模块以及评论数据样例便于理解数据流与调试排错具有较高的课程汇报与演示价值。1. 微博数据挖掘与社交舆情分析这门课设到底在做什么拿到“基于python实现的微博数据挖掘与社交舆情分析系统源码”这个标题时你应该和我第一次做舆情课设时的感觉一样名词都认识但不知道从哪下手。简单说这个系统要解决三件事——把微博上某个话题或某个账号的博文批量拿到本地用Python把非结构化文本清洗成能算的数据最后算出情感倾向、热度趋势和意见分布用图表呈现出来。它的价值不在于算法多深而在于把“爬虫—清洗—分析—可视化”这条链路完整走通这正是课程大作业最看重的工程完整性。这套东西适合三类人一是正在选课设方向、想找一个“技术栈全但难度可控”题目的本科生二是想给简历加一个能讲清楚的数据分析项目的求职者三是需要快速验证舆情分析流程的初级研发。它不依赖GPU不做深度学习一台普通笔记本加上Python 3.8就能跑完。接下来我按自己做过的流程把每个环节的命令、参数和坑都摊开讲。2. 先搭数据采集层用Python把微博数据“接”进来2.1 选型官方API还是爬虫取决于你的数据规模做微博数据采集第一条岔路就是选官方API还是自己写爬虫。微博开放平台确实有API但个人开发者能申请到的接口权限非常有限尤其是读取用户时间线、搜索话题这类核心接口通常要求企业认证或者应用审核通过。等你申请下来课程设计的提交截止日期估计也到了。我的建议很直接课程设计这个量级用爬虫方案目标锁定微博移动端接口 m.weibo.cn它返回的是结构化JSON比网页端解析HTML省事一个量级。这套方案的上限大约是一小时几千条博文对付课设要求的“抓取某话题最近几百条”绰绰有余。如果你要做百万级数据分析那才需要回去折腾官方API或者分布式采集但那是另一个故事了。下面是采集层的最小骨架我习惯先保存cookie再写主力代码避免每次运行都走登录流程。2.2 第一个可复现的采集脚本按关键词搜索微博先别急着写全功能系统把最小采集跑通后面每一步都基于这个脚本迭代。import requests import json import time from urllib.parse import quote # 从浏览器复制你的cookie注意cookie会过期过期后重新登录微博复制一次 COOKIE 你的微博登录cookie HEADERS { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15, Cookie: COOKIE, Referer: https://m.weibo.cn/ } def fetch_weibo(keyword, page_limit5): 按关键词搜索微博返回博文文本列表 :param keyword: 搜索关键词如考研或双减 :param page_limit: 抓取页数每页10~20条 results [] base_url https://m.weibo.cn/api/container/getIndex for page in range(1, page_limit 1): params { containerid: f100103type1q{quote(keyword)}, page_type: searchall, page: page } resp requests.get(base_url, paramsparams, headersHEADERS, timeout10) if resp.status_code ! 200: print(f第{page}页请求失败状态码: {resp.status_code}) time.sleep(3) continue data resp.json() cards data.get(data, {}).get(cards, []) for card in cards: mblog card.get(mblog) if mblog and mblog.get(text): results.append({ text: mblog[text], time: mblog.get(created_at, ), user: mblog.get(user, {}).get(screen_name, ), reposts_count: mblog.get(reposts_count, 0), comments_count: mblog.get(comments_count, 0), attitudes_count: mblog.get(attitudes_count, 0) }) print(f已完成第{page}页采集累计{len(results)}条) time.sleep(2) # 请求间隔 return results if __name__ __main__: data fetch_weibo(考研, page_limit3) with open(raw_weibo.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) print(f采集完成共{len(data)}条已保存到raw_weibo.json)这段代码的关键在于请求参数。containerid里的100103type1q关键词是移动端搜索接口固定的格式page_typesearchall表示搜索全部微博如果你只想看热门微博可以改成hot。page从1开始递增每页返回的条数不固定大致是10到20条所以page_limit5大概能拿到50到100条数据对课设里的“话题舆情分析”够用了。两个参数你需要根据自己场景调time.sleep(2)是请求间隔调大更安全但更慢timeout10是超时时间校园网不稳定时建议调大到15。还有个常见调整是加上since_id做增量抓取拿上一次返回的分页游标继续翻页这个字段在响应JSON的data.cardlistInfo.since_id里想抓超过10页的数据必须用游标而非page硬翻。2.3 页面正文里还有HTML标签先做个迷你清洗注意mblog.text字段是带HTML标签的比如a href/n/人民日报人民日报/a这样的格式还有span classurl-icon之类的表情图标。如果不处理分词阶段你会得到一堆a、href这种垃圾 token。我在采集完第一时间做字符串清洗写个通用函数import re from html import unescape def clean_html(raw_text): 去掉微博正文里的HTML标签、表情链接和多余空白 # 去掉所有尖括号标签 text re.sub(r[^], , raw_text) # 反转义HTML实体比如 amp; 变回 text unescape(text) # 去掉微博特有的转发微博后缀和话题符 text text.replace(转发微博, ).strip() # 多个空格压缩为一个 text re.sub(r\s, , text) return text这里re.sub(r[^], , raw_text)是最核心的一行它能干掉大部分标签。但有个坑微博正文里有些内容在标签之外还有nbsp;这类不换行空格unescape之后还要再strip()一次。你不用追求清洗得尽善尽美课设答辩时只要展示出来的词云里没有a、href这种明显垃圾项就算过关。接下来要把这些清洗过的文本送入存储和分词环节。3. 数据清洗与存储把JSON变成能做分析的“干净数据”3.1 去重与时间归一化两行代码解决的两个常见翻车点采集回来的数据不能直接用先处理两个最影响后续分析的细节重复帖和微博时间格式。import json from datetime import datetime def dedupe_and_norm_time(raw_pathraw_weibo.json, save_pathclean_weibo.json): with open(raw_path, r, encodingutf-8) as f: records json.load(f) # 1. 以全文文本为key去重保留第一条 seen set() unique_records [] for r in records: text r[text].strip() if text and text not in seen: seen.add(text) unique_records.append(r) # 2. 微博时间格式如 Wed Apr 03 14:20:12 0800 2024 # 转成标准ISO格式方便后续按小时聚合 for r in unique_records: dt datetime.strptime(r[time], %a %b %d %H:%M:%S %z %Y) r[time_iso] dt.isoformat() with open(save_path, w, encodingutf-8) as f: json.dump(unique_records, f, ensure_asciiFalse, indent2) print(f去重后剩{len(unique_records)}条原{len(records)}条) return unique_records时间字段是微博里典型的“看得懂但没法排序”格式strptime的格式化串%a %b %d %H:%M:%S %z %Y对应英文星期缩写、英文月份缩写、日期、时间、时区和年份。这里有个容易翻车的点如果用中文环境的Windows运行%a和%b可能因为区域设置解析失败因为微博返回的是Wed Apr这种英文缩写而中文系统默认区域可能是中文。我一般在脚本顶部强制设置locale.setlocale(locale.LC_TIME, C)兜底不然在别人的同学电脑上跑就崩。去重的逻辑也很粗暴直接以全文作为key对课设来说完全够用。3.2 用jieba做中文分词并过滤停用词中文文本不像英文按空格切分需要分词。课设阶段用jieba足够它不用训练下载词库就能跑精度对付舆情分析没问题。import jieba import jieba.analyse # 停用词表建议直接存一个txt文件每行一个词 STOP_WORDS set() with open(stopwords.txt, r, encodingutf-8) as f: for line in f: word line.strip() if word: STOP_WORDS.add(word) def tokenize_text(text): 分词并过滤去停用词、去单字词、去纯数字 words jieba.lcut(text) filtered [] for w in words: w w.strip() if not w: continue if w in STOP_WORDS: continue if len(w) 1: # 单字词信息量低过滤掉 continue if w.isdigit(): continue filtered.append(w) return filtered这里可以对jieba.lcut做关键词参数调整jieba.lcut(text, cut_allFalse)是精确模式默认就是适合分析cut_allTrue是全模式会产生大量冗余词不建议用。分词后我习惯顺手输出一份词频统计不是为了分析是为了快速检查清洗效果——如果词频Top 20里全是“我们”“今天”“一个”这种词说明停用词表太薄得往里补词。stopwords.txt不需要自己从零写网上搜“中文停用词表”下载一份几百词的版本再手动加上“微博”“转发”“分享”等平台词即可。3.3 存储选型为什么我推荐SQLite而不是Excel或MySQL数据量在万条以内时我建议不要上MySQL——安装、配账号、建库对课设来说是纯额外负担。SQLite是Python内置的一个文件就是一个库交作业时把.db文件一起打包就行老师打开就能验证。import sqlite3 def init_db(db_pathweibo_analysis.db): conn sqlite3.connect(db_path) cur conn.cursor() cur.execute( CREATE TABLE IF NOT EXISTS weibo_posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT NOT NULL, cleaned_text TEXT, publish_time TEXT, user_name TEXT, reposts_count INTEGER DEFAULT 0, comments_count INTEGER DEFAULT 0, attitudes_count INTEGER DEFAULT 0, sentiment INTEGER DEFAULT 0 ) ) conn.commit() conn.close() def insert_records(records, db_pathweibo_analysis.db): conn sqlite3.connect(db_path) cur conn.cursor() for r in records: cur.execute( INSERT INTO weibo_posts (text, cleaned_text, publish_time, user_name, reposts_count, comments_count, attitudes_count) VALUES (?, ?, ?, ?, ?, ?, ?), (r[text], r.get(cleaned_text, ), r.get(time_iso, ), r.get(user, ), r.get(reposts_count, 0), r.get(comments_count, 0), r.get(attitudes_count, 0)) ) conn.commit() conn.close()这个表结构里最值得说的是sentiment字段默认填0后面做情感分析时更新成-1负面、0中性、1正面。建表的时候把它预留出来比后面ALTER TABLE加字段省事。SQLite对并发写支持较弱但你的采集是单线程串行写入完全没压力。如果某天你想上量到几十万条再把代码里的connect换成MySQL的pymysqlSQL基本不用改太多这是推荐你用标准SQL建表的原因。4. 舆情分析从“转发数”到“态度分布”都算清楚4.1 情感分析用SnowNLP还是词典法先跑通再调优舆情分析的核心输出之一是情感分布也就是这个话题下正面、负面、中性微博各占多少比例。课设阶段有两个选择用SnowNLP库直接预测或者自己维护一个情感词典做打分。我给你的建议是双轨跑先用SnowNLP跑一版看看结果分布是否合理如果明显偏负面SnowNLP训练语料偏向电商评论对时政类文本常有偏差再用词典法兜底。SnowNLP的接口极简三行代码就能完成批量情感预测对新手相当友好。from snownlp import SnowNLP def analyze_sentiment_snownlp(texts): 输入清洗后的文本列表返回情感分数列表 分数范围[0,1]0.5为中性阈值 scores [] for text in texts: if not text: scores.append(0.5) continue s SnowNLP(text) # sentiments属性返回积极概率大于0.6记为正小于0.4记为负 scores.append(s.sentiments) return scores跑完之后你会发现一个经典现象大量微博的情感分数扎堆在0.4到0.6之间这就是典型的“中性堆积”。别慌这是正常现象——微博里大量内容是事实陈述、求助、转发并不带明显情绪。你可以在分析报告里专门写一段说明“近X%的微博为中性信息说明该话题下信息传播属性强于观点表达”反而显得你思考过。参数方面可以加一个跨度阈值比如0.6正、0.4负、中间中性不要用0.5死切容错更好。如果整批语料明显偏颇再考虑用snownlp的贝叶斯训练接口用人工标注的小样本重新训练但那是进阶操作课设不必强求。4.2 计算热度指数与传播指标给每个话题算一个“舆情体温”除了情感课设成品还需要有“热度”这个指标。我一般用转发、评论、点赞三个维度的加权求和作为单条微博热度分再按天聚合出趋势线。下面这段代码把原始数据变成可画折线图的时间序列from collections import defaultdict from datetime import datetime def compute_heat_trend(records): 按天聚合微博数量和热度分 权重说明转发的传播权重最高评论次之点赞最轻 daily defaultdict(lambda: {count: 0, heat: 0.0}) for r in records: day r[time_iso][:10] # 取2024-04-03 heat r[reposts_count] * 3 r[comments_count] * 2 r[attitudes_count] * 1 daily[day][count] 1 daily[day][heat] heat # 排序并转成列表方便后续绘图 sorted_days sorted(daily.keys()) result [] for d in sorted_days: result.append({ date: d, post_count: daily[d][count], heat: round(daily[d][heat], 2) }) return result def find_top_posts(records, top_n10): 找出传播力最强的微博用于判断谁在主导话题走向 scored [] for r in records: score r[reposts_count] * 3 r[comments_count] * 2 r[attitudes_count] scored.append((score, r)) scored.sort(keylambda x: x[0], reverseTrue) return [r for score, r in scored[:top_n]]权重的选择是这里唯一需要你拍板的参数3:2:1是业界比较常见的经验值强调转发的二次传播价值。你想根据话题属性调整也行——比如娱乐八卦话题点赞权重可以调高时政话题评论权重可以调高。这个调参过程建议写进课设报告里导师会认为你有数据sense而不是傻跑代码。4.3 基于词频和TF-IDF的关键词提取让系统“说出”话题焦点最后一步分析是用关键词描述话题焦点。jieba.analyse提供了extract_tags接口直接做TF-IDF关键词提取import jieba.analyse def extract_topic_keywords(all_texts, top_k20): 从全部博文文本中提取关键词 all_texts: 清洗后的文本列表 corpus \n.join(all_texts) # 返回[(词, 权重)]权重越高越能代表该话题 keywords jieba.analyse.extract_tags( corpus, topKtop_k, withWeightTrue, allowPOS(ns, n, vn, v) # 只保留地名、名词、动名词、动词 ) return keywordsallowPOS这个参数值得多说一句。默认情况下extract_tags会把形容词、副词也带进来导致关键词列表里混入“非常”“真的”这类噪声。限制词性后结果会更像“话题标签”——名词和动词主导。比如分析“考研”话题得到的是“国家线”“调剂”“复试”“上岸”这类实义词而不是“今年”“终于”这种虚词。如果你发现结果里还是有很多词不达意可以加自定义词典jieba.add_word(考研党)让新词不被切碎。这一步做完你的分析模块就齐了下一章把它们串成一个可操作的Web系统。5. 从脚本到“系统”用Flask把分析结果变成可展示的Web页面5.1 为什么选Flask课程设计里的“性价比之王”课设要求通常有一句“具有可视化界面”这意味着不能只在终端里打印结果。做Web端有两条路Django和Flask。Django功能全但重一个舆情展示系统用不到它的Admin后台和ORM全套Flask轻、灵活、好解释百行代码就能搭一个带路由的页面服务。我的理由是你要的是让老师输入关键词、点按钮、看到图表而不是做一个生产级Web应用。Flask上手半小时够用Demo效果好排错方便。5.2 搭建最小可运行系统请求进来数据出去from flask import Flask, render_template, request, jsonify import json app Flask(__name__) app.route(/, methods[GET, POST]) def index(): # 首页如果是GET请求直接展示空表单页 if request.method GET: return render_template(index.html) # 如果是POST请求拿到前端传回的关键词并执行分析 keyword request.form.get(keyword, ) # 这里调用你前面写好的采集→清洗→分析全流程 result run_full_pipeline(keyword) return render_template(result.html, resultresult) app.route(/api/keywords, methods[GET]) def api_keywords(): 给前端提供关键词列表的JSON接口 方便前端画词云时直接取数据 keyword request.args.get(keyword, ) if not keyword: return jsonify({error: keyword is required}), 400 words extract_topic_keywords(load_clean_texts(keyword)) return jsonify({keyword: keyword, keywords: words}) def run_full_pipeline(keyword): 串起整个分析链路返回一个dict records fetch_weibo(keyword, page_limit5) records clean_records(records) sentiment_result analyze_sentiment_snownlp([r[cleaned_text] for r in records]) heat_trend compute_heat_trend(records) top_keywords extract_topic_keywords([r[cleaned_text] for r in records]) return { keyword: keyword, total_count: len(records), sentiment: sentiment_result, heat_trend: heat_trend, keywords: top_keywords }这里我把完整的采集和分析流程封装成run_full_pipeline函数Flask层保持薄业务逻辑留在独立模块里。这样做的好处是你可以先在不启动Web服务的情况下跑python pipeline.py验证数据链路再套上Web层排错范围大大缩小。前端页面用简单的index.html加一个ECharts CDN就能画饼状图和折线图不需要引入Node.js之类的构建工具。课设阶段这样“够用就好”避免陷入前端工程化的无底洞。5.3 参数配置外置别把Cookie硬编码在代码里很多同学把Cookie直接写在爬虫文件里一旦Cookie过期就要改代码既麻烦又不专业。我用一个config.py统一管理# config.py import os # 从环境变量读取如果没设置则用默认值 WEIBO_COOKIE os.getenv(WEIBO_COOKIE, 你的默认cookie填写在这里) REQUEST_INTERVAL float(os.getenv(REQUEST_INTERVAL, 2)) PAGE_LIMIT int(os.getenv(PAGE_LIMIT, 5)) DB_PATH os.getenv(DB_PATH, weibo_analysis.db) STOP_WORDS_PATH os.getenv(STOP_WORDS_PATH, stopwords.txt) # 情感分析阈值可以按话题调整 POSITIVE_THRESHOLD float(os.getenv(POSITIVE_THRESHOLD, 0.6)) NEGATIVE_THRESHOLD float(os.getenv(NEGATIVE_THRESHOLD, 0.4))用os.getenv的好处是换环境不用改代码文件运行前用环境变量注入即可。如果你不想暴露Cookie在公开仓库可以用python-dotenv库配合.env文件把.env加进.gitignore。这个细节在课设报告里写一句“配置与代码分离避免敏感信息硬编码”会让老师觉得你有工程意识。6. 避坑手册舆情系统开发中最常见的六个翻车现场6.1 登录Cookie频繁失效现象爬虫运行几个小时后突然返回{ok: 0, msg: 登录已过期}或者干脆跳转到登录页HTML。原因微博对移动端接口的token做了时间戳校验短则半小时长则数小时就过期另外如果你用同一个Cookie高频请求触发风控会提前失效。解决不要手动复制粘贴Cookie了用selenium写个自动登录脚本登录成功后用browser.get_cookies()把Cookie序列化保存到本地文件每次请求前读取。另外把请求间隔从2秒改到3到5秒能显著延长Cookie寿命。实在不行就在fetch_weibo里捕获ok0的响应打印“Cookie失效请重新登录”的提示后优雅退出。6.2 采集到的文本全是“加载更多”或空字符串现象text字段是空串或者返回的页面不是微博数据而是页面框架。原因m.weibo.cn接口对某些搜索词会返回page_typesearchall不支持的结果或者当前页没有卡片数据还有一种情况是IP被限流接口返回了验证页面。解决在解析逻辑里加判断cards为空直接跳过连续三次请求返回异常就停止脚本。另外建议换containerid100103type61q关键词这种纯文本搜索接口它只返回包含关键词的博文不混入“大家都在搜”这类聚合卡片清洗负担更小。6.3 jieba分词把“新冠”切成“新”和“冠”现象词频统计里出现大量单字词或者词云里“新”“冠”分开显示。原因默认词典是基于通用语料训练的对新闻事件里的专有名词覆盖不够。解决加载自定义词典文件每行一个词比如新冠、奥密克戎、双一流。然后在代码里调用jieba.load_userdict(userdict.txt)或jieba.add_word(新冠)切分时就保持完整。6.4 SnowNLP对文本一直报TypeError现象SnowNLP(text)报错TypeError: expected string or bytes-like object。原因清洗管道里某些记录是None类型或者requests返回的JSON里text字段缺失。解决在调用SnowNLP前加一层类型兜底if not isinstance(text, str): return 0.5。同时在建库SQL里把cleaned_text字段设为NOT NULL DEFAULT 从存储层杜绝脏数据进入分析层。6.5 前端ECharts图表空白不渲染现象页面打开但图表区域空白浏览器控制台报TypeError: Cannot read property map of undefined。原因Flask返回的数据结构与前端setOption里期望的字段名不一致比如后端传的是heat_trend前端代码里写的是trend_data。解决先用浏览器开发者工具看网络请求的响应体确认字段名。我习惯让后端直接输出result的JSON预览到页面顶部临时加一个pre{{ result | tojson }}/pre核对无误后再写前端渲染逻辑。Python字典的key和JavaScript对象的属性访问不要混用比如后端result[total_count]前端就要用result.total_count或result[total_count]别两边写法不一致。6.6 Linux下运行报找不到lxml库现象ImportError: cannot import name etree from lxml或安装lxml时卡在编译。原因lxml在Linux上依赖系统级的libxml2和libxslt开发包部分纯净环境没有预装。解决Ubuntu/Debian先执行sudo apt-get install libxml2-dev libxslt1-devCentOS用yum install libxml2-devel libxslt-devel然后再pip install lxml。如果不想折腾系统依赖直接用pip install pyquery或beautifulsoup4替代它们的底层也依赖lxml但不是必须的能避开这个坑。7. 让课设从“能用”变成“有亮点”三个加分技巧课设答辩时老师看的不只是“跑通了没有”更看你有没有在标准流程之外多想一步。我在自己的项目里加了三个小功能展示效果提升明显工作量却很小它们恰好是你在前面的章节代码基础上最容易扩展的部分。第一个技巧是给情感分析加一个“时间衰减窗口”。舆情是有生命周期的同样一条负面微博在爆发当天的传播力和一周后完全不同。计算单条微博的热度分时我按发布时间做指数衰减weight exp(-0.1 * 距离发布时间的小时数)乘以原有的转发评论点赞总和。这个公式不用解释得很深只要让老师看到你考虑了时间维度而不是简单求和就可以开始讲你的舆情周期理解了。第二个技巧是做“正负面微博的对比词云”。只给出一张总词云太普通了把情感分数大于0.6的文本拼在一起出一张词云小于0.4的文本拼在一起出另一张对比着看非常直观。比如分析某个手机品牌的新品发布正面词云是“性能”“颜值”“拍照”负面词云是“发热”“卡顿”“续航”结论一目了然整个分析深度立刻上了一个台阶。实现上就是WordCloud库对两个文本列表各跑一次生成图片用matplotlib拼图展示。第三个技巧是给系统加一个“关键词一时间热力矩阵”。纵轴是TF-IDF提取的Top关键词横轴是日期单元格颜色深浅表示这个关键词在某天的出现频次。这个热力图直接展示了话题的演化过程——哪个词先出现哪个词是后期爆发出来的是分析报告里最抢眼的一张图。实现用seaborn的heatmap就行数据准备就是按天按词做交叉统计大概二十行代码。这三个技巧的共同点是不引入新框架、不增加数据源、不写复杂算法只是把已有数据换个角度重新组织和展示。但它们在答辩现场的说服力很强——老师会看到你在“认真对待分析和呈现”这件事而不是在应付作业。这整套写下来我最想提醒你的是别把课设当成负担它是一个让你把Python语法、网络请求、数据处理、Web开发串成一条线的难得机会。我在做这个项目时踩过的坑你顺着上面六条大多能绕开没踩过的坑我留着让你体验。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门