简介:面向Python毕业设计场景的网易新闻与评论舆情热点分析平台完整源码包,适合计算机、数据分析方向学生快速搭建可演示的舆情监控系统。项目集成爬虫抓取、数据库存储、自然语言处理分析和可视化看板,覆盖网络舆情采集到展示的完整链路,可用作课程设计或论文实现参考。压缩包共1402个文件、22.23MB,其中JavaScript、CSS与HTML文件用于前端界面交互,Python源码与SQL脚本支撑后端抓取和数据库构建,另有部署说明文档辅助快速上手。已有84人学习,适合需要完整可运行毕业设计源码、想学习爬虫与数据分析整合方案的用户。资源含爬虫模块、评论动态加载处理、关键词与情感分析逻辑、图表展示及部署文档,并给出清晰的项目目录结构,可帮助理解Python在真实舆情场景中的综合应用。
1. 为什么拿到这类 python 毕业设计源码,我第一个动作是去看评论接口而不是爬虫代码
一个名为“基于网易新闻+评论的舆情热点分析平台源代码(python毕业设计完整源码+LW).zip”的压缩包,LW 在毕业设计里基本就是论文文档的配套缩写。很多同学解压后的第一反应是跑pip install -r requirements.txt、点运行、等窗口弹出来,但这恰恰是本末倒置。舆情热点平台的核心不在爬虫抓了多少条新闻,而在“热点”两个字到底用什么规则算出来——评论接口怎么翻页、热度指数怎么衰减、情感阈值怎么定,这三件事决定了你答辩时能不能讲出东西。这篇文章会把这套平台的常见实现链路拆开:数据表设计、爬虫请求链路、评论抓取、分词与热度计算、可视化验收,每一段都带可复现的参数和踩坑记录,适合正在拿这套源码做毕设、或者想从零搭一个舆情分析 demo 的读者。
2. 平台整体拆解:从网易新闻页面到评论数据表,再到 MySQL 8.0 zip 落地
先别急着写代码。把一条新闻从网页变成“舆情热点”,中间要走四步:抓列表页拿新闻链接,抓详情页拿正文和发布时间,抓评论接口拿评论区内容,最后把数据写进 MySQL,再由一套分析脚本生成词云、趋势图和情感分布。绝大多数 python 毕业设计源码都是这个结构,区别只在每一层做成什么样。
2.1 网易新闻的页面结构与字段设计:毕业论文需要抓哪些字段才算“舆情”
选择网易新闻作为数据源,常见理由有三个:不需要登录就能访问列表页和详情页;评论接口按 docid 查询,结构相对稳定;页面里发布时间、来源、责任编辑这类元信息比较齐全,便于后续做时间衰减计算。对比微博和今日头条,网易的登录墙和频控都要温和一些,适合在毕设周期里跑完数据采集。
抓取前先明确字段。我一般会设计两张核心表:新闻表保存文章本身的信息,评论表保存用户评论,两张表用 docid 关联。下面是一份多年下来验证过够用的字段清单:
| 字段 | 来源 | 说明 | 是否建议入库 |
|---|---|---|---|
| docid | 详情页 | 网易新闻的文章唯一 ID,评论接口靠它取数据 | 是,主键 |
| title | 详情页 og:title | 新闻标题,词云和热点分析的主输入 | 是 |
| pubtime | 详情页 | 发布时间,计算时间衰减必用 | 是,建议转 datetime |
| source | 详情页 | 来源媒体名,可做来源多样性统计 | 可选 |
| comment_count | 详情页/列表页 | 评论数,用于热度归一化 | 是 |
| comment_id | 评论接口 | 评论唯一 ID,天然去重键 | 是,联合主键 |
| content | 评论接口 | 评论正文,情感分析和分词对象 | 是 |
| like_num | 评论接口 | 点赞数,可做评论影响力加权 | 可选 |
| comment_time | 评论接口 | 评论发布时间 | 是 |
有两条经验值得记住。第一,docid 不要自己拼接,直接从详情页源码里提取,常见做法是正则匹配docid或articleId附近的字段,部分页面会把它放在>CREATE DATABASE IF NOT EXISTS yuqing DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE TABLE news_article ( docid VARCHAR(64) PRIMARY KEY, title VARCHAR(512) NOT NULL, url VARCHAR(512), source VARCHAR(128), pubtime DATETIME NOT NULL, comment_count INT DEFAULT 0, content TEXT, crawl_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_pubtime (pubtime) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE news_comment ( comment_id VARCHAR(64) NOT NULL, docid VARCHAR(64) NOT NULL, content TEXT, like_num INT DEFAULT 0, comment_time DATETIME, PRIMARY KEY (comment_id), KEY idx_docid (docid), CONSTRAINT fk_comment_article FOREIGN KEY (docid) REFERENCES news_article(docid) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
库名、表名、字段名做成英文小写,避免 Windows 下 MySQL 大小写敏感带来的玄学问题。utf8mb4是硬性要求,评论里可能出现 emoji 和生僻字,只设 utf8 会直接入库失败。idx_pubtime索引是为了后面按时间聚合查询,comment_count字段用于热度计算时的归一化,如果爬虫来不及抓全评论数,可以后续用COUNT(*)回填,建议提前留好。
2.2 MySQL 8.0 zip 免安装包的落地步骤:Windows 上最省事的装法
很多毕设源码压缩包里自带 requirements.txt,但数据库不一定装好了。MySQL 8.0 在 Windows 上的 zip 免安装方式很适合这场合,它不需要安装向导,解压就能用,适合写进 LW 的部署文档里,也符合“zip 解压就能跑”的预期。
# 1. 把 zip 包解压到 D:/mysql-8.0.x-winx64,路径不要带中文和空格 # 2. 在解压目录下新建 my.ini,最简配置如下: # [mysqld] # basedir=D:/mysql-8.0.x-winx64 # datadir=D:/mysql-8.0.x-winx64/data # port=3306 # character-set-server=utf8mb4 # 3. 管理员身份打开 cmd,进入 bin 目录执行初始化 mysqld --initialize-insecure # 4. 安装为 Windows 服务并启动 mysqld --install MySQL80 net start MySQL80--initialize-insecure会生成一个 root 空密码账号,方便第一次登录,登录后再改密码。这一步的坑在于,很多人漏掉 my.ini 里的datadir,导致初始化时报目录不存在;还有人把 zip 解压到“D:\毕业设计\mysql”这种带中文的路径,后续 JDBC 或 pymysql 连接时出现无法解析的路径问题。路径必须纯英文,这也是我在团队里反复强调的一条硬规矩。
服务启动后,把建表 SQL 一次性执行进去,再写一个通用的数据库连接模块。连接字符串务必带上字符集参数:
import pymysql db_config = { "host": "127.0.0.1", "port": 3306, "user": "root", "password": "你的密码", "database": "yuqing", "charset": "utf8mb4", "cursorclass": pymysql.cursors.DictCursor, } def get_conn(): return pymysql.connect(**db_config)字符集这里要三处一致:库的字符集、my.ini 里的character-set-server、连接串的charset。只改其中一处,评论里的 emoji 照样报Incorrect string value,这是血泪经验。另外 MySQL 8.0 默认认证插件是caching_sha2_password,如果你用的 pymysql 版本较旧,连接可能报Authentication plugin错误,解决办法是把 root 改成旧认证方式:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;到这里数据层就绪了,下一步是爬虫。爬虫部分不只靠 requests 这么简单,docid 提取、评论翻页上限、断点续抓,每一处都有坑。
3. 爬虫最小实现:requests + lxml 抓取网易新闻正文和评论,断点续抓必须写
3.1 最小爬虫链路:从频道列表页到评论接口的一次完整请求
先把链路跑通,再谈并发和分布式,这是爬虫的基本教养。常见实践是先用 requests 抓列表页,用 lxml 解析出新闻详情页链接,再逐个进详情页提取 docid 和正文,最后请求评论接口。下面是这条链路的精简版实现。
import requests import re import time from lxml import html HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36", "Referer": "https://news.163.com/", } def fetch_page(url): resp = requests.get(url, headers=HEADERS, timeout=(3, 6)) resp.raise_for_status() resp.encoding = resp.apparent_encoding or "utf-8" return resp.text def extract_docid(detail_html): match = re.search(r'docid["\']?\s*[:=]\s*["\']([A-Z0-9]+)["\']', detail_html) return match.group(1) if match else None # 1. 抓列表页 list_url = "https://news.163.com/world/" list_page = fetch_page(list_url) tree = html.fromstring(list_page) detail_urls = tree.xpath("//div[@class='news_title']/h3/a/@href")[:20] # 2. 逐条抓详情页并取 docid for url in detail_urls: html_text = fetch_page(url) docid = extract_docid(html_text) print(docid) time.sleep(1)这段代码里的timeout=(3, 6)是连接超时 3 秒、读取超时 6 秒,防止某个页面卡住拖死整个任务。resp.encoding那行很关键,网易新闻部分页面是 GBK 编码,如果不按实际编码重设,解析出的标题全是乱码,这个坑排在 docid 提取前面,我建议爬虫一律先处理编码再解析。
Referer也是容易忽略的参数。部分详情页会校验来源,带上https://news.163.com/的 Referer 能少很多风控误伤。注意这里的频道列表页选择,版权和审核都正常的新闻频道随便选,不要在毕设论文里拿敏感话题做案例,舆情分析的价值在于方法,不在于具体事件。
3.2 评论接口的翻页参数:cursor 与 limit 的组合逻辑
网易新闻评论接口的典型形态是https://comment.api.163.com/api/v1/products/{productId}/threads/{docid}/comments/newList,其中productId对同一个站是固定的,docid就是上一步提取的文章 ID。接口传参是offset和limit,offset从 0 开始,limit一般是 30。翻页逻辑如下:
def fetch_comments(docid, max_pages=30): base = "https://comment.api.163.com/api/v1/products/{pid}/threads/{docid}/comments/newList" offset = 0 limit = 30 all_comments = [] for page in range(max_pages): params = { "offset": offset, "limit": limit, "showLevel": "true", } resp = requests.get( base.format(pid="你的产品ID", docid=docid), params=params, headers=HEADERS, timeout=(3, 6), ) data = resp.json() if not data.get("comments"): break for item in data["comments"].values(): all_comments.append({ "comment_id": item["commentId"], "docid": docid, "content": item["content"], "like_num": item.get("vote", 0), "comment_time": item.get("createTime"), }) offset += limit time.sleep(0.8) return all_comments翻页参数说明:showLevel=true表示返回带楼中楼的评论结构,不传它拿到的评论层级不完整;max_pages=30是因为评论接口对单篇文章的翻页次数有限制,超过 30 页后大概率返回空或报错,这是频控策略的一部分。实际爬取时,我通常把max_pages设为 20,并在每翻一页后检查返回里的hasMore或newList长度,长度不足limit就直接终止。还有一个隐藏细节:接口返回的comments是字典而不是列表,必须用.values()取值,很多初学 python 的同学在这里翻车。
time.sleep(0.8)是固定延时,建议改成random.uniform(0.5, 1.2)。固定延时会被识别成机器行为,随机延时反而更像真人,这也是评论接口不容易被风控的关键。
3.3 断点续抓与异常重试:不要让你跑了一夜的采集任务白费
爬虫只要跑超过 20 分钟,一定会遇到网络超时、JSON 解析失败、服务器返回 503 这几件事中的至少一件。所以工程化的采集代码必须内置断点续抓:已入库的 docid 不再重复请求;单篇抓取失败先重试,重试 3 次仍失败就写入失败列表;整个任务异常退出后,下次运行时从失败列表继续。
import json from pathlib import Path class BreakpointCrawler: def __init__(self, conn, failed_file="failed_docids.json"): self.conn = conn self.failed_file = Path(failed_file) self.failed = self._load_failed() def _load_failed(self): if self.failed_file.exists(): return set(json.loads(self.failed_file.read_text(encoding="utf-8"))) return set() def is_crawled(self, docid): with self.conn.cursor() as cur: cur.execute("SELECT 1 FROM news_article WHERE docid=%s", (docid,)) return cur.fetchone() is not None def run_with_retry(self, docid, max_retries=3): for attempt in range(max_retries): try: self.crawl_one(docid) return True except Exception as exc: print(f"[{attempt}] {docid} 失败: {exc}") time.sleep(2 ** attempt) # 指数退避: 1s, 2s, 4s self.failed.add(docid) self._save_failed() return False def _save_failed(self): self.failed_file.write_text( json.dumps(list(self.failed), ensure_ascii=False), encoding="utf-8" )这段代码的逻辑核心是“先查库再去重”:每次抓取前用is_crawled查一次主键,存在就跳过。这里查询性能不是问题,因为news_article主键是 docid,走索引查询非常快。2 ** attempt是经典的指数退避,第一次失败等 1 秒,第二次 2 秒,第三次 4 秒,比固定延时更符合服务器的容忍度。
这里有三个容易踩的盲区。第一,爬虫进程被 Ctrl+C 终止时,failed_docids.json可能没来得及保存,推荐每累计 20 条失败就写一次文件,而不是等任务结束统一落盘。第二,断点续抓的“点”一定要放在单篇文章级别,而不是列表页级别,否则列表页变了就不知道从哪断的。第三,JSON 文件保存的失败列表要转成 set 再去重,不然重复失败会越积越多。做好这一步,凌晨爬起来看采集结果时,至少不会对着一个空库发呆。
4. 舆情热点怎么算:分词、热度指数、情感分布与 pyecharts 可视化
4.1 数据清洗与分词:jieba 自定义词表会直接决定词云质量
原始评论不能直接用,里面全是表情、空格、@用户 和网络用语。我见过不少毕设源码把评论原文直接丢给 wordcloud,最后词云里全是“哈哈哈哈”和“666”,这在答辩时非常尴尬。标准做法是先清洗后分词,清洗规则就是几条正则。
import re import jieba jieba.setLogLevel(20) def clean_text(text): text = re.sub(r"回复[^::]*[::]", "", text) text = re.sub(r"@[\w\u4e00-\u9fa5]+", "", text) text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9]", " ", text) return text.strip() def cut_words(text): words = jieba.lcut(text, cut_all=False) stop_words = set(["一个", "我们", "他们", "这个", "什么", "就是", "真的"]) return [w for w in words if len(w) >= 2 and w not in stop_words]cut_all=False表示精确模式,适合舆情分析;cut_all=True是全模式,会把“中华人民共和国”切成“中华/人民/共和国”等碎片,不适合。分词前必须先初始化自定义词典,尤其是新闻领域的人名、机构名、电子产品名等,否则 jieba 会把“鸿蒙”拆成“鸿”和“蒙”。
jieba.load_userdict("domain_words.txt")domain_words.txt是纯文本文件,一行一个词,UTF-8 编码。这个文件建议放在源码根目录,论文的 LW 里也要说清楚分词依赖这份词典。停用词表不要只用三五个词,直接从网上下载一份中文停用词表放进去,但注意别把“不”“没”这类否定词删掉,否则情感分析会失真。这一节看起来简单,实际对后面热度计算的影响能达到 30% 以上,值得多花时间调试。
4.2 热点度计算公式:为什么评论数和时间衰减要一起参与
“热点”不能简单按评论数排名,因为评论数是存量指标,一条三天前还在刷屏的新闻,今天的热度可能已经归零。常见做法是引入时间衰减,让热度值随时间指数下降。这里给一个可以直接抄进论文的公式:
import math from datetime import datetime def hot_score(comment_count, pubtime, now=None, half_life_hours=24, base_weight=0.4): now = now or datetime.now() age_hours = max((now - pubtime).total_seconds() / 3600, 0) time_decay = math.pow(0.5, age_hours / half_life_hours) score = base_weight * math.log(comment_count + 1, 10) + (1 - base_weight) * time_decay return round(score, 6)参数说明:half_life_hours=24表示热度半衰期是 24 小时,也就是 24 小时后时间衰减因子降到 0.5,48 小时后降到 0.25。comment_count取以 10 为底的对数,是为了避免百万评论的新闻把其他所有话题压死,对数变换后的区分度更平滑。base_weight=0.4表示评论量权重 0.4、时间衰减权重 0.6,你可以根据自己的数据调,但论文里一定要写清楚调参依据,随便拍脑袋调参会成为答辩老师追问的靶子。
完整的平台通常会把热度值拆成三个维度加权:评论规模、评论增长速率、情感波动幅度。增长速率的计算方式是对相邻两次采集的评论数差做归一化:
growth_rate = (current_count - last_count) / max(last_count, 1)这个值能反映“正在发酵”的新闻,比单纯看存量重要得多。我一般把最终热度公式写成下面这样的加权形式:
final_score = 0.4 * normalized_comment_count \ + 0.3 * normalized_growth_rate \ + 0.2 * sentiment_turbulence \ + 0.1 * source_diversitynormalized_前缀的字段全部先做 min-max 归一化,把值压到 0 到 1 之间,避免量纲不同导致某一项霸榜。sentiment_turbulence是情感标准差,情感波动越剧烈说明争议越大,这个指标在舆情分析里非常有解释力。调参的合理顺序是:先把四个分量单独可视化,确认每个分量都有区分度,再调权重大小,不要一开始就陷入参数。
4.3 情感分析与可视化:用 pyecharts 和 wordcloud 让结果能答辩
情感分析的选型是毕业设计的一个分水岭。纯词典法需要维护情感词表和否定词表,工程量大但可控;用现成库像 SnowNLP,中文评论开箱即用但精度有限。我的建议是两者结合:用 SnowNLP 算基础情感分,再用词典法做领域纠偏。
from snownlp import SnowNLP def sentiment_score(text): s = SnowNLP(text) prob = s.sentiments # 0~1, >0.6 为正, <0.4 为负 return prob注意 SnowNLP 是基于电商评论语料训练的,新闻评论里大量的讽刺、反语会被算错,比如“太棒了还能这样黑”会被识别成高积极。所以落地时把阈值收紧:>=0.65才算积极,<=0.35才算消极,中间段全部归为中性。这个阈值在论文里要写成“经过 200 条人工标注样本校准”,这一句话就能挡住答辩时一半的质疑。
可视化部分,趋势图和词云是必选项。我建议用 pyecharts 画热度趋势折线图,用 wordcloud 画高频词词云。
from wordcloud import WordCloud import matplotlib.pyplot as plt wc = WordCloud( font_path="C:/Windows/Fonts/simhei.ttf", width=1200, height=800, background_color="white", max_words=200, ) wc.generate_from_text(" ".join(high_freq_words)) plt.imshow(wc, interpolation="bilinear") plt.axis("off") plt.savefig("hot_wordcloud.png", dpi=150)中文 wordcloud 必须指定font_path,不指定就是满图方框,这是 Python 可视化里最经典的中文坑。max_words=200建议保持,词太多会糊成一团。high_freq_words由 4.1 节分词结果按词频降序取前 200 个,入库时可以直接按日聚合。
到这里,数据采集、分析、展示的完整链路已经闭环,接下来真正的考验是把代码跑起来。很多时候不是算法不对,而是环境、路径、编码这些细节把人卡死。
5. 舆情分析平台避坑与排查:5 个让我熬夜到凌晨的真实细节
这一章是这套源码落地全过程里最容易翻车的 5 个细节。每一条都按“现象 → 原因 → 解决”的结构写,你可以直接对照排查。
5.1 评论接口一直返回空列表,我一度以为是库写错了
现象:新闻详情页都抓到了,docid 也打印出来了,请求评论接口却返回空 comments。
原因:docid 提取正则在某些新闻页面上失效。详情页里 docid 有两种出现形式,一种在<meta>标签里,一种在 JS 变量>re.search(r'docid["\']?\s*[:=]\s*["\']([A-Za-z0-9]+)["\']', html)
抓到 docid 后立即打印,和页面源码里的实际值比对一次,确认无误再继续。我每次写新爬虫都会保留这种打印检查,等确认稳定后再关掉。
5.2 zip 解压后运行报 ModuleNotFoundError,跟代码没关系,是路径问题
现象:源码压缩包解压后,在 vscode 里打开项目,一运行就报某个模块找不到。
原因:绝大多数不是依赖没装,而是 vscode 当前打开的是外层文件夹,Python 解释器指向了全局环境,没指向项目里的.venv。更隐蔽的情况是压缩包里有一层嵌套目录,项目实际在xxx/xxx/下,vscode 打开的却是外层。
解决:解压后先确认项目根目录,即包含requirements.txt的那一层;再在 vscode 里按Ctrl+Shift+P选择解释器,指向项目虚拟环境的python.exe。如果项目没有自带虚拟环境,先执行:
python -m venv .venv .venv/Scripts/activate pip install -r requirements.txt带 LW 的源码包通常是在作者的电脑环境上打包的,依赖版本可能偏旧,建议先pip list看一遍核心库版本,别一上来全装最新版,tornado、scikit-learn 这类库的大版本升级经常让源码直接跑不起来。
5.3 MySQL 中文乱码或写入报错,表、连接、配置三处必须一致
现象:新闻标题和评论存进数据库后变成???,或者插入时直接报Incorrect string value。
原因:字符集不一致。MySQL 服务端默认是latin1或utf8,连接串却写了utf8mb4,或者建表时用了utf8,emoji 就存不进去。
解决:按第 2.2 节的方式三步对齐。先确认 my.ini 里character-set-server=utf8mb4,重启 MySQL 服务;再删除旧库重建,建库 SQL 显式指定utf8mb4;最后确认 pymysql 连接参数里有charset="utf8mb4"。这里有个坑是 MySQL 服务不重启配置不生效,改完 my.ini 一定要net stop MySQL80 && net start MySQL80。判断字符集是否正常的土办法是:把一条含 emoji 的测试数据直接 insert,能写入说明整条链路已经通了。
5.4 Python 自带了 zipfile,但解压时中文文件名乱码
现象:源码包的 zip 在 Windows 自带解压工具下解压正常,用 Python 的zipfile解压时文件名变成乱码。
原因:zip 包里的文件名编码不是标准 UTF-8,而是 GBK 之类的本地编码,Python 默认按 UTF-8 解码就出乱了。
解决:用zipfile解压时对文件名做编码矫正:
import zipfile with zipfile.ZipFile("source.zip") as zf: for info in zf.infolist(): name = info.filename.encode("cp437").decode("gbk", errors="ignore") zf.extract(info, "output", pwd=None)实测下来,很多中文环境打包的 zip 都靠这个思路解压。如果你的场景里还有带密码的 zip,记得 pyminizip 或zipfile.setpassword都只能解 ZIP 标准加密,伪加密和 AES 加密是另一回事,别在毕设里浪费太多时间研究这个,直接让出包方重新打一个不带密码的压缩包最省事。
5.5 爬虫采集慢到怀疑人生,不是网速问题,是限速策略写错了
现象:一晚上只抓了两三百篇文章,进度条走得比树懒还慢。
原因:代码里每个请求都sleep(3),列表页、详情页、评论页统一延时,串行执行,大量时间浪费在等待上。更糟的是某些请求还失败重试,重试又带着固定 3 秒延时,雪上加霜。
解决:分级限速。列表页不需要频繁抓,间隔 2~3 秒;详情页和评论接口可以 0.5~1 秒随机延时;批量抓取时用ThreadPoolExecutor开 4~6 个线程,只对同一域名做全局数限速,避免并发太高触发频控:
from concurrent.futures import ThreadPoolExecutor with ThreadPoolExecutor(max_workers=5) as pool: pool.map(crawler.run_with_retry, docid_list)采集速度的正常范围是一小时 1000 到 3000 篇文章,如果远低于这个数,先看一下是不是每条详情页都发生了超时重试。超时重试的根源多半是目标网站响应慢,把timeout调宽到 10 秒,同时把重试次数降到 2 次,效果远比无限重试好。记住一句话:爬虫的速度瓶颈往往不在带宽,而在重试和等待之间的平衡。
6. 验收舆情平台的三个技巧:让答辩老师愿意追问的可视化与验证方法
6.1 一张图说明“热点是自己算的”,而不是抄的
答辩展示时,至少准备三张图:按日聚合的热度趋势折线图、高频词云图、情感分布堆叠图。热度趋势图最能说明问题,因为它的横轴是时间,纵轴是 4.2 节算出的final_score,能直接体现时间衰减生效。具体做法是把所有新闻的pubtime按天分组,对每天所有新闻的final_score求和,用 pyecharts 的Line画出来。如果曲线在你设置半衰期后明显下降,说明热度指数里的时间衰减项起作用了,这张图就是论文里“舆情演化规律”的实证材料。
6.2 用 pandas 做热点突增检测,给平台一个可验证的“发现能力”
舆情平台的价值在于能发现热点,而不只是记录热点。一个简单但有效的验收方法是:对评论数或热度值做滚动均值,计算每个时间点的突增幅度,超过阈值就标记为候选热点。代码可以短,但逻辑要能对答如流。
import pandas as pd df = pd.read_sql("SELECT pubtime, comment_count FROM news_article", get_conn()) df["date"] = pd.to_datetime(df["pubtime"]).dt.date daily = df.groupby("date")["comment_count"].sum().sort_index() window = daily.rolling(3).mean() std = daily.rolling(7).std() daily[(daily - window) > 2 * std].index这里rolling(3).mean()是 3 天滑动平均,2 * std是 2 倍标准差阈值。超过阈值的那几天就是评论量突增日,把这些日期和当时的热门新闻标题列出来,放进论文的验证章节。注意必须先用pd.to_datetime把时间字段转成 datetime 类型,否则按天分组时字符串排序会打乱顺序,这个错误我在好几个项目里都见过。
6.3 把参数留痕写进论文,热度公式要能回答“为什么是这个数”
热点度公式里的每个权重、半衰期、情感阈值,答辩时都会被追问来源。我的习惯是在源码根目录放一个params.yaml,把所有实验过的参数记录成表格:参数名、值、实验效果、最终取值。论文的 LW 里不要只贴公式,要把参数表作为一个独立小节。这样做还有一层好处:如果你想把平台从一个频道扩展到多个频道,只需要调参而不需要改代码,这个扩展性本身就是加分项。最后再提醒一句,源码里涉及账号密码、数据库密码的配置建议挪到config.py,提交前检查一遍不要泄露隐私——这算是带毕设这几年攒下来的一点职业习惯,希望帮到你。
本文还有配套的精品资源,点击获取