尧图精选

基于Python的网络舆情分析系统设计与实现

🕒 发布时间:2026/9/1 20:10:34 📁 来源:尧图网络
简介本资源是一套完整的基于Python的网络舆情分析系统毕业设计实现面向计算机、通信、人工智能及自动化等相关专业的本科生与教师适用于课程设计、大作业及毕业设计等实践场景。系统采用Django框架构建Web界面集成CNN/LSTM模型进行情感分析并包含词向量sgns.zhihu.bigram、预训练模型文件.pb、.index、数据集hotel_comment及可视化截图等核心模块代码经实际调试运行验证答辩评分高达98分。压缩包共36个文件含13个Python源码、10个文本说明与配置文件、2个模型权重文件、2个索引文件、2个词向量数据文件及图像、Markdown文档等整体大小为298.36MB。目前已有575人学习下载配套文档详尽、结构清晰既可作为零基础入门范例也支持进阶用户二次开发与功能拓展具备扎实的工程参考价值与教学示范意义。 又是一年毕业设计季后台收到好几个同学留言问“基于Python的网络舆情分析系统”这个题怎么做。这个题目确实很经典它把爬虫、文本挖掘、情感分析、可视化展示全部串起来了技术栈完整业务场景清晰不管是对找工作还是读研都有帮助。我用一个实际跑通的毕业设计项目来讲讲整个系统怎么拆、怎么做从架构设计到核心代码再到踩坑记录完整走一遍。1. 选题定位与系统整体架构设计1.1 网络舆情分析系统到底在解决什么问题网络舆情分析系统核心任务是从互联网上抓取特定主题的文本数据然后对数据进行清洗、分词、情感判断最后通过图表把舆论倾向和热度趋势呈现出来。说得直白一点就是帮用户回答三个问题大家都在聊什么、情绪是正面还是负面、热度是涨还是跌。毕业设计做这个题有几个天然优势。第一数据源开放新闻网站、微博评论区、论坛帖子都可以作为采集目标不需要对接商业API第二技术链路完整从requests请求到jieba分词再到snownlp情感分析每个环节都有自己的技术点论文和答辩都有内容可讲第三演示效果好最终成果是一个带Web界面的系统输入一个关键词就能看到词云、情感饼图、趋势折线图老师一眼就能看懂做了什么。1.2 技术选型为什么最终选择这套Python组合方案我见过不少同学一开始就上Scrapy Elasticsearch Bert模型结果做了两个月连数据都没跑通最后草草交差。毕业设计的核心原则是在有限的周期内把每个环节做到完整、可解释、能演示。所以我的技术选型遵循了一条“够用且不复杂”的思路。Web框架选Flask。Django虽然自带Admin后台和ORM但它的重量级对于一个舆情分析项目来说是负担Flask的路由和模板机制足够撑起整个前端展示。数据采集用requests BeautifulSoup。Scrapy的并发能力强但它的Spider中间件、Item Pipeline这些概念需要额外学习成本而且对于毕业设计的采集量级几千到几万条完全用不上。requests的代码直观出了问题容易排查。情感分析用SnowNLP。它内置了一个中文情感分类模型输入一句话返回0到1之间的情感倾向值0到0.5是消极0.5到1是积极。为什么不直接用深度学习模型因为需要标注数据集做微调数据准备的工作量远超系统本身。SnowNLP面向通用语料训练在电商、新闻场景下准确率能到七成左右作为毕业设计已经够用后面通过自定义情感词典做补充修正。可视化方面词云用wordcloud jieba配合生成趋势图和饼图用ECharts。ECharts的图表交互效果好鼠标悬停能显示具体数值答辩演示时加分明显。1.3 四层架构设计数据从采集到展示的完整流转路径整个系统我分了四层每一层的职责非常明确层与层之间只通过数据传递互不耦合。数据采集层负责从目标网站抓取网页内容包括新闻标题、正文、发布时间、来源等字段。数据存储层统一落地到MySQL表结构在设计时就预留了情感分析的字段。分析处理层负责对文本进行清洗、分词、情感打分这个阶段产出的数据直接决定可视化的准确性。可视化展示层从数据库读取处理结果渲染出趋势图、词云、情感分布图。这种分层模式的好处有两个。一是每一层都可以单独测试爬虫挂了不影响已经入库的数据情感分析代码改动了也不需要重新抓取数据开发效率高二是后期扩展方便。比如想增加数据源只需要在采集层加一个crawler其他三层完全不用动。2. 核心模块详细拆解每个环节的关键实现细节2.1 数据采集层爬虫代码怎么写得既稳定又不被拦截采集层是整个系统的数据入口也是踩坑最多的地方。很多同学写爬虫一上来就写死了一个固定的User-Agent结果请求几十次之后就被对方服务器拦截然后就跑来问我是不是IP被封了。我的爬虫模块用了一个基础的请求函数每次请求都从User-Agent池里随机选一个配合随机的延时时间尽量避免触发对方的风控机制。import requests import random import time from bs4 import BeautifulSoup USER_AGENTS [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15, Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36 ] def fetch_page(url): headers { User-Agent: random.choice(USER_AGENTS), Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.8,en-US;q0.5,en;q0.3 } try: resp requests.get(url, headersheaders, timeout10) resp.encoding resp.apparent_encoding resp.raise_for_status() except requests.RequestException as e: print(f[请求失败] {url}, 错误: {e}) return None time.sleep(random.uniform(1, 3)) return resp.text这段代码里有两个容易忽略的地方。第一个是resp.encoding resp.apparent_encodingrequests库在猜测编码时经常会出错导致中文变成乱码用apparent_encoding让requests根据网页内容实际检测编码能规避大部分乱码问题。第二个是每次请求后time.sleep(random.uniform(1, 3))1到3秒的随机间隔既不会给对方服务器造成压力也让请求行为看起来更像人工浏览。拿到HTML之后用BeautifulSoup解析提取数据。以新闻列表页为例标题通常在h2或者h3标签下的a里面正文在div的class属性里。解析时建议先打印出HTML结构确认选择器不要凭感觉猜。def parse_news_list(html, base_url): soup BeautifulSoup(html, html.parser) news_items [] for item in soup.select(div.news-item): title_tag item.select_one(h3 a) if not title_tag: continue title title_tag.get_text(stripTrue) link title_tag.get(href) if link.startswith(/): link base_url link date_tag item.select_one(span.date) publish_time date_tag.get_text(stripTrue) if date_tag else news_items.append({ title: title, url: link, publish_time: publish_time }) return news_items采集层有一个设计细节列表页只拿标题和链接正文内容在下一层解析时补齐。这样做的原因是列表页响应快、数据量小先快速拿到一批链接然后再逐个进入详情页抓正文。如果列表页和详情页一起解析一旦某个详情页超时整个采集流程都会卡住。2.2 数据存储层MySQL表结构设计与入库逻辑数据库设计是很多毕业设计容易翻车的地方。如果表结构设计得不好后期写查询SQL时会非常痛苦。我设计的news表如下CREATE TABLE news ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL COMMENT 新闻标题, content TEXT COMMENT 正文内容, source VARCHAR(50) DEFAULT COMMENT 来源网站, url VARCHAR(500) DEFAULT COMMENT 原文链接, publish_time VARCHAR(50) DEFAULT COMMENT 发布时间, sentiment_score FLOAT DEFAULT 0 COMMENT 情感得分, 0-1, 小于0.5为负面, sentiment_label VARCHAR(10) DEFAULT neutral COMMENT 情感标签: positive/neutral/negative, keyword VARCHAR(50) DEFAULT COMMENT 搜索关键词, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 入库时间, UNIQUE KEY uk_url (url(255)) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有两个特别重要的设计决策。第一个是给url加了唯一索引这是为了防止同一篇新闻被重复采集。爬虫在翻页过程中经常会出现不同列表页包含同一篇新闻的情况没有唯一索引的话入库数据会有大量重复情感分析和统计结果都会被带偏。第二个是publish_time用了VARCHAR而不是DATETIME。原因很简单不同网站的日期格式五花八门有的是“2024-05-20 10:30”有的是“5小时前”还有的是“2024年5月20日”。在入库阶段强行统一格式会引入很多异常处理逻辑不如先原样存字符串在分析阶段再统一转成标准格式。写入逻辑在SQL层面直接做去重用INSERT IGNORE配合唯一索引重复记录直接跳过代码里不需要再做一次查重查询。def save_news(item, keyword): sql INSERT IGNORE INTO news (title, content, source, url, publish_time, keyword) VALUES (%s, %s, %s, %s, %s, %s) cursor.execute(sql, ( item[title], item[content], item[source], item[url], item[publish_time], keyword ))2.3 文本处理层数据清洗与jieba分词的正确姿势从网页抓下来的正文首先要去掉HTML标签、特殊符号和广告噪声。我写了一个文本预处理函数顺序很重要先解HTML实体再去标签最后清理冗余字符。import re import html def clean_text(raw_text): # 1. 解HTML实体比如 amp; - text html.unescape(raw_text) # 2. 去标签 text re.sub(r[^], , text) # 3. 去脚本和样式内容 text re.sub(r(script|style)[^]*.*?/\1, , text, flagsre.S) # 4. 去空格和特殊符号 text re.sub(r\s, , text) text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。、], , text) return text.strip()这里要注意清理顺序不能乱。如果先去标签再去HTML实体有些标签属性里的amp;可能会被提前转换导致后面的正则匹配失败。先处理实体、再处理标签能保证标签结构完整匹配更可靠。分词用的是jieba但直接对全文分词会得到大量无关词。比如“我们”“但是”“因为”这类停用词以及“可以”“一下”这类口语词需要加载停用词表过滤。import jieba import jieba.analyse def segment(text, stopwords_filestopwords.txt): with open(stopwords_file, r, encodingutf-8) as f: stopwords set(line.strip() for line in f) words jieba.lcut(text) words [w for w in words if w not in stopwords and len(w.strip()) 1] return words这里有一个经验len(w.strip()) 1这个条件可以过滤掉大量单个字。中文里单字词的信息量很低而且高频单字如“的”“了”“是”往往在停用词表里已经存在但难免有漏网之鱼放在分词之后统一过滤效果更好。生成词云时还可以用jieba.analyse.extract_tags按TF-IDF权重提取关键词这样词云不会出现“频率高但无实际含义”的词视觉上更有信息含量。2.4 情感分析引擎SnowNLP配合自定义词典的效果优化情感分析是整个系统的“灵魂”模块。SnowNLP用起来非常简单几行代码就能跑from snownlp import SnowNLP text 这家公司发布的新产品获得了用户一致好评 s SnowNLP(text) print(s.sentiments) # 输出0到1的值但是直接这样用会遇到一个尴尬的问题SnowNLP对新闻文本的适应度不高。因为SnowNLP最初是面向电商评论训练的对“正面”“负面”的判断逻辑偏向口语化表达。比如“这款产品价格贵但质量好”这样的句子SnowNLP往往会判成负面因为“贵”字在训练语料里是高频负面词。所以我加了自定义情感词典来修正。具体做法是维护一个custom_sentiment_dict.txt文件每一行一个词加上情感分取值为-1到1暴跌, -0.9 暴涨, 0.9 稳中有升, 0.6 不尽人意, -0.6 强烈推荐, 0.8 严重亏损, -0.9在计算情感得分时先判断文本中是否包含词典中的词如果包含就对原始得分做偏移修正def analyze_sentiment(text): base_score SnowNLP(text).sentiments adj_score 0.0 with open(custom_sentiment_dict.txt, r, encodingutf-8) as f: for line in f: word, score line.strip().split(,) if word in text: adj_score float(score) final_score max(0.0, min(1.0, base_score adj_score)) if final_score 0.6: label positive elif final_score 0.4: label negative else: label neutral return final_score, label阈值为什么选0.6和0.4这是因为SnowNLP的输出集中在0.3到0.7之间大部分文本评分都在中间地带徘徊。如果阈值设成0.5会导致大量中立文本被误判为正面或负面。把中间带拉宽只对明确的情感信号做二分类整体的准确率反而更高。这个调参过程在论文里也可以作为一个亮点来分析。另外一个优化思路短文本的情感判断比长文本更准确。对于整篇新闻正文建议把正文按句号分句逐句算情感得分最后取平均值。整篇文本一锅端的话正负情绪相互抵消最后得分趋近0.5完全没有区分度。import re def analyze_long_text(text): sentences re.split(r[。], text) sentences [s for s in sentences if len(s) 5] if not sentences: return 0.5, neutral scores [analyze_sentiment(s)[0] for s in sentences] avg_score sum(scores) / len(scores) if avg_score 0.6: return avg_score, positive elif avg_score 0.4: return avg_score, negative else: return avg_score, neutral2.5 可视化展示层Flask ECharts的组合可视化展示层我用Flask提供Web服务和数据接口前端页面用ECharts渲染图表。整个页面分三个区块顶部是搜索框可以输入关键词重新采集和分析中间是情感分布饼图和热度趋势折线图底部是词云图。Flask的后端需要提供两个接口一个用于触发采集和分析任务一个用于读取分析结果并返回JSON。from flask import Flask, render_template, request, jsonify import json app Flask(__name__) app.route(/) def index(): return render_template(index.html) app.route(/api/analyze, methods[POST]) def analyze(): keyword request.json.get(keyword, ) # 触发采集任务 crawl_and_analyze(keyword) # 返回分析结果 result get_analysis_result(keyword) return jsonify(result) app.route(/api/trend) def trend(): keyword request.args.get(keyword, ) data get_trend_data(keyword) return jsonify(data)ECharts的数据格式是JSON所以后端要处理成ECharts能直接消费的结构。比如饼图的格式是[{name: 正面, value: 120}, {name: 负面, value: 30}, ...]折线图的数据格式是{dates: [05-01, 05-02], positive_count: [10, 15], negative_count: [3, 5]}。词云图我用的是wordcloud库生成图片然后把图片Base64编码传给前端或者保存为静态文件直接引用。这里有一个坑wordcloud默认对中文支持不好必须指定中文字体路径比如Windows下的C:\Windows\Fonts\simhei.ttf。from wordcloud import WordCloud import matplotlib.pyplot as plt def generate_wordcloud(keyword): words get_keywords_from_db(keyword) text .join(words) wc WordCloud( font_pathC:/Windows/Fonts/simhei.ttf, width800, height600, background_colorwhite, max_words200 ) wc.generate(text) wc.to_file(static/wordcloud.png)词云图之前还有一个容易被忽略的步骤生成图片之前要确认数据库中是否真的有分词后的数据。如果某次采集只抓到了列表页正文还没来得及抓取数据库里的content字段是空的分词结果自然也是空的词云图就会生成一张纯白图片。3. 完整跑通全流程环境搭建到一键启动3.1 开发环境与依赖清单这个项目在Windows 10上完成开发Python版本3.8。建议使用虚拟环境隔离依赖避免污染全局Python环境。在项目根目录执行以下命令创建虚拟环境并安装依赖python -m venv venv # Windows下激活 venv\Scripts\activate # Linux/Mac下激活 source venv/bin/activate pip install flask requests beautifulsoup4 jieba snownlp pandas pymysql wordcloud有个细节要单独说pandas在这个项目里不是必须的但我在做时间序列聚合的时候用了它。比如要按天统计正负面新闻数量用pandas的groupby比手写SQL循环高效很多代码可读性也好。3.2 数据库初始化在MySQL中创建数据库和表mysql -u root -pCREATE DATABASE sentiment_system DEFAULT CHARACTER SET utf8mb4; USE sentiment_system; -- 将上文面的CREATE TABLE语句粘贴执行数据库字符集一定要用utf8mb4如果用了utf8某些特殊字符比如生僻字和表情符号入库时会报错。这个坑非常隐蔽我一开始用默认字符集爬虫抓取的数据入库时报Incorrect string value错误排查了半天才发现是字符集问题。3.3 核心代码文件组织我的项目目录结构如下sentiment_system/ ├── app.py # Flask主程序 ├── config.py # 数据库和爬虫配置 ├── crawler.py # 爬虫模块 ├── analyzer.py # 文本清洗、分词、情感分析 ├── storage.py # 数据入库、查询 ├── templates/ │ └── index.html # 前端页面 ├── static/ │ ├── echarts.min.js │ └── wordcloud.png # 生成的词云图 ├── stopwords.txt # 停用词表 ├── custom_sentiment_dict.txt # 自定义情感词典 └── requirements.txtconfig.py里保存数据库连接信息和爬虫请求信息这些配置集中管理后续修改不用在代码里到处找DB_CONFIG { host: localhost, user: root, password: your_password, database: sentiment_system, charset: utf8mb4 } CRAWLER_CONFIG { target_url: https://news.example.com, max_pages: 10, delay_min: 1, delay_max: 3 }3.4 启动系统并验证全流程启动前先确认MySQL服务已开启然后运行python app.py浏览器访问http://127.0.0.1:5000输入一个关键词比如“人工智能”点击分析按钮。系统会依次执行爬虫采集→文本入库→分词清洗→情感分析→可视化渲染。整个过程可能需要几十秒到几分钟取决于抓取的文章数量。我建议第一次测试时把max_pages设小一点比如3页先跑通流程再加大采集量。我曾经在调试阶段设置抓取50页结果某个网页卡死整个流程卡了几分钟没有响应最后发现是某条数据的URL不规范导致请求超时。先小规模验证流程再逐步扩容这是做爬虫项目的基本习惯。4. 常见问题与排查技巧实录4.1 中文乱码问题爬虫抓取网页后返回的中文全是乱码这是最常见的问题。原因在于部分网站的响应头没有正确声明编码requests默认按ISO-8859-1解码导致乱码。解决办法是使用resp.apparent_encoding重新检测编码或者在请求头中指定Accept-Charset: utf-8。如果依然乱码可以手动指定编码比如resp.encoding gbk因为不少国内老站还在用GBK编码。先打印resp.apparent_encoding看看检测结果是什么再决定用哪种编码。4.2 采集被拦截页面详情请求正常但到了第几十页就被返回403。这通常不是IP被封而是没有带Cookie或Referer头。某些网站的服务器会校验Referer和请求来源。一个简单有效的方法是先从浏览器复制完整请求头包括Cookie、Referer、Sec-Fetch-*等字段在requests里原样带上。我用这种方法处理过来自同一个IP连续采集上千条数据被拦截的情况。如果目标网站对请求频率特别敏感把延时时间从1到3秒提高到3到6秒采集速度慢一点但胜在稳定。4.3 情感分析结果明显不准我在测试中发现SnowNLP把“这个手机性价比很高强烈推荐”判成了0.3分负面。原因前面提到了SnowNLP的语料是电商评论对“推荐”“性价比”这类词在特定语境下的权重不够。解决方式是调自定义情感词典把“强烈推荐”加入正面词典权重设为0.8。词典的维护是个持续迭代的过程跑一批新闻出来看一下误判样本把高频的误判词加入词典。另外新闻类文本中很多表达是中性的“据悉”“据报道”这些词不影响情感判断不用特殊处理。4.4 可视化图表数据显示为空词云图为空或者折线图没有数据。这种情况优先检查MySQL中的数据。控制台里执行SELECT COUNT(*) FROM news;如果数量为0说明采集环节就没成功如果数量不为0但content字段为空说明爬虫只写了标题没写正文。还有一种情况是sentiment_score字段全为0说明情感分析环节可能没跑或者报错被吞掉了。这种问题一定是数据结构问题优先查库不要先去改前端代码。4.5 使用过程中的体验优化与后续扩展跑通这个系统之后如果想让毕业设计更高一个档次可以在几个方向上做扩展。第一个是增加定时采集能力。目前系统是手动触发改进后可以用APScheduler做定时任务每天自动采集一次长期积累数据后就能生成月度舆情报告这个功能在真实业务场景中非常实用。第二个是引入更丰富的数据源。目前只采新闻网站可以扩展微博评论、微信公众号文章、知乎回答等。不同的数据源有不同的抓取方式和文本结构这本身就是很好的论文素材。第三个是升级情感分析模型。如果用SnowNLP的准确率无法满足需求可以尝试用预训练的BERT模型做情感二分类。不需要从零训练用transformers库加载开源的bert-base-chinese模型在少量标注数据上微调即可。5. 毕设答辩与技术文档的整理思路系统代码写完不是结束毕业设计的最终成绩很大一部分取决于论文和答辩展示。结合这个项目我建议论文框架按“系统需求分析-关键技术研究-系统设计与实现-测试与结果分析”四个章节展开其中技术研究部分重点写情感分析原理和文本预处理流程测试部分对比不同情感分析方案的效果差异。答辩时演示流程建议控制在5分钟以内输入关键词→展示采集过程日志→展示数据库中的结构化数据→展示情感分布饼图、趋势图、词云→说明系统架构图和核心技术点。把流程走通比讲一堆理论更有说服力。文档说明这一块如果源码是网上找的或者从学长那里要来的一定要自己把整个项目跑通一遍把依赖环境、数据库配置、运行步骤重新整理成自己的README。很多同学拿到的源码本身是可用的但缺少环境配置说明导致在自己的电脑上跑不起来。我拿到任何一份毕设源码第一件事就是创建虚拟环境、安装依赖、配置数据库然后把启动命令一步步记录下来这才是真正掌握了这份代码。我个人在实际操作中的体会是网络舆情分析系统这个题目之所以值得做不是因为技术有多前沿而是它把一个完整的数据闭环呈现了出来。你从Web上收集原始数据经过清洗和分析最终转换成有业务价值的可视化信息这个过程本身就是数据分析工作的缩影。把每个环节做扎实比盲目追求花哨的算法模型更重要。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →