☰
基于Python的网络舆情分析系统实战:从爬虫采集到情感可视化全流程
2026/10/3 18:11:56 网站建设 项目流程

简介:这是一套面向高校人工智能课程设计与期末大作业的基于Python的网络舆情分析系统完整项目,涵盖源码、全部数据与文档说明。项目已调试可运行,无需修改即可直接用于答辩或提交,适合需要快速获得高质量完成方案的学生。压缩包共118个文件,大小45.22MB,主要包含Python业务代码(py)、Java辅助模块(java/class)、界面与配置文件(jar/xml/json)、说明文档(md/txt)以及示例图表等,目录结构清晰,便于按模块理解舆情采集、清洗、分析与可视化流程。目前已有1476人学习下载。资源的价值在于,不仅提供可复用的完整代码,还附带了数据文件、文档说明及可视化展示组件(如柱状图、饼图),并集成了外部NLP分析能力,可帮助读者掌握从数据获取到情感研判的完整链路,同时为课程答辩提供充分支撑。

1. 人工智能大作业“基于Python的网络舆情分析系统”:从选题到交付一共要跨几道坎

先说结论:网络舆情分析这个人工智能大作业,卡人的地方从来不是算法有多深,而是从 python 爬虫采集、文本清洗、情感分析到可视化这条数据链路能不能闭环。题目里挂着「人工智能」,实际评阅人最在意的是你的系统能不能输入一个时间段、输出一张说得清讲得明的舆情结论图;至于用没用深度学习,反而排在后面。这个项目的典型交付物包括:一套可运行源码、一份可复现的爬取与预处理脚本、若干清洗后的数据集,以及一份能把每个模块讲清楚的文档说明。适合两类人:一是人工智能方向的学生想拿一份完整大作业交差,二是刚入门 NLP 的工程师想用舆情分析练手,把爬虫、分词、情感模型、图表报告串起来。

2. 先立系统骨架:数据层、算法层、展示层的模块边界与选型理由

2.1 为什么选五段式架构:爬虫采集、文本预处理、情感分析、主题聚类、可视化

我见过不少舆情分析大作业翻车,翻在最常见的一种做法上:把所有代码塞进一个 notebook,从上到下跑完,图是出来了,但老师一问“数据从哪来、清洗规则是什么、情感阈值为什么这么定”,就答不上来。所以建议一开始就按五段式把系统切开:爬虫采集、文本预处理、情感分析、主题聚类、可视化。

这五个模块之间的数据流是单向的:采集层产出原始 HTML 或 JSON,预处理层产出干净的分词文本,分析层产出情感分数与主题编号,展示层把结果画成词云、趋势线和饼图。模块之间只用 CSV 文件或 DataFrame 衔接,不搞对象之间深耦合。这样做的直接好处是,任何一个环节出问题,你只需要替换掉那一段,不用把整个系统推倒重来。对课程作业来说,这也是最容易在文档里画架构图的结构。

第一版不要贪多。爬虫只做 1 到 2 个新闻或社交平台的数据源,情感分析先跑词典法,主题聚类直接落 LDA,可视化用 pyecharts 或 wordcloud,足够覆盖一个大作业的工作量。在这套骨架里,你后续想加 BERT 情感模型、加实时舆情大屏,都是在固定接口上做替换,不用改整体设计。

2.2 一组不翻车的技术选型:requests、jieba、SnowNLP、pyecharts

网络舆情分析系统的技术选型,第一条原则是“老师能复现”。凡是需要联网下载大模型权重、需要特殊硬件支持的方案,都容易在答辩现场变成黑匣子。我一般推荐这套组合:requests + BeautifulSoup 搞定采集,jieba 做中文分词,SnowNLP 或朴素贝叶斯做情感判断,gensim 做 LDA 主题模型,最后用 pyecharts 和 wordcloud 出图。

这套选型的理由很直接。requests 和 BeautifulSoup 是 python 爬虫的标配,文档多、报错信息容易搜;jieba 对中文分词的工程化支持最成熟,自定义词典、停用词过滤都很方便;SnowNLP 虽然模型老,但它基于电商评论语料训练,拿来跑舆情文本需要重新标定阈值,这个“重新标定”的过程本身就值得写进大作业文档里。主题聚类用 gensim 的 LDA,跑得快,参数也就几个,适合课程项目;如果语料超过几万条,再考虑 BERTopic。

可视化层有个容易忽略的点:pyecharts 出的是 HTML 交互图,方便答辩时现场操作;wordcloud 出的是静态图,适合贴在报告里。我的经验是两类图都要出,交互图证明系统能看动态变化,静态词云证明你有明确的数据结论。具体版本号不用追新,锁在你本机 python 环境能装上的稳定版本即可。

2.3 源码目录与“文档说明”怎么组织,答辩不心虚

拿到这类 zip 包,很多人第一个动作是解压后直接在根目录乱放一堆文件。但一个可以交作业的系统,目录结构要从一开始就克制。常见做法是分四个目录:code、data、output、docs,再放一个根 README。code 里按模块拆脚本,data 里放原始采集数据和清洗后数据,output 放图表和结果文件,docs 放设计说明和答辩演示大纲。

network_opinion_analysis/ ├── README.md ├── code/ │ ├── 01_crawler.py # 数据采集 │ ├── 02_preprocess.py # 清洗+分词 │ ├── 03_sentiment.py # 情感分析 │ ├── 04_topic.py # 主题聚类 │ └── 05_visualization.py # 可视化出图 ├── data/ │ ├── raw/ # 原始数据 │ ├── clean/ # 清洗后的数据 │ └── userdict.txt # 自定义词典 ├── output/ │ ├── 词云图.png │ ├── 情感趋势.html │ └── topic_result.csv └── docs/ ├── 设计说明书.md └── 答辩演示要点.md

这段目录设计想说明的不是文件该叫什么名字,而是每个模块的边界。01_crawler.py 只负责把网页内容保存成 CSV,不负责分词;02_preprocess.py 只负责清洗和分词,不负责情感打分。这样你在写文档说明时,能按模块写上“该模块输入是什么、输出是什么、涉及的参数有哪些”,评委老师最吃这一套。

数据文件命名也建议带上时间和来源,例如20250101_news_sina_raw.csv。很多作业数据量不大,但文件名乱写会导致分析阶段搞不清哪份数据是清洗过的,最后发现图表数据源对不上,这是最常见的返工原因。文档说明不用写得像论文那么长,一个设计说明加一个使用说明,加起来 3000 字以内,把模块流程、参数配置、运行步骤写清楚就够了。

3. 把舆情分析跑起来:采集、清洗、情感判断与可视化输出

3.1 爬虫采集模块的最小可用脚本:requests + BeautifulSoup

采集是整个系统最容易“翻车”也最需要控制尺度的环节。先声明一个原则:课程作业请选择允许访问的公开页面,控制抓取频率,不要绕过任何反爬机制;如果你的题目指定了某个平台,优先看该平台是否有公开 API 或开放数据接口。下面这段代码是通用静态列表页的抓取模板,目标 URL 需要替换成你自己选中的合规数据源。

# code/01_crawler.py # 仅用于课程作业的公开信息采集示例:静态新闻列表页 import time import random import requests from bs4 import BeautifulSoup HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ", } session = requests.Session() session.headers.update(HEADERS) def fetch_page(url, retry=3): for attempt in range(retry): try: resp = session.get(url, timeout=10) if resp.status_code == 200: return resp.text except requests.RequestException as e: print(f"第 {attempt + 1} 次请求失败: {e}") time.sleep(2 * (attempt + 1)) return None def parse_list(html): soup = BeautifulSoup(html, "html.parser") items = [] for node in soup.select("div.news-item h2 a"): items.append({ "title": node.get_text(strip=True), "url": node["href"], "source": "example_news", }) return items if __name__ == "__main__": base_url = "https://example.com/news?page=" all_items = [] for page in range(1, 6): # 只抓前 5 页,控制规模 html = fetch_page(base_url + str(page)) if html: all_items.extend(parse_list(html)) time.sleep(random.uniform(1.0, 2.5)) # 随机延时 print(f"采集到 {len(all_items)} 条数据")

这段代码的逻辑分三层:fetch_page负责网络请求和重试,parse_list负责从 HTML 里提取标题和链接,主循环控制页码并做随机延时。三个地方需要按你的数据源调整:HTML 选择器、分页 URL 规则、延时区间。

timeout=10防止某个请求长时间卡死整个流程;retry=3只在网络异常时重试,不是状态码异常时重试;random.uniform(1.0, 2.5)的意义是让请求间隔不均匀,避免触发最简单的频率限制。如果你要抓的数据源是动态渲染页面,requests 拿不到列表内容,常见做法是换成 DrissionPage 或 Selenium 驱动浏览器获取渲染后 HTML,但那样会慢很多,课程作业不优先推荐。

3.2 文本清洗与 jieba 分词:停用词表、自定义词典和去重

采集下来的文本不能直接丢给模型。舆情文本里常见噪声包括:HTML 实体、URL、话题标签、@ 用户名、广告词、重复转发内容。清洗的目标不是把文本删得只剩名词,而是保留能表达情绪和主题的信息,同时去掉明显无意义的内容。

# code/02_preprocess.py import re import jieba # 停用词表:每行一个词,支持 # 注释 def load_stopwords(path="data/stopwords.txt"): stopwords = set() with open(path, encoding="utf-8") as f: for line in f: word = line.strip() if word and not word.startswith("#"): stopwords.add(word) return stopwords STOPWORDS = load_stopwords() def clean_text(text): if not text: return "" text = re.sub(r"http\S+", "", text) # 去掉链接 text = re.sub(r"#\S+#", "", text) # 去掉话题标签 text = re.sub(r"@[\w-]+", "", text) # 去掉 @ 用户名 text = re.sub(r"\s+", " ", text) # 合并空白 return text.strip() def tokenize(text): words = jieba.lcut(clean_text(text)) result = [] for w in words: w = w.strip() if len(w) > 1 and w not in STOPWORDS: result.append(w) return result jieba.load_userdict("data/userdict.txt") # 加载领域词典 if __name__ == "__main__": sample = "【最新】某品牌发布新款旗舰手机,预约量超出预期,市场反应热烈!" print("/".join(tokenize(sample)))

清洗先于分词,顺序不能反。先用正则去掉链接和话题标签,可以避免 jieba 把 URL 里的字母数字切成一堆无意义 token。len(w) > 1过滤单字,是为了去掉“的、了、吗”这类即使不在停用词表里也基本没信息量的字;但要注意,“车”“房”这种单字在特定舆情里是有意义的,所以这条规则要结合你的语料看是否保留。

jieba.load_userdict是中文分词最重要的调参入口。舆情语料里大量出现人名、产品名、新词,比如“苹果手机”“新能源汽车”“双减政策”,默认词库切得不对,后续情感分析会跟着错。userdict.txt 的格式是每行一个词,最多三列:词语、词频、词性。你不确定词频就只写字,jieba 会自行判断。停用词表建议直接用哈工大停用词表做底,再自己追加当前语料里出现的高频噪声词。

3.3 情感分析与热度计算:SnowNLP 阈值标定和分数落库

情感分析模块是这个系统的“人工智能”担当,但它的实现难度比想象中低。最省事的方式是用 SnowNLP 的情感分数接口,它返回 0 到 1 之间的值,越接近 1 越正向。问题是 SnowNLP 基于电商语料训练,直接拿默认的 0.5 分界线跑舆情文本,会把很多句子判偏。我建议你从清洗好的语料里抽 300 到 500 条,人工打标成“正面、中性、负面”三类,然后观察分数分布,再定阈值。

# code/03_sentiment.py from snownlp import SnowNLP # 阈值需要用自己的标注集重新标定,不要照抄 THRESHOLD_POS = 0.65 THRESHOLD_NEG = 0.35 def get_sentiment(text): score = SnowNLP(text).sentiments if score >= THRESHOLD_POS: label = "正面" elif score <= THRESHOLD_NEG: label = "负面" else: label = "中性" return score, label if __name__ == "__main__": cases = [ "产品发布会圆满成功,现场气氛热烈", "小区附近工地夜间施工噪音扰民,居民多次投诉未果", "关于该话题的讨论仍在持续,各方观点不一", ] for c in cases: score, label = get_sentiment(c) print(f"{score:.3f} -> {label} | {c}")

这段代码的核心不是调用 SnowNLP,而是“阈值标定”这一步。你把自己标注好的样本批量跑一遍 scores,画出直方图:正面样本集中在 0.8 以上,负面样本集中在 0.2 以下,中间有明显的低谷区,那阈值就设在低谷区;如果样本分数重叠严重,说明当前模型不适合这批语料,要换训练数据。

热度计算的常见做法是:当天的舆情热度 = 当天提及量 × 正面/负面情绪加权系数。比如“正面”“中性”“负面”分别按 1、1.2、1.5 加权,因为负面舆情通常传播更快。这个公式不用追求严谨,但要能在文档里说清楚你为什么要这么加权。情感分析结果务必落库输出成 CSV,列名建议是text, score, label, date, source,后续可视化直接读这份文件。

3.4 词云、趋势线、饼图与报告导出

展示层决定了大作业的印象分。我的建议是出三张图:词云反映热点关键词,情感趋势折线图反映舆情随时间的演变,情感占比饼图反映整体态度结构。词云有一个中文环境特有的坑:必须指定字体路径,否则中文全部显示成方块。

# code/05_visualization.py from wordcloud import WordCloud import matplotlib.pyplot as plt # 如果是 Windows,常见字体路径是 C:/Windows/Fonts/simhei.ttf # 如果是 macOS,常见路径是 /System/Library/Fonts/PingFang.ttc font_path = "C:/Windows/Fonts/simhei.ttf" def draw_wordcloud(words, filename="output/词云图.png"): wc = WordCloud( font_path=font_path, width=1200, height=800, max_words=100, # 最多显示 100 个词 background_color="white", # 白底黑字,打印不费墨 collocations=False, # 避免出现重复词对 ).generate(" ".join(words)) wc.to_file(filename)

max_words=100是词云最值得调的参数。词太少显得单薄,词太多全是小词。一般根据你的语料量,先从 50 起步,看到核心词仍然突出,再往上加到 100。collocations=False必须开启,它禁止把相邻词自动组合成短语,否则会出现大量无意义的二连词占用词频位置。

趋势折线图我推荐用 pyecharts 生成 HTML 版本,因为可以鼠标悬浮看细节,答辩效果好。情感倾向随时间的变化,按天聚合,横轴日期,纵轴正面、中性、负面各自的发文量,画三条线。最后把三张图组合进一个简单报告里,输出 HTML 报告,比给老师一堆散落图片显得专业得多。

4. 网络舆情分析系统实战避坑:现象、原因与解决

4.1 现象:爬虫跑着跑着被平台限流

你可能会遇到:脚本前几十个页面抓得顺利,突然连续报超时或 403,再重启已经抓不动了。这不是你的代码写错了,而是请求频率触发了服务端的限流策略。很多静态页面虽然没有登录墙,但会在几十分钟内统计单 IP 请求频率。

原因有三个层次:延时太短或固定间隔,导致请求模式像机器;没有保持 session 和 cookie,每次都被当新访客;User-Agent 太老或太通用,被识别为脚本。解决方法是三管齐下:用 1 到 3 秒随机延时替代固定 1 秒;全程复用同一个 requests.Session,让它自动保存 cookie;准备 3 到 5 个不同的 User-Agent 轮换使用,哪怕只是微调版本号。

最稳妥的兜底方案是把抓取改成“断点续爬”。每次保存已抓 URL 到本地文件,重新运行时先读这个文件做去重,这样即使中间断掉,也不用从头抓。这条经验不止用于舆情作业,任何课程爬虫都适用。

4.2 现象:情感分析结果整体偏离,正面文本被判负面

典型场景是:你抓了一轮“某产品价格波动”的新闻,人工读着明明是中立报道,系统却大量输出负面。我见过最离谱的一次,连“市场份额创新高”都被判负面。原因一句话就能讲清:SnowNLP 是在电商评论文本上训练的,它理解的“正面”更多是“质量好、物流快、性价比高”,而舆情文本里大量的“增长、创新、超出预期”并不在它的正向词空间里。

解决分两步。第一步,抽 300 到 500 条文本,人工打标签,跑出每条的 sentiment 分数,画出正负面样本的分布图,重新定阈值。如果正面样本和负面样本的分数区间几乎没有分离,进入第二步:不要再死磕 SnowNLP,改用 TF-IDF 加朴素贝叶斯,在你自己的标注集上重新训练一个情感分类器。

from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import make_pipeline # texts: 清洗后的文本列表, labels: 0负面 1中性 2正面 model = make_pipeline(TfidfVectorizer(ngram_range=(1, 2)), MultinomialNB(alpha=0.5)) model.fit(texts, labels)

ngram_range=(1, 2)表示同时考虑单个词和相邻两个词,这对情绪表达很重要,比如“不够好”和“好”如果只按单字词,语义就扭了。alpha=0.5是拉普拉斯平滑系数,数值越小越依赖训练数据;标注集超过 500 条时设 0.5 比较稳妥,少于 200 条就设 1.0 防过拟合。这个模型训练快,参数也好解释,放到大作业文档里比 SnowNLP 更拿得出手。

4.3 现象:主题聚类数量选不准,每个主题都很碎

LDA 主题聚类输入的num_topics是 K 值,很多人在 3 到 10 之间反复试,效果都是要么主题太粗、五六类都混在一起,要么太碎、一类事件被拆成三四个。原因是对舆情语料来说,K 值的合理区间跟事件覆盖度相关:你只分析一个热点事件,K 设 3 到 5 足够;你分析一个月内某行业全部舆情,K 至少要 8 到 12。

解决方式是用困惑度或主题一致性做粗略筛选。简单做法是跑一组 K 值,比如 4、6、8、10,每轮计算 LDA 模型的 coherence,选一个开始平坦化的拐点。但课程作业不用那么学术,我一般建议直接人工检查两三轮。先把 K 设成 6,跑出来看每个主题下前 10 个词,如果多个主题的前 10 词高度重叠,就调大 K;如果某个主题里同时出现两个明显无关的事件名,就调小 K。

LDA 还需要注意随机性问题。同一份语料、同一个 K 值,跑两次主题词可能不一样。解决方法是设置random_state=42,让结果可复现,这个细节写进文档能加不少分。

4.4 现象:采集量看着吓人,清洗后没剩多少

你可能遇到:脚本显示采集了 5 万条,按标题去重后只剩 8000 条,再按内容清洗后只剩 4000 条,最后能用于分析的只有 3000 条。这不一定是坏事,但说明采集阶段没有做初步过滤,把大量重复转发、短文案、广告内容抓了进来。

原因通常有两个。一是同一个新闻被多个站点转载,标题都一模一样;二是平台自己的转发和滚动更新机制,让同一条内容反复出现。解决方法是加两级去重:第一级按 URL 去重,第二级按标题的哈希值去重。二级去重时建议对标题先做简单归一化,去掉标点符号和空格,否则“某某事件最新进展”和“某某事件最新进展!”会算成两条。

import hashlib def title_hash(title): norm = re.sub(r"[^\w\u4e00-\u9fa5]", "", title) return hashlib.md5(norm.encode("utf-8")).hexdigest() seen = set() def is_duplicate(title): h = title_hash(title) if h in seen: return True seen.add(h) return False

这个哈希去重比直接在 DataFrame 里用drop_duplicates快得多,几万条数据毫秒级完成。需要说明的是,去重只解决“同文不同源”的问题,不解决“同一事件的不同报道”问题,后者应该交给 LDA 主题聚类去归类,而不是在预处理层强行删除。

5. 从“跑通”到“作品”:验证方法、模块封装与交付自查

5.1 三组固定测试集,让每次运行都有可比性

舆情分析系统最容易被问倒的一个问题是:“你凭什么说这个结果是对的?”这不是让你证明算法精度有多高,而是要建立一套可验证的基准。我习惯在处理好的数据里保留三组固定测试集:一组 100 条纯新闻标题,一组 100 条带明显情绪倾向的短评,一组 100 条包含新词、方言词或反讽表达的疑难文本。这三组数据不参与模型训练,只用来做回归验证。

每次改动情感阈值、停用词表或分词词典后,就跑一遍三组测试集,记录正面、中性、负面的命中比例。只要三条数据线的命中率没有异常波动,就可以放心继续。这套办法不需要你懂多少机器学习评估理论,但它能让你在答辩时说出“我在清洗后保留了三组基准数据,每个模块调整后都做回归测试”,这比“模型准确率 95%”可信得多,因为在数据量有限的情况下,准确率本身没什么说服力。

5.2 配置与数据路径外置,临时换主题不慌

课程大作业有个常见意外:老师看完初稿说“你能不能换一个热点事件重跑一遍”。如果你的代码里写死了数据文件名、阈值、字体路径,改一个变量要动五个脚本,一晚上就耗进去了。我的习惯是把所有可变参数集中到一个config.py或 YAML 文件里。

# code/config.py DATA_DIR = "data/raw" OUTPUT_DIR = "output" STOPWORDS_FILE = "data/stopwords.txt" USERDICT_FILE = "data/userdict.txt" FONT_PATH = "C:/Windows/Fonts/simhei.ttf" THRESHOLD_POS = 0.65 THRESHOLD_NEG = 0.35 TOP_N_WORDS = 100 TOPIC_K = 6

其他脚本统一import config,不要自己在文件里再写一遍路径。这个习惯最大的好处是:换数据源时只需要把新 CSV 放进data/raw,改DATA_DIR指向它,重跑整条链路即可。做舆情分析的人都知道,数据一换,分词词典、停用词列表、情感阈值都要跟着微调,配置集中化能让你在半小时内完成一次全流程重跑。

5.3 答辩交付前,我最后两小时一定会做的自查清单

最后这部分是我自己的交付习惯,每次交大作业或项目前都按这个顺序过一遍。第一,从头跑一遍完整的五段式流程,确认没有改路径导致的 FileNotFoundError;第二,检查三张图的字体和标签有没有乱码,尤其是换过机器以后,中文字体路径经常失效;第三,打开输出 CSV,看情感标签列是否有空值,这往往是清洗阶段某条文本为空导致的;第四,在干净环境里用pip install -r requirements.txt装依赖,确认没有遗漏某个库;第五,把 README 里的运行命令照着敲一遍,确保别人能复现。

依赖清单这个环节最容易出问题。很多人代码里用了from collections.abc import Iterable,但 requirements 里只写了pandas,没写具体版本,换一台 python 3.9 的机器直接报错。锁依赖版本时不必追求最新,只要记录你当前环境实测能跑过的版本即可,并在 README 里注明建议的 python 版本。我习惯把pip freeze > requirements.txt生成的完整列表提交到 docs 目录,虽然列表长,但保证复现不出问题。

最后再说一个血泪经验:交付前一定要在另一台电脑或虚拟环境里完整跑一遍。你本机能跑不代表代码没问题,有时纯粹是某个依赖包版本碰巧兼容。网络舆情分析系统本身不复杂,但它依赖链挺长——requests、beautifulsoup4、jieba、snownlp、gensim、wordcloud、pyecharts——任何一个版本不兼容都会在中途报错。提前花一个小时做干净环境验证,比答辩当天翻车强太多。希望这篇笔记能帮你把这个经典的人工智能大作业做扎实,少走我当年走过的弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询