基于Python的舆情热点分析平台:爬虫、情感分析与可视化实战

发布时间:2026/9/12 13:25:36

基于Python的舆情热点分析平台:爬虫、情感分析与可视化实战 简介基于网易新闻与评论的舆情热点分析平台是一套Python毕业设计完整源码集成爬虫抓取、MySQL存储、NLP文本分析与可视化展示适合计算机相关专业学生用于毕业设计、课程设计也适合舆情分析、数据挖掘初学者进行系统学习和二次开发。资源包共1402个文件压缩包大小22.23MB包含Python程序py、pyc、数据库脚本sql、基于Bootstrap、Layui等前端框架的js/css/html资源以及图片和部署说明文档前后端结构完整目录划分清晰。压缩包内带有python部署说明、数据库建表文件和完整的爬虫、分析、展示模块涵盖新闻列表抓取、评论动态加载处理、关键词提取、情感倾向分析与结果图表展示等关键环节可直接运行或按需修改便于深入理解爬虫逆向、数据清洗、NLP热点挖掘等实现细节。目前已有84人学习下载可作为毕业设计选题参考、课程设计项目模板或舆情分析系统的基础源码。1. 从毕设源码到真能演示的舆情平台差在“评论”这条数据流网上搜“基于网易新闻评论的舆情热点分析平台”能找到不少 python 毕业设计源码但很多下载下来只有爬虫脚本和几张截图跑完第一次抓取就不知道下一步该做什么。这个题目真正的分界点不在“爬新闻标题”而在“评论”这条数据流新闻本身只告诉你发生了什么评论的增量、点赞和情绪才反映公众态度。要把两者合并成可排序的热点分数就得把抓取、清洗、分词、情感分析和可视化串成一条完整链路。这也正是这个项目适合 python 入门的同学做综合练习、也适合答辩现场直接演示的原因。下面按搭这套系统时的顺序拆开讲尽量把参数和坑留在代码附近。2. 基于网易新闻评论的舆情爬虫从 docid 到热门评论接口2.1 先摸清新闻 URL 与评论接口的对应关系做 python 爬虫之前先把数据源结构理清。网易新闻正文页 URL 一般是几级目录加文件名例如https://www.news.163.com/24/1101/12/XXXX.html文件名去掉.html得到的就是 docid。评论接口按 docid 组织最常见路径形式是comment.api.163.com/api/json/post/list/new/hot/{boardid}/{docid}返回 JSON里面同时有 commentCount、hotPosts、newPosts 三个关键字段。数据项来源用途docid正文 URL 文件名拼接评论接口boardid从页面源码或 Network 面板反查拼接评论接口commentCount评论接口 JSON热度分基础值hotPosts评论接口 JSON高赞评论内容、投票数newPosts评论接口 JSON按时间排序的评论用于窗口增量boardid 不一定总是cm开头不同频道会有差异所以我会在抓列表页时把 boardid 一起存进数据库而不是在代码里写死。提取 docid 的写法很简单import re def extract_docid(url: str) - str: # 匹配形如 /24/1101/12/ABC123.html 的路径 pattern r/(\d{4})/(\d{4})/(\d{2})/([A-Za-z0-9_])\.html m re.search(pattern, url) if not m: return docid m.group(4) # 少数 URL 会带 _xxx 后缀这里只保留主 id return docid.split(_)[0]正则里年份四位、月份和日期各两位文件名用[A-Za-z0-9_]而不是点号匹配是为了避免把 URL 上的查询参数带进 docid。提取成功后后续所有请求都以这个字符串为键。2.2 用 requests lxml 抓新闻列表按 href 模式筛链接新闻列表页和正文页我都用 requests lxml 处理。网易改版频率不低CSS 类名经常变所以不要依赖某一个 class 选择器而是先枚举页面上所有链接用 href 模式筛出新闻地址。代码如下import requests from lxml import html as lh HEADERS {User-Agent: Mozilla/5.0 ... Chrome/120.0 Safari/537.36} def fetch_news_links(list_url: str): resp requests.get(list_url, headersHEADERS, timeout15) resp.encoding resp.apparent_encoding or utf-8 doc lh.fromstring(resp.text) links [] for a in doc.cssselect(a): href a.get(href) or if re.search(r/\d{4}/\d{4}/\d{2}/[A-Za-z0-9_]\.html, href): title (a.text_content() or ).strip() links.append({ title: title, url: href if href.startswith(http) else https://www.news.163.com href }) return links这段代码没有写死新闻区块的 class只要页面里存在符合条件的链接就能抓到缺点是会混入少量频道页链接所以入库前还要用标题长度和正文容器判断过滤。resp.apparent_encoding是 requests 根据内容猜测编码抓中文页面时能避免大部分乱码问题。正文抓取也按同样原则先找一个常见正文容器找不到就退化成抓所有p文本拼接。常见容器是classpost_body或idcontent这类具体以抓回来的 HTML 为准。拼接时补一个空行分隔避免段落粘连。2.3 热门评论接口offset/limit 分页与字段筛选评论接口返回的不是简单列表而是一个字典最常消费的字段是 commentCount、hotPosts。hotPosts 里每条评论包含 content、voteCount、replyCount 和 user.nickname。写一个拉取函数def fetch_hot_comments(docid: str, boardid: str, offset: int 0, limit: int 30): api fhttps://comment.api.163.com/api/json/post/list/new/hot/{boardid}/{docid} params {offset: offset, limit: limit} resp requests.get(api, headersHEADERS, paramsparams, timeout10) if resp.status_code ! 200: return [], 0 payload resp.json() comments payload.get(hotPosts) or [] total payload.get(commentCount) or 0 clean [ { nickname: c.get(user, {}).get(nickname, ), content: c.get(content, ), vote: c.get(voteCount, 0), reply: c.get(replyCount, 0), create_time: c.get(createTime, 0), } for c in comments if c.get(content) ] return clean, total常用分页参数是 offset 和 limit一次请求建议控制在 30 条以内offset 按 30 递增。这里用payload.get(hotPosts) or []而不是直接索引是因为热门评论为空时服务端可能不返回该字段。createTime 一般是毫秒级时间戳入库前统一除以 1000 转成秒。2.4 抓取节奏、断点与异常处理毕业设计场景不需要多大吞吐单线程加延时就能满足。我会在每两个请求之间加time.sleep(random.uniform(1, 2))。新闻接口对访问频率比评论接口更敏感评论接口偶尔返回空 JSON 也可能只是该新闻评论较少不一定是封禁。更稳的做法是抓取循环里记住上次抓到的 docid 或 createTime每次启动先查库从断点继续避免重复抓整个列表。对 requests 的 RequestException 和超时做三层重试重试间隔按 1 秒、3 秒、10 秒递增。入库时用 docid 做唯一键重复请求同一篇文章只更新评论数不新增记录后面算热度时才不会重复计数。3. 舆情数据的清洗规则与 SQLite/MySQL 存储结构3.1 三张表的设计新闻表、评论表、热点结果表我会先建三张表news 存新闻元数据comment 存评论明细hotspot 存每个时间窗口算出来的热点结果。news 和 comment 之间用 docid 关联hotspot 里冗余一份 keyword 列表方便页面直接展示。建表 SQL 如下CREATE TABLE IF NOT EXISTS news ( id INTEGER PRIMARY KEY AUTOINCREMENT, docid TEXT UNIQUE NOT NULL, title TEXT, url TEXT, channel TEXT, publish_time INTEGER, comment_count INTEGER DEFAULT 0, content TEXT ); CREATE TABLE IF NOT EXISTS comment ( id INTEGER PRIMARY KEY AUTOINCREMENT, comment_id TEXT UNIQUE, docid TEXT, nickname TEXT, content TEXT, vote_count INTEGER DEFAULT 0, reply_count INTEGER DEFAULT 0, create_time INTEGER, sentiment_score REAL DEFAULT NULL ); CREATE TABLE IF NOT EXISTS hotspot ( id INTEGER PRIMARY KEY AUTOINCREMENT, window_start INTEGER, keyword TEXT, heat_score REAL, positive_ratio REAL, emotion_label TEXT, docid TEXT, UNIQUE(window_start, docid, keyword) );news 表的 docid 建立唯一索引这是整个系统的主关联键comment 表同样用 comment_id 去重防止同一条评论被重复抓入造成评论数虚高。hotspot 表的 window_start 是时间窗口起点按小时整点对齐这样前端画趋势图时能直接按时间排序不需要在查询时再做一次聚合。3.2 清洗HTML 标签、表情符、用户和连续标点评论内容不洗一下后续 jieba 分词和情感分析都会受影响。常见需要处理的是残留 HTML 实体、emoji、用户名、连续重复标点。清洗函数可以写成import re def clean_comment(text: str) - str: text re.sub(r[^], , text) # 去掉残留标签 text re.sub(r\w{1,20}, , text) # 去掉 用户 text re.sub(r[\U0001F000-\U0001FAFF], , text) # 去掉 emoji text re.sub(r([。!?,\.])\1, r\1, text) # 压缩连续标点 return text.strip()噪声类型处理方式注意点残留 HTML 标签[^]正则删除要先于其他规则执行用户名\w{1,20}删除部分昵称含下划线需单独补emoji按 Unicode 扩展区过滤删除后可能留空格再 strip 一次连续中文标点压缩成单个防止分词把句子切碎清理完还要做 md5 去重同一用户在不同新闻下复制粘贴的评论并不少见直接把 content 的 md5 作为辅助去重键能明显降低后面情感聚合时的噪音。3.3 为什么先用 SQLite再平滑迁移 MySQL演示环节没有并发写需求时sqlite3 标准库最省事不用额外装服务。但如果你在简历里写了 MySQL最好把数据库操作封装成独立模块。我的做法是 db.py 里只暴露 insert_news、insert_comment、query_hotspots 几个函数SQLite 和 pymysql 各自实现一份。迁移时只需要把连接串换掉import pymysql conn pymysql.connect(hostlocalhost, userroot, password..., charsetutf8mb4)MySQL 的 text 字段默认排序规则对中文 emoji 不友好建库时就指定 utf8mb4。迁移完成后把 news 表的 docid 唯一索引重建一次评论表则按 create_time 建普通索引后面按时间窗口查增量时速度差异会很明显。4. 从 jieba 分词到时间衰减热点评分与情感分析核心模型4.1 热度公式评论量、点赞量、情绪比和时间衰减热点不能只看评论总数。一条 3 万评论的旧闻躺一周不该比一条当天突发、评论只有 3000 的文章更“热点”。我常用的是时间窗口 对数压缩 半衰期衰减的组合heat 0.5 * ln(comment_count 1) 0.3 * ln(vote_total 1) 0.2 * positive_ratio再乘以 exp(-hours_since_publish / HALF_LIFE)。HALF_LIFE 默认取 6意思是 6 小时前新闻的热度会衰减到当前的一半。用对数而不是原始评论数是为了避免头部新闻把长尾完全压没。import math def heat_score(comment_count: int, vote_total: int, positive_ratio: float, hours_since_publish: float, half_life: float 6.0) - float: base 0.5 * math.log(comment_count 1) 0.3 * math.log(vote_total 1) 0.2 * positive_ratio return round(base * math.exp(-hours_since_publish / half_life), 4)三个权重系数不是拍脑袋定的后面校准时能通过 6.1 节的方法继续调。这里先把评论数、赞数、情绪比压到可对比的量纲再乘时间衰减排序时就能直观比较不同时间窗口的新闻。4.2 jieba 分词与 TF-IDF 关键词抽取先过滤“网易”“新闻”中文分词默认用 jieba多轮抓取后积累一批领域词典把“辟谣、通报、回应”这类舆情动词加进 userdict.txt让它们不被拆散。关键词抽取最简单的是 TF-IDFjieba 自带 extract_tagsimport jieba.analyse STOPWORDS {网易, 新闻, 客户端, 视频, 记者, 我们, 你们} def extract_keywords(text: str, topk: int 8): tags jieba.analyse.extract_tags(text, topKtopk, withWeightTrue) result [] for word, weight in tags: if word in STOPWORDS or len(word) 2: continue result.append(word) return resultTF-IDF 对新闻正文里反复出现的报道套话很敏感所以停用词表至少要包含“网易、新闻、客户端、记者、来源、编辑”。想再高一档可以把每天的所有候选关键词用 TextRank 跑一遍再取交集但毕设阶段 extract_tags 已经够展示效果。4.3 SnowNLP 情感得分与情绪占比聚合情感分析直接用 SnowNLP理由是第一不需要训练集第二对短评的效果在小规模数据上可接受。每条评论拿到一个 0 到 1 之间的 sentiments 值按阈值映射成正面、中性、负面情感得分范围标签 0.4负面0.4 ~ 0.6中性 0.6正面聚合到新闻维度就是统计三类占比把正面比例作为热度的情绪分量。实现from snownlp import SnowNLP def sentiment_label(score: float) - str: if score 0.4: return negative if score 0.6: return positive return neutralSnowNLP 对反讽、表情包基本失效这是语料训练决定的不是代码问题。答辩时被问就说“词典模型兜底 阈值可配”后续换 BERT 或大模型只改动这一个函数即可。4.4 同义词合并与相似新闻聚类同一个事件常被多个频道发一遍标题不同但正文段落大量重叠。直接用 docid 去重只能处理完全重复处理不了“同事件不同稿”。最省事的方案是对每篇新闻的关键词集合算 Jaccard 相似度相似度大于 0.4 就归为一簇def jaccard(a: set, b: set) - float: if not a or not b: return 0.0 return len(a b) / len(a | b)把热搜关键词表里每个候选词和已有簇的“簇关键词”比较命中则并入簇并更新时间窗口。这个逻辑放在热点计算的最后一步先算出每条新闻的 heat_score 再聚簇避免聚类把不同窗口的事件混在一起。真实数据里同一事件通常能聚合出 3 到 5 篇新闻热点总分取簇内最大 heat_score。5. 用 Flask ECharts 搭建舆情热点分析平台5.1 按“平台”而不是“脚本”组织代码目录抓取、计算、展示如果全揉在一个文件里答辩时很难讲清楚数据流。我习惯把工程拆成四块spider 负责采集core 负责分词与热度计算db.py 做持久化app.py 只暴露 HTTP 接口。目录如下news_hotspot/ ├── app.py ├── db.py ├── requirements.txt ├── spider/ │ ├── __init__.py │ └── news_spider.py ├── core/ │ ├── __init__.py │ ├── hotspot.py │ └── sentiment.py └── templates/ └── dashboard.html数据流是单向的spider 写库hotspot 定时读库并写 hotspot 表Flask 从库查结果给前端前端用 ECharts 出图。只要不出现页面直接调爬虫函数这种写法后面扩展定时任务就很容易。5.2 后端Flask 路由与 JSON 接口后端接口不需要太花哨热点列表、情感占比、趋势数据三个接口足够撑起页面。核心路由代码from flask import Flask, jsonify, render_template app Flask(__name__) app.route(/) def dashboard(): return render_template(dashboard.html) app.route(/api/hotspots) def api_hotspots(): rows query_hotspots(limit50) return jsonify({code: 0, data: rows}) app.route(/api/sentiment) def api_sentiment(): rows query_sentiment_summary() return jsonify({code: 0, data: rows}) app.route(/api/trend) def api_trend(): rows query_trend() return jsonify({code: 0, data: rows})三个路由分别对应页面首屏、热榜表格、趋势折线图。query_hotspots 返回的字段包含 window_start、keyword、heat_score、docid前端不需要知道底层 SQL 结构。接口主要返回字段前端用途/api/hotspotskeyword, heat_score, window_start热榜表格/api/sentimentpositive, neutral, negative情感饼图/api/trendtime_point, avg_heat趋势折线图5.3 前端ECharts 折线图与热点表格页面用一个自适应布局上半部分是热度趋势折线图下半部分是热点关键词表格再加一个情感占比饼图。折线图的 setOption 片段const chart echarts.init(document.getElementById(trend)); chart.setOption({ xAxis: { type: category, data: timePoints }, yAxis: { type: value, name: 热度 }, series: [{ name: 热点热度, type: line, smooth: true, areaStyle: {}, data: heatValues }] });xAxis 的 data 从接口返回的 window_start 格式化得到series 里的 heatValues 是同一窗口内 top 10 热点的平均值。ECharts 折线图和词云是两个独立插件词云需要额外引入 echarts-wordcloudscale 和 sizeRange 调大一点词语过密时显示效果更好。5.4 定时抓取APScheduler 让平台自己跑起来平台要展示“自动监测”不能靠手动跑脚本。用 APScheduler 的 BackgroundScheduler 挂两个周期任务每 30 分钟抓取一次最新新闻和评论每 15 分钟计算一次热点。核心代码from apscheduler.schedulers.background import BackgroundScheduler scheduler BackgroundScheduler() scheduler.add_job(run_spider, interval, minutes30) scheduler.add_job(compute_hotspot, interval, minutes15) scheduler.start()两个任务间隔不同是对的爬虫太频繁会给新闻接口压力热点计算要等新评论入库后再跑所以给 15 分钟窗口。演示时如果不想等可以临时把分钟参数改成 seconds。6. 舆情热点分析平台的参数调优与效果验证6.1 半衰期与权重系数怎么调才有说服力HALF_LIFE 和三大权重是热度输出的决定性参数。最简单的标定办法是抓取最近 7 天数据对照网易首页的实时热榜把能对上的人工标成 1其余标成 0。然后小步调参先固定 half_life6把权重在 0.5/0.3/0.2 附近来回试以排序结果重合度为评判目标再把 half_life 分别改成 2、4、8、12 看衰减曲线。舆情事件发酵通常在发布后 2 到 4 小时达到峰值所以 half_life 取 4 到 8 都算合理别设成 24 小时以上。6.2 用 NDCG10 验证热点排序质量人工看排序结果不够量化建议给排名指标加一个 NDCG10。先准备真实相关序列 rel正样本为命中的热搜新闻pred 是模型热度排序结果计算公式import numpy as np def ndcg_at_k(pred_relevance: list, k: int 10) - float: pred pred_relevance[:k] dcg sum((2**r - 1) / np.log2(i 2) for i, r in enumerate(pred)) ideal sorted(pred_relevance, reverseTrue)[:k] idcg sum((2**r - 1) / np.log2(i 2) for i, r in enumerate(ideal)) return dcg / idcg if idcg 0 else 0.0每次调参后重算一遍 NDCG把参数组合和分数记在同一张表里比“凭感觉看起来准了”更有说服力。注意这里传的 pred_relevance 是每条结果的真实相关性取值 0 或 1列表顺序按模型排序来取。6.3 部署清单python 版本、编码与 403 处理复现这套系统时建议直接用 python 3.10 或 3.11太老的版本对 snownlp 依赖的 numpy 不友好装完依赖后第一件事是用 VSCode 配置 python 环境把解释器选到项目的 venv 里避免依赖装错位置。文件读写统一用 utf-8否则评论里的繁体字容易在写入 SQLite 时乱码。接口 403 时先看 User-Agent 是否真实浏览器再看是不是抓取频率过高把 sleep 区间拉长并补上浏览器 Cookie 头通常能恢复。把 HALF_LIFE 调成 6 小时后重新抓一天数据再看 NDCG 涨幅这是最容易上手的一条校准路径。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/9/12 13:20:36

2026年大模型技术对比:ChatGPT与Gemini核心差异与应用

1. 2026年大模型技术格局前瞻当我在2023年第一次使用GPT-4时,那种震撼感至今记忆犹新——它不仅能流畅对话,还能解决复杂的编程问题。三年后的今天,大模型技术已经演进到令人惊叹的程度。2026年的大模型领域,ChatGPT和Gemini两大技…

2026/9/12 17:40:56

通达信日线数据本地解析:从.day文件到量化回测的Python实践

简介:这是一个面向量化投资初学者与Python数据分析用户的通达信日线数据读取脚本,核心解决从通达信(TDX)本地安装目录中解析非标准行情文件、提取日线历史数据的问题,无需依赖网络API或付费数据源。脚本基于Python3编写…

2026/9/12 17:40:56

CS自学指南:从数学基础到领域纵深的一套完整课程路径

CS自学指南:从数学基础到领域纵深的一套完整课程路径 【免费下载链接】cs-self-learning 计算机自学指南 项目地址: https://gitcode.com/GitHub_Trending/cs/cs-self-learning 打开 CS自学指南 仓库,你会看到几十门名校计算机课程按模块排好&…

2026/9/12 17:40:56

Java随机数生成器原理与应用详解

1. Java随机数生成器核心原理剖析java.util.Random是Java标准库中最基础的伪随机数生成器实现,它采用线性同余算法(LCG)作为核心生成机制。这个48位的随机数生成器在Java 1.0时代就已引入,其设计参考了Donald Knuth在《计算机程序设计艺术》中提出的经典…

2026/9/12 2:05:33

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/12 3:55:12

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/12 10:09:03

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/12 0:04:17

MATLAB仿生优化框架:长鼻浣熊算法多策略融合实现

简介:本资源是一份面向智能优化算法研究者与MATLAB初学者的仿生智能算法实践代码包,聚焦于长鼻浣熊优化算法(COA)的多策略改进与性能验证。针对传统COA易陷局部最优、收敛精度不足等问题,作者融合Circle映射初始化提升…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 JavaWeb 的校园一卡通管理系统的设计与实现 基于 JavaWeb 的校园卡业务管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 Java 的图书馆借阅管理平台的搭建与实现 基于 Java 的图书馆综合管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/12 6:29:36

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/12 14:32:17

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/12 6:37:43

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码