Python舆情分析大作业:从数据采集到可视化的完整实战攻略
简介一份基于Python的人工智能大作业网络舆情分析系统完整项目面向计算机及相关专业学生适用于期末大作业、毕业设计或个人项目实战练习。该项目经导师指导并通过评审最终得分98分源码已在本地编译运行并严格调试覆盖舆情数据采集、文本处理、统计分析与可视化展示等环节附带详细说明文档、图表生成脚本、配置代码及数据表格有助于使用者快速理解系统设计思路并二次开发。压缩包共118个文件主要类型包括Python脚本、Java辅助类、文本说明、Markdown文档、Jupyter笔记及Excel数据表等整体大小45.2MB目录按功能模块划分清晰易检索并包含相关图片素材便于对照验证界面与图表效果。目前已有143人学习下载适合需要可运行高分项目作为参考的开发者。1. 网络舆情分析大作业为什么说这是一条完整的技术链路期末周拿到基于 Python 的人工智能大作业这个题目时很多人的第一反应是舆情分析不就是用爬虫抓点评论再画几张图吗真动手才发现从数据采集、文本清洗、情感计算到可视化展示是一条完整的技术链路任何一环断了都交不出东西。这套高分项目源码加全套资料解决的就是这个痛点它不是给你一段跑完就完的代码而是把舆情分析从采集到答辩展示的每一步都铺好了。适合两类人一是想快速复现、稳妥拿分的大三学生二是想把这套流程改造成自己课程设计底座的开发者。下面按照我在实际项目里拆解的顺序把每个模块怎么用、参数怎么调、坑在哪讲清楚。2. 数据采集层用 requests 与 BeautifulSoup 把舆情评论捞回来我拿到这套资料时最关心的就是数据从哪来、怎么存。大多数舆情大作业的难点从来不是算法而是没有干净、足量的文本数据。这一章先把采集层的架构和代码讲清楚后面分析层才能站得住。2.1 三层架构与模块划分这套系统整体分成采集层、分析层、展示层三个部分对应到源码包里的目录也很清晰spider/负责请求和解析analysis/负责分词和情感计算visual/负责图表生成和报告导出。我第一次看到这个结构的时候比较意外因为很多大作业源码是把三层揉在一个文件里答辩拆分时说不清楚而这份源码的模块边界做得比较干净。采集层最核心的选型是 requests 搭配 BeautifulSoup。有人问为什么不用 Scrapy我一般会解释对于期末大作业的数据量级——通常几千条评论——requests 足够而且调试时能直接看到请求和响应不用像 Scrapy 那样起一个完整爬虫工程。当你需要在 Jupyter 里边写边看返回结果时这个选择能省很多时间。2.2 评论列表抓取与请求参数来看采集层最基础的一个函数源码里抓取评论列表的部分是这个套路import requests from bs4 import BeautifulSoup import time import random def fetch_comments(url, page1): 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, Accept: text/html,application/xhtmlxml,application/xml;q0.9 } params {page: page} resp requests.get(url, headersheaders, paramsparams, timeout10) if resp.status_code 200: resp.encoding utf-8 soup BeautifulSoup(resp.text, html.parser) items soup.select(.comment-list .item .text) return [item.get_text(stripTrue) for item in items] return []这段代码的逻辑是先把请求头里的User-Agent伪装成浏览器避免被目标站点直接拒绝再把页码作为 GET 参数传过去拿到响应后强制用utf-8解码然后通过 CSS 选择器定位评论节点。这里的timeout10是有讲究的如果不设置网络异常时脚本会一直挂着影响后面批量采集的稳定性。我建议你根据自己的数据源修改选择器和翻页参数。比如某些平台的评论是>import csv import os def save_comments(comments, file_pathdata/comments.csv): file_exists os.path.isfile(file_path) with open(file_path, a, encodingutf-8, newline) as f: writer csv.writer(f) if not file_exists: writer.writerow([content, source, time]) for c in comments: writer.writerow([c, target_site, time.strftime(%Y-%m-%d %H:%M:%S)])用追加模式a写入而不是覆盖模式这样即使采集中途断了已抓到的数据不会丢。newline是 csv 模块在 Windows 下的必备参数不加的话每行中间会多一个空行看起来是小问题但会让后面的 pandas 读取多出很多空值。每次数据进来时打上source和time字段后面做时间趋势分析时可以直接按日期聚合。我在复现时发现采集速度不能拉满。有些站点对单 IP 的访问频率很敏感所以源码里建议在循环中加time.sleep(random.uniform(1, 3))让每次请求间隔随机化。这是爬虫的基本礼仪也是让任务能跑完的保命手段。提示抓取公开评论前先看目标站点 robots 协议和用户协议只用于学习实验别碰需要登录才能看的私有数据。3. 文本挖掘与情感分析分词、打分、关键词提取三件套数据到手后真正体现人工智能含量的部分是文本分析。这一章讲清楚三个核心操作分词与停用词过滤、SnowNLP 情感打分、TF-IDF 关键词提取。这套源码里这三件套是连成一条流水线的我按顺序拆开说。3.1 jieba 分词与停用词过滤原始评论是一段一段的完整句子计算机没法直接统计词频第一步要分词。源码里用 jieba这是 Python 中文分词的标配。分词之后还有一个关键动作过滤停用词。停用词就是的了是在这类没有实际信息量的词不过滤的话后面做词云时会满屏都是这些虚词。import jieba import pandas as pd def load_stopwords(pathstopwords.txt): stopwords set() with open(path, r, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) return stopwords def clean_text(text, stopwords): words jieba.lcut(text) filtered [] for w in words: if not w.strip(): continue if w in stopwords: continue if len(w) 1: continue filtered.append(w) return .join(filtered)jieba.lcut返回的是列表比jieba.cut的生成器更直观方便调试。停用词表建议自己按数据场景加词比如你的舆情对象是某款手机那手机产品这类高频但无区分度的词也可以丢进停用词表。len(w) 1这个判断能去掉单字噪声比如语气词啊哈。把清洗后的文本存回 DataFrame 时我一般会新增一列这样后面情感分析和词云都基于清洗后的列不需要重复分词。这套源码里给到的做法也是类似的它用df[clean_content] df[content].apply(lambda x: clean_text(x, stopwords))一次性生成新列比循环遍历效率高。3.2 SnowNLP 情感打分与阈值划分情感分析部分是项目的灵魂。源码选的是 snownlp 库它给每段文本打一个 0 到 1 之间的情感分数越接近 1 越正向越接近 0 越负向。注意这个分数并不是分类结果需要自己划阈值。from snownlp import SnowNLP def sentiment_label(text, pos_th0.6, neg_th0.4): if not text.strip(): return neutral s SnowNLP(text) score s.sentiments if score pos_th: return positive if score neg_th: return negative return neutral这里有两个可以调的参数pos_th和neg_th。默认 0.6 和 0.4 是经验值我实际测试时发现不同平台的评论语言风格差异很大知乎评论用词偏书面分数偏高短视频评论大量使用口语和反讽0.6 的阈值会把很多明显负向的内容判成中性。所以你在答辩前最好取一部分数据人工标注再回过来调阈值。源码里把这组参数放在config.py中就是方便你反复试。需要提醒的是snownlp 对否定句式容易判错比如这个功能一点都不好用模型可能看到好用就给高分。这是朴素贝叶斯模型的天然短板源码里没有做过于复杂的优化如果大作业想拿高分可以在答辩时主动说这个局限并演示如何用规则修正——这比假装没有问题更能体现工程思维。3.3 TF-IDF 关键词提取除了情感分布舆情分析还需要知道大家在讨论什么。关键词提取源码用的是jieba.analyse.extract_tags这个函数背后是 TF-IDF 算法——某个词在一篇文档中出现得多但在整个语料中出现得少说明它有区分度权重就高。import jieba.analyse def extract_keywords(text_list, top_k20): corpus .join(text_list) tags jieba.analyse.extract_tags( corpus, topKtop_k, withWeightTrue, allowPOS(n, v, adj, nz, vn) ) return tagstopK20控制返回多少个关键词withWeightTrue会把每个词的 TF-IDF 权重一并返回方便后续排序和可视化。allowPOS这个参数是我比较欣赏的一个细节它限定只提取名词、动词、形容词等有实际含义的词性把副词、连词全部过滤掉。如果你不设这个参数会发现关键词里混进很多非常还是之类的高频副词词云就花了。我复现的时候把这份关键词结果按权重排序后导出成 CSV然后和情感分数列做交叉分析能看出来负向情感集中在哪些主题词上。这一步不是源码强制要做的但对答辩很有价值它从大家情绪如何深化到了大家为什么情绪不好。4. 结果可视化与答辩展示pyecharts 大屏和词云怎么搭分析结果算出来后,还需要一个直观的展示。这一章讲从数据到图表的完整落地覆盖 pyecharts 柱状图和饼图、词云图、以及把多张图合成一个 HTML 报告。对于期末答辩来说,这一章的完成度直接影响第一印象。4.1 pyecharts 情感分布柱状图源码的可视化部分用的是 pyecharts它生成的是基于 ECharts 的 HTML 图表交互效果比 matplotlib 静态图好一个档次。看看情感分布的柱状图怎么画from pyecharts.charts import Bar from pyecharts import options as opts def draw_sentiment_bar(labels, counts, save_pathsentiment_bar.html): bar Bar() bar.add_xaxis(labels) bar.add_yaxis( 评论数, counts, category_gap40%, label_optsopts.LabelOpts(positiontop) ) bar.set_global_opts( title_optsopts.TitleOpts(title舆情情感分布), yaxis_optsopts.AxisOpts(name条), xaxis_optsopts.AxisOpts(name情感类别) ) bar.render(save_path)category_gap40%是柱间距占类目宽度的比例调大柱子变细调小柱子变粗。LabelOpts(positiontop)让每根柱子顶部直接显示数字答辩展示时不用眼睛去对数轴。render默认输出 HTML 文件双击浏览器就能打开不需要额外部署服务。我一般会把柱状图、饼图、折线图分别渲染成三个 HTML 文件再整合到一个索引页。源码里就是这么做的visual/目录下有几个独立的绘图函数每个函数接收 pandas 的 Series 或 list返回值是渲染好的 HTML 路径。4.2 词云图与中文字体问题词云是整个大作业里最出效果的一张图。wordcloud 库本身不复杂但有一个高频翻车点默认字体不包含中文生成的图片全是方框。源码把字体路径显式指定为msyh.ttc这是微软雅黑。from wordcloud import WordCloud import matplotlib.pyplot as plt def draw_wordcloud(word_freq, font_pathmsyh.ttc, save_pathwordcloud.png): wc WordCloud( font_pathfont_path, width800, height600, background_colorwhite, max_words100 ) wc.generate_from_frequencies(word_freq) plt.imshow(wc, interpolationbilinear) plt.axis(off) plt.savefig(save_path, dpi150, bbox_inchestight)max_words100是词云上最多显示词的数量。generate_from_frequencies接收的是字典格式键是词、值是权重正好对应 3.3 节extract_tags返回的 TF-IDF 结果。如果你电脑上没有msyh.ttc可以用find font -iname *.ttc找系统中文字体路径否则必须安装中文字体。这一步在 Linux 服务器上跑的时候尤其容易踩。4.3 一键导出 HTML 报告答辩时如果一个个打开 HTML 文件场面会很散。源码里提供了一个build_report的函数把图表数据和结论文字拼到一个报告页里def build_report(sentiment_counts, keywords, outputreport.html): summary f共分析评论 {sum(sentiment_counts.values())} 条正向占比 \ f{sentiment_counts.get(positive, 0) / max(sum(sentiment_counts.values()), 1):.1%} html f html headmeta charsetutf-8title舆情分析报告/title/head body h1{summary}/h1 h2情感分布/h2 iframe srcsentiment_bar.html width100% height400/iframe h2高频关键词/h2 p{keywords}/p img srcwordcloud.png width800 /body /html with open(output, w, encodingutf-8) as f: f.write(html)iframe把前面生成的柱状图直接嵌进报告页img嵌入词云图这样整个答辩展示只需要打开一个report.html。max(sum(..., 1)这个写法是防止评论数为零时除零报错。报告里我建议你也输出原始数据的抽样表格比如正向和负向评论各挑 5 条典型的附上让老师直观看到模型判得对不对。源码的report.html模板里预留了p{keywords}/p的位置你可以在summary里叠加更多指标比如时间趋势峰值。5. 避坑合集舆情分析项目里最容易翻车的五个现场这个项目我从复现到改造踩过不少坑很多是运行时才暴露的。这一章把最常见的五个问题按现象 → 原因 → 解决写清楚你照着排查比一个个搜报错效率高得多。5.1 目标站点返回 403User-Agent 伪装不到位现象requests.get返回状态码 403或者返回的页面是验证码页面解析不到任何评论数据。原因目标站点识别出请求来自脚本。很多站点会校验User-Agent、Accept-Language等请求头默认的python-requests/x.xx一眼就能识别。解决把请求头补全至少要带User-Agent和Accept如果还不行用fake_useragent库随机生成 UA并在每次请求之间加随机延时。源码里给的headers字典是我的基础模板你可以在里面继续加Referer模拟从站内页面跳转过来的路径。5.2 中文乱码编码声明不统一现象写入 CSV 后打开全是乱码或者UnicodeEncodeError: ascii codec cant encode characters。原因requests响应的encoding默认可能是ISO-8859-1直接用resp.text就会把 UTF-8 的中文解析成乱码写入文件时 Python 默认编码在 Windows 下不是 UTF-8。解决resp.encoding utf-8要在resp.text之前设置打开写入文件时显式传encodingutf-8如果 CSV 要用 Excel 打开不乱码写入时用utf-8-sig而不是utf-8。这是我改了三次才顿悟的细节utf-8-sig会在文件头加 BOMExcel 才认。5.3 SnowNLP 对否定句和反讽误判现象明显负向的评论被判成正向这个手机电池一点都不耐用得分 0.7。原因snownlp 是基于朴素贝叶斯训练的它对否定词和反讽没有专门的语法建模看到耐用这类正倾向词就整体拉高分数。解决先加一个简单的否定规则——如果句子中出现不没不太毫无等词且紧邻词是正倾向词就把分数压到1 - score。这个规则写在sentiment_label里代码量小但能让准确率明显提升。答辩时主动展示这个规则函数的输出比单纯甩一个模型有说服力。5.4 pyecharts 图在 Jupyter 里不显示现象在 Jupyter Notebook 里调用bar.render_notebook()没反应或者只看到空白输出。原因pyecharts 版本不同渲染内核要求不同老版本是新版本渲染器冲突或者在 JupyterLab 里没有加载 ECharts 的 JS 依赖。解决优先用bar.render(xxx.html)生成独立 HTML 文件浏览器打开如果一定要在 Notebook 里看检查pyecharts版本并安装配套的echarts依赖包或者改用chart.notebook()新接口。我的习惯是直接渲染到 HTML反正答辩也要用浏览器展示。5.5 pandas 与 numpy 版本冲突现象导入 pandas 时报AttributeError: module numpy has no attribute float或者跑得好好的代码突然报错。原因numpy 2.x 移除了np.float等旧别名老代码里如果用了这些写法就会崩pandas 版本和 numpy 版本不匹配也会出现类似问题。解决查看requirements.txt把 numpy 固定到 1.26.xpandas 固定到 2.0 以上这是目前兼容性最稳的组合。安装时用pip install numpy1.26.4 pandas2.0.3精确锁版。如果是在虚拟环境里跑建议每条依赖都锁大版本不要全用最新版。6. 进阶技巧把一次性大作业改成参数化可复用的分析管线如果你的目标不只是交差而是想把这套源码变成以后课程设计、实习作品集里能反复用的底座我建议你做两件小事配置解耦和流程封装。第一件事是把所有可变参数从代码里抽出来。源码里其实已经把阈值、爬取 URL、关键词数量散落在各个函数里我拿到后第一件事就是建一个config.py# config.py CRAWL_URL https://example.com/comment?page{page} HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 } POS_TH 0.6 NEG_TH 0.4 TOP_K 20 OUTPUT_DIR output这样换一个舆情事件只需要改CRAWL_URL和阈值不用在爬虫函数和情感函数里翻找。第二件事是把整套流程封装成一个入口函数输入是数据源输出是报告 HTMLdef run_pipeline(crawl_urlNone, csv_pathNone): if crawl_url: data crawl_and_save(crawl_url) else: data pd.read_csv(csv_path) data[label] data[content].apply(sentiment_label) counts data[label].value_counts().to_dict() keywords extract_keywords(data[clean_content].tolist()) build_report(counts, keywords, outputoutput/report.html) print(分析报告已生成output/report.html)这个入口函数我一般会在期末项目里作为main.py的调用目标。老师运行一份测试数据直接看到输出报告不关心内部细节这也符合交付一个系统而不是代码片段的要求。从那以后我每次拿到任何课程设计或比赛源码第一件事都不是急着看算法细节而是先跑通数据流再拆出可变参数最后画清模块边界。这个习惯让我复现项目的速度提高了不少。希望这份舆情分析源码和全套资料也能让你少走这些弯路期末顺利交出能打的作品。本文还有配套的精品资源点击获取