
简介这是一套面向计算机相关专业学生与项目实战学习者的网络舆情分析系统完整源码包可作为人工智能课程大作业、毕业设计或课程实践的高分参考方案评审得分98分难度适中适合需要快速搭建可运行项目、理解舆情分析流程的读者。压缩包共118个文件约45.2MB以27个py源码、36个txt说明、12个jar与10个class等为主另含java、xml、ipynb、json、xlsx等文件覆盖Python分析脚本、Java界面组件与依赖库、配置与数据文档结构清晰便于按模块查阅。内容围绕舆情数据采集、情感倾向判断与可视化展示展开包含柱状图、饼图等统计图表实现以及自然语言处理相关调用示例源码均经本地编译调试可正常运行。目前已有144人学习下载适合希望借鉴完整项目结构、排错思路与实现细节的学习者参考使用。1. 网络舆情分析系统到底在分析什么从一条帖子到一张情绪热力图很多同学做「基于Python的人工智能大作业网络舆情分析系统」时第一反应是去网上找一份爬虫代码把评论抓下来跑一个情感分类画个饼图就交差。结果答辩时被问一句「你这套东西怎么判断一条新评论是正面还是负面」当场卡壳。问题不在代码量而在于没想清楚这套系统真正要解决的是什么它要做的不是「抓数据」而是把非结构化的中文短文本变成可量化、可追踪、可解释的情绪指标并且能在数据持续流入时稳定输出结论。这套系统适合三类人一是需要交人工智能大作业、但不想只交一个MNIST手写数字识别的学生二是想入门NLP工程化、把BERT这类模型真正跑在业务数据上的开发者三是需要给某个产品做舆情监控原型、验证技术路线可行性的小团队。它不追求工业级吞吐但要求链路完整——采集、清洗、分词、情感判定、话题聚类、可视化每一环都能讲清楚为什么这么做。下面按落地顺序拆开讲重点放在能复现的代码和参数上。2. 数据采集与清洗把网页里的脏文本变成能喂给模型的样本2.1 采集方案选型为什么我一般不用全站爬虫做舆情分析数据来源通常分三类公开社交平台的评论区、新闻门户的读者留言、论坛帖子。很多教程一上来就写一个通用爬虫框架配置一堆规则去抓全站这在作业场景里是给自己挖坑。原因有两个第一全站抓取容易触发反爬调试成本极高第二舆情分析需要的是「带时间戳的文本互动量」而不是整个页面的HTML。我一般会采用「接口优先、页面兜底」的策略。如果目标站点有公开的评论接口返回JSON直接请求接口字段干净、分页明确。如果没有再用页面解析但只抓列表页的标题和详情页的正文不递归抓取无关链接。下面是一个模拟项目里常用的采集骨架用requests加BeautifulSoup重点看它的重试和限速逻辑。import requests from bs4 import BeautifulSoup import time import random import csv # 模拟项目采集某公开论坛的帖子标题与发布时间 # 注意实际使用时请替换为目标站点的合法URL并遵守robots协议 HEADERS { 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 } def fetch_page(url, retry3): 带重试和随机延时的页面抓取 for i in range(retry): try: # 随机延时0.8~2.0秒降低被封风险 time.sleep(random.uniform(0.8, 2.0)) resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() resp.encoding resp.apparent_encoding # 自动处理中文编码 return resp.text except requests.RequestException as e: print(f第{i1}次请求失败: {e}) if i retry - 1: return None return None def parse_list(html): 解析列表页提取标题、链接、时间 soup BeautifulSoup(html, html.parser) items [] # 这里的class名是模拟的实际要按目标页面结构调整 for node in soup.select(.thread-item): title node.select_one(.title) link node.select_one(a) timestamp node.select_one(.time) if title and link: items.append({ title: title.get_text(stripTrue), url: link.get(href), time: timestamp.get_text(stripTrue) if timestamp else }) return items if __name__ __main__: # 模拟翻页采集前3页 all_items [] for page in range(1, 4): url fhttps://example-forum.test/list?page{page} html fetch_page(url) if html: all_items.extend(parse_list(html)) # 写入CSV方便后续清洗 with open(raw_posts.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[title, url, time]) writer.writeheader() writer.writerows(all_items) print(f共采集{len(all_items)}条)这段代码的逻辑说明fetch_page里的retry参数控制失败重试次数time.sleep(random.uniform(0.8, 2.0))是限速核心不要去掉。resp.apparent_encoding能自动识别GBK或UTF-8避免中文乱码。parse_list里的选择器.thread-item是占位符实际要按目标页面改。参数上timeout10适合大多数公开页面如果目标响应慢可以调到15但不要超过20否则单次卡死会拖垮整个采集流程。2.2 清洗规则正则不是万能的但没它万万不能抓下来的文本里混着大量噪声HTML实体、表情符号、提及、URL、重复空格。直接丢给分词工具会得到一堆无意义的符号。清洗的目标不是「绝对干净」而是「保留情绪信息的同时去掉干扰」。比如「转发微博」这种词要删但「太差了」必须留。我一般会写一个清洗管道按顺序执行去HTML标签、去URL、去用户、去话题标记、统一全半角、去多余空白。下面是一个可复用的清洗函数。import re import html def clean_text(text): 中文舆情文本清洗管道 if not text: return # 1. 反转义HTML实体如 amp; - text html.unescape(text) # 2. 去掉HTML标签 text re.sub(r[^], , text) # 3. 去掉URL text re.sub(rhttps?://\S|www\.\S, , text) # 4. 去掉提及保留后面的内容 text re.sub(r[\w\u4e00-\u9fa5], , text) # 5. 去掉话题标记的#号保留话题词 text re.sub(r#([^#])#, r\1, text) # 6. 全角转半角 text .join( chr(ord(ch) - 0xFEE0) if 0xFF01 ord(ch) 0xFF5E else ch for ch in text ) # 7. 去掉连续空白和换行 text re.sub(r\s, , text).strip() return text # 测试 samples [ 这个产品太差了#吐槽# 某用户 http://t.cn/abc, lt;divgt;客服态度极差lt;/divgt;, ] for s in samples: print(clean_text(s))逻辑说明第5步用r\1保留话题词本身因为话题词往往携带主题信息比如「#续航#」能帮助后续聚类。第6步的全角转半角只处理可见字符区间0xFF01-0xFF5E不会误伤中文。参数上如果数据里英文较多可以在第3步之前加一步去掉纯英文长串但舆情场景一般不需要。清洗完之后建议把文本长度小于5个字的记录直接丢弃这类短文本情感倾向极弱留着只会干扰模型。同时把重复文本去重用pandas的drop_duplicates(subset[clean_text])即可。3. 情感判定模型从SnowNLP到BERT的选型与微调3.1 基线方案SnowNLP够不够用很多作业直接用SnowNLP做情感分析因为它安装简单、调用一行代码。但SnowNLP的训练语料是电商评论对新闻评论、论坛帖子的泛化能力有限。我实测过一批论坛数据SnowNLP把「呵呵真是服了」判成正面因为「服」在电商语境里常出现在好评中。所以SnowNLP只能当基线不能当最终方案。如果作业时间紧可以用SnowNLP快速跑通链路但要在报告里说明它的局限性。调用方式如下from snownlp import SnowNLP def snownlp_sentiment(text): 返回0~1之间的情感分越接近1越正面 if not text: return 0.5 return SnowNLP(text).sentiments # 测试 print(snownlp_sentiment(这个功能太棒了)) # 接近1 print(snownlp_sentiment(垃圾系统根本用不了)) # 接近0参数上没有可调的SnowNLP的sentiments属性直接输出概率。它的优点是零训练成本缺点是领域偏移明显。我一般会用它给数据打一个粗标签然后人工抽检200条估算准确率。如果准确率低于70%就必须换模型。3.2 进阶方案用BERT做中文情感微调要拿高分必须上预训练模型。中文情感分类常用的基座是bert-base-chinese或hfl/chinese-roberta-wwm-ext。后者在中文任务上通常更好因为用了全词掩码。微调时数据格式是「文本标签」标签用0/1表示负面/正面。下面是一个基于transformers的微调脚本用Trainer接口适合作业场景。import pandas as pd from sklearn.model_selection import train_test_split from datasets import Dataset from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer ) # 模拟项目假设已有清洗后的数据字段为text和label # label: 0负面, 1正面 data pd.read_csv(labeled_data.csv) train_df, val_df train_test_split(data, test_size0.2, random_state42) model_name hfl/chinese-roberta-wwm-ext tokenizer AutoTokenizer.from_pretrained(model_name) def tokenize(batch): return tokenizer( batch[text], paddingmax_length, truncationTrue, max_length128 # 舆情文本通常较短128足够 ) train_ds Dataset.from_pandas(train_df).map(tokenize, batchedTrue) val_ds Dataset.from_pandas(val_df).map(tokenize, batchedTrue) model AutoModelForSequenceClassification.from_pretrained( model_name, num_labels2 ) args TrainingArguments( output_dir./sentiment_model, evaluation_strategyepoch, save_strategyepoch, learning_rate2e-5, # 微调学习率不要超过5e-5 per_device_train_batch_size16, per_device_eval_batch_size32, num_train_epochs3, # 小数据集3轮足够多了会过拟合 weight_decay0.01, load_best_model_at_endTrue, metric_for_best_modelaccuracy, logging_dir./logs ) def compute_metrics(eval_pred): from sklearn.metrics import accuracy_score, f1_score logits, labels eval_pred preds logits.argmax(axis-1) return { accuracy: accuracy_score(labels, preds), f1: f1_score(labels, preds, averageweighted) } trainer Trainer( modelmodel, argsargs, train_datasettrain_ds, eval_datasetval_ds, compute_metricscompute_metrics ) trainer.train() trainer.save_model(./sentiment_model_final)逻辑说明max_length128是权衡舆情文本超过128个token的很少设太大浪费显存。learning_rate2e-5是BERT微调的经典值如果数据量小于1000条可以降到1e-5。num_train_epochs3配合load_best_model_at_endTrue能自动选验证集上最好的轮次。weight_decay0.01是正则化防止过拟合。参数上per_device_train_batch_size如果显存不够降到8同时把learning_rate降到1e-5。evaluation_strategyepoch表示每轮评估一次如果数据量很大可以改成steps并设eval_steps500。微调完成后推理时用pipeline最方便from transformers import pipeline classifier pipeline( sentiment-analysis, model./sentiment_model_final, tokenizermodel_name ) results classifier([这个更新太良心了, 又崩了没法用]) print(results)3.3 标签怎么来人工标注与弱监督结合作业场景最大的痛点是没标注数据。我的做法是先用SnowNLP或一个开源情感词典给全量数据打弱标签然后从「高置信度」和「低置信度」各抽100条人工修正。高置信度用来确认模型没跑偏低置信度用来补充难样本。修正后的数据再拿去微调BERT通常准确率能到85%以上。如果连人工修正的时间都没有可以用textblob的中文扩展或者SnowNLP的sentiments做三分类大于0.7算正面小于0.3算负面中间算中性。但中性样本在训练时容易干扰建议先做二分类把中性归入负面或正面视业务定义而定。4. 话题聚类与可视化让情绪数据能被人看懂4.1 用TF-IDF加KMeans做话题聚类情感分只能告诉你「整体情绪如何」但舆情分析还需要知道「大家在讨论什么」。话题聚类就是把文本按内容分组。最轻量的方案是TF-IDF加KMeans不需要GPU适合作业。import jieba from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.cluster import KMeans # 模拟项目对清洗后的文本做聚类 texts [ 电池续航太差了半天就没电, 屏幕显示效果很好色彩鲜艳, 系统更新后卡顿严重, 拍照清晰夜景模式不错, 充电速度慢等得着急 ] # 结巴分词去掉单字和停用词 def cut_words(text): words jieba.lcut(text) return .join([w for w in words if len(w) 1]) corpus [cut_words(t) for t in texts] vectorizer TfidfVectorizer( max_features1000, # 控制特征维度 min_df1, # 小数据集设为1 max_df0.9 # 去掉出现太频繁的词 ) X vectorizer.fit_transform(corpus) kmeans KMeans(n_clusters2, random_state42, n_init10) labels kmeans.fit_predict(X) for text, label in zip(texts, labels): print(f簇{label}: {text})逻辑说明cut_words里去掉单字是因为中文单字歧义大比如「电」可能指电池也可能指电话。max_features1000在数据量小时够用数据量大可以调到5000。n_init10是KMeans的初始化次数设大一点避免局部最优。聚类数n_clusters需要根据业务定一般先试3到5类看轮廓系数。4.2 可视化用Pyecharts做情绪热力图作业答辩时一张能交互的热力图比静态饼图更有说服力。Pyecharts可以生成HTML文件直接浏览器打开。下面是一个按时间维度的情绪折线图加话题词云的组合示例。from pyecharts.charts import Line, WordCloud from pyecharts import options as opts from pyecharts.commons.utils import JsCode # 模拟项目按天统计平均情感分 dates [2024-01-01, 2024-01-02, 2024-01-03, 2024-01-04] scores [0.65, 0.42, 0.38, 0.71] line ( Line() .add_xaxis(dates) .add_yaxis( 平均情感分, scores, is_smoothTrue, markpoint_optsopts.MarkPointOpts( data[opts.MarkPointItem(type_min, name最低)] ) ) .set_global_opts( title_optsopts.TitleOpts(title舆情情感趋势), yaxis_optsopts.AxisOpts(min_0, max_1) ) ) line.render(sentiment_trend.html) # 词云 words [(续航, 35), (屏幕, 28), (卡顿, 22), (拍照, 18), (充电, 15)] wc ( WordCloud() .add(, words, word_size_range[20, 60]) .set_global_opts(title_optsopts.TitleOpts(title高频话题词)) ) wc.render(wordcloud.html)参数上is_smoothTrue让折线更平滑适合展示趋势。min_0, max_1固定Y轴范围避免不同图表之间无法对比。词云的word_size_range控制字号范围数据量大时可以调大上限。5. 避坑与排查那些让系统跑不起来的细节5.1 中文编码乱码从CSV到模型输入现象采集下来的文本在CSV里正常读进pandas后变成乱码。原因通常是写入时用了utf-8但Excel默认用GBK打开。解决写入CSV时用encodingutf-8-sig这个BOM头能让Excel正确识别。如果已经乱码用pd.read_csv(file.csv, encodinggbk)重新读。5.2 模型过拟合训练集准确率99%验证集只有60%现象微调BERT时训练loss一直降但验证集准确率不升反降。原因数据量太小少于500条模型记住了训练样本。解决把num_train_epochs降到2learning_rate降到1e-5同时加warmup_ratio0.1。如果还不行用数据增强比如同义词替换。5.3 分词把情绪词切碎「太差了」变成「太」「差」「了」现象TF-IDF聚类时「太差了」和「很差」被分到不同簇。原因jieba默认模式会把「太差了」切成三个词。解决加载自定义词典把「太差了」「很差」加进去或者用jieba.lcut(text, cut_allFalse)并配合停用词表去掉「太」「了」这类程度副词和语气词。但注意去掉「太」会丢失强度信息所以更好的做法是保留但在TF-IDF里用ngram_range(1,2)让「太差」成为一个特征。5.4 可视化图表中文不显示一堆方框现象Pyecharts生成的HTML里中文变成方框。原因默认字体不含中文。解决在set_global_opts里加title_optsopts.TitleOpts(title..., title_textstyle_optsopts.TextStyleOpts(font_familyMicrosoft YaHei))或者直接在HTML的head里引入中文字体。更简单的办法是用pyecharts.globals.ThemeType里的CHALK主题它默认支持中文。5.5 推理速度慢单条预测要等好几秒现象用BERT做实时预测时每条评论要等2秒以上。原因每次调用pipeline都重新加载模型。解决把classifier定义在全局只加载一次。如果还是慢用torch.no_grad()包裹推理并把batch_size调大一次预测多条。另外可以把模型导出为ONNX格式推理速度能提升30%左右。6. 把系统跑成可复现的作业从脚本到报告的关键技巧最后一章不讲新模型讲怎么让这套东西在答辩时站得住。我见过太多作业代码能跑但报告里全是「准确率95%」却没有混淆矩阵被老师一问就露馅。我的习惯是每跑完一个模型立刻保存三样东西——混淆矩阵、分类报告、错误样本列表。混淆矩阵用sklearn.metrics.confusion_matrix分类报告用classification_report错误样本就是预测错的那几条单独存CSV。这三样东西放在报告附录里比任何漂亮话都有说服力。另一个技巧是固定随机种子。transformers的TrainingArguments里有seed参数默认是42但numpy和torch的种子要单独设。在脚本开头加import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) set_seed(42)这样每次跑出来的结果一致老师复现时不会因为随机性对不上。参数上seed选42只是习惯选任何固定值都行关键是全流程统一。还有一个容易被忽略的点把清洗、分词、模型推理封装成函数而不是写成一长串脚本。比如predict_sentiment(text)内部完成清洗和推理外部只需要传原始文本。这样在报告里展示时逻辑清晰也方便做单元测试。我一般会写一个pipeline.py里面只有三个函数clean_text、predict、visualize每个函数不超过50行。最后说一个验证方法用留出法而不是交叉验证。舆情数据有时间属性随机划分会导致未来数据泄露到训练集。正确做法是按时间切分前80%做训练后20%做测试。如果数据没有时间戳至少用train_test_split时加shuffleFalse。这个细节在报告里写一句能体现你对数据泄露的理解是加分项。我自己的教训是第一次做这类作业时把全部精力花在调BERT参数上结果答辩时老师问「你的数据怎么来的、清洗规则是什么」完全答不上来。后来才明白舆情分析系统的价值不在模型多深而在链路完整、每一步可解释。希望帮到你。本文还有配套的精品资源点击获取