资讯动态

基于网络舆情的Python股票分析系统:从数据采集到可视化实战

发布时间:2026/9/28 16:40:56 来源:尧图企业网站定制
简介这是一套面向计算机相关专业学生与Python实战学习者的高分大作业资源主题为基于网络舆情的股票分析系统项目经导师指导并通过评审得分98分。系统整合网络爬虫、自然语言处理、股票价格接口、数据可视化、预测模型与用户界面等模块可帮助读者理解舆情数据采集、情感分析与股价走势关联的完整实现思路。资源包共89个文件以49个py源码为核心辅以11个csv数据集、9个html页面、3个docx项目文档、2个sql建库脚本及css、png、yml等配置与静态资源压缩包约2.87MB目录结构清晰便于按模块检索学习。目前已有86人学习下载。读者可从中获得可本地编译运行的完整源码、立项与需求分析文档、数据库脚本及环境配置适合作为课程设计参考或数据分析实战练习帮助快速掌握舆情分析与股票预测的工程化流程。1. 从一份高分大作业说起网络舆情 Python 股票分析系统到底在做什么很多同学第一次看到「基于网络舆情的 Python 股票分析系统」这个题目第一反应是去搜「python 股票分析系统 源码」想直接找一份能交差的代码。但真正拿过高分的方案核心从来不是代码量而是把「舆情」和「行情」两条数据线接在了一起。简单说这个系统要做的事是用 Python 爬虫抓取股吧、财经资讯、社交平台上跟某只股票相关的文本做情感倾向判断再把情感得分和股票的历史行情、成交量对齐最后用可视化界面把「舆情热度」和「价格走势」放在同一张图里呈现。它解决的是「消息面到底有没有提前反映在价格里」这个老问题适合做课程设计、毕业设计也适合想入门 python 数据分析与可视化的同学。下面我按自己搭过的一套方案把选型、代码、参数和踩过的坑讲清楚。2. 数据层怎么搭舆情文本和行情数据的两条采集链路2.1 为什么舆情数据不能只靠一个来源做这个系统最容易翻车的地方是以为爬一个股吧就够了。实际跑下来你会发现单一来源的文本量在非交易时段几乎为零而且情绪分布极度偏斜——要么全是看多要么全是骂声。我一般会同时接三类数据财经资讯标题、股吧帖子标题、以及带话题标签的短文本。三类数据的时间戳精度不同资讯通常到分钟帖子到秒短文本到秒所以入库时统一转成datetime并保留原始时间字段方便后面做时间对齐。行情数据这条线相对标准用akshare或tushare拉日线即可。日线字段至少保留日期、开盘、收盘、最高、最低、成交量、涨跌幅。注意复权问题做舆情和价格相关性分析时建议用前复权价格否则除权当天的跳空会被误判成舆情冲击。import akshare as ak import pandas as pd # 拉取某只股票的前复权日线start_date/end_date 按需改 df_price ak.stock_zh_a_hist( symbol600519, perioddaily, start_date20240101, end_date20241231, adjustqfq # qfq前复权避免除权跳空干扰 ) df_price df_price.rename(columns{ 日期: date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume, 涨跌幅: pct_chg }) df_price[date] pd.to_datetime(df_price[date]) df_price df_price.sort_values(date).reset_index(dropTrue) print(df_price.head())这段代码的关键参数是adjustqfq它决定价格序列是否连续。perioddaily对应日频如果你要做日内舆情冲击可以改成分钟频但数据量和缺失值处理成本会明显上升。symbol用六位代码不带交易所前缀。跑完先看df_price.isnull().sum()成交量列偶尔会有空值直接dropna会丢交易日建议用前值填充再标记。2.2 舆情采集的请求节奏和字段设计采集端我一般用requests加BeautifulSoup不追求框架因为课程设计场景下稳定比性能重要。请求头里必须带User-Agent否则很多页面直接返回空。翻页用page参数每页间隔 1 到 2 秒别贪快。入库字段建议固定为stock_code、publish_time、title、source、url。正文可以先不抓标题的情感信号在短周期里已经够用抓正文会让清洗成本翻倍。import requests, time, random from bs4 import BeautifulSoup HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } def fetch_titles(url_tpl, pages5): rows [] for p in range(1, pages 1): resp requests.get(url_tpl.format(pagep), headersHEADERS, timeout10) if resp.status_code ! 200: continue soup BeautifulSoup(resp.text, html.parser) for item in soup.select(.article-item): # 选择器按目标站点结构调整 title item.get_text(stripTrue) if title: rows.append({title: title, source: forum}) time.sleep(random.uniform(1.0, 2.0)) # 随机间隔降低被封风险 return rows data fetch_titles(https://example.com/list?page{page}, pages5) print(len(data))timeout10是必须的没有超时设置的请求在批量跑时会卡死整个流程。random.uniform(1.0, 2.0)比固定sleep(1)更稳固定间隔反而容易被识别。选择器.article-item只是示例实际要按目标页面结构改改完先用print(soup.prettify()[:2000])确认结构再写循环。如果返回条数明显少于预期先查是不是被重定向到了登录页。3. 情感分析选型SnowNLP、词典法还是预训练模型3.1 三种方案的适用边界课程设计里最常见的选择是SnowNLP优点是装完就能用缺点是金融领域语料上准确率一般遇到「利好出尽」「高位放量」这类反讽或专业表达容易判反。词典法比如知网情感词典 金融极性词表可控性强但维护词表费时间。预训练模型效果最好但依赖transformers和模型文件环境配置对新手不友好而且推理速度慢几千条文本还能接受几万条就要考虑批量推理。我的建议是如果只是交作业、数据量在五千条以内用SnowNLP打底再叠加一个金融极性词修正表如果要做量化策略回测直接上预训练模型别在词典上耗太久。from snownlp import SnowNLP # 金融场景修正词表正向加分负向减分 POS_WORDS [涨停, 利好, 超预期, 回购, 增持] NEG_WORDS [跌停, 利空, 暴雷, 减持, 亏损] def sentiment_score(text): base SnowNLP(text).sentiments # 返回 0~1越接近 1 越正面 for w in POS_WORDS: if w in text: base min(1.0, base 0.15) for w in NEG_WORDS: if w in text: base max(0.0, base - 0.15) return base print(sentiment_score(公司发布回购公告市场情绪明显回暖)) print(sentiment_score(业绩暴雷机构大幅减持))SnowNLP(text).sentiments返回的是概率值不是极性标签所以后面做聚合时要么取均值要么按 0.5 切分统计正负比例。修正词的加减幅度 0.15 是我试出来的经验值加太多会让分数饱和失去区分度。注意SnowNLP对长文本会先分词再算标题这种短文本反而更稳。3.2 把逐条情感聚合成日度舆情指标单条情感分数没法直接和日线对齐必须按股票、按天聚合。我一般算三个指标当日平均情感分、正面帖子占比、舆情热度帖子数。热度要做归一化否则不同股票的讨论量差异会让模型偏向热门股。import pandas as pd df_text[date] pd.to_datetime(df_text[publish_time]).dt.date df_text[score] df_text[title].apply(sentiment_score) df_text[is_pos] (df_text[score] 0.5).astype(int) daily df_text.groupby([stock_code, date]).agg( sent_mean(score, mean), pos_ratio(is_pos, mean), heat(title, count) ).reset_index() # 热度做 min-max 归一化避免量纲差异 daily[heat_norm] (daily[heat] - daily[heat].min()) / \ (daily[heat].max() - daily[heat].min() 1e-9) print(daily.head())1e-9是防止分母为零的后悔药某只股票当天只有一条帖子时很容易触发。pos_ratio比sent_mean更抗极端值一条措辞极端的帖子会把均值拉偏但占比变化有限。聚合完记得检查日期连续性停牌日和周末没有帖子是正常的后面和行情合并时用merge而不是直接按位置对齐。4. 舆情与行情的对齐合并、滞后和相关性验证4.1 合并时最容易错的时间口径舆情是全天产生的行情是收盘后确定的。如果你把当天所有帖子都算作「当天信息」就会引入未来函数——收盘后发的帖子不可能影响当天收盘价。我的做法是只取交易日 15:00 之前的帖子算当日舆情15:00 之后的归到下一交易日。这一步不做回测结果会好得离谱实盘一跑就原形毕露。# 只保留交易日 15:00 前的文本 df_text[publish_time] pd.to_datetime(df_text[publish_time]) df_text[trade_date] df_text[publish_time].dt.normalize() mask df_text[publish_time].dt.hour 15 df_text.loc[~mask, trade_date] df_text.loc[~mask, trade_date] pd.Timedelta(days1) # 与行情按日期合并 merged pd.merge( df_price, daily, left_ondate, right_ontrade_date, howleft ) merged[[sent_mean, pos_ratio, heat_norm]] \ merged[[sent_mean, pos_ratio, heat_norm]].fillna(0) print(merged[[date, close, sent_mean, pos_ratio, heat_norm]].tail())howleft保证行情日期为主舆情缺失填 0 而不是丢行。填 0 的含义是「当天无讨论」比填均值更符合业务逻辑。合并后先看merged.shape是否等于行情行数不等就说明舆情侧有重复日期回去查groupby是不是漏了stock_code。4.2 用滞后相关确认舆情有没有领先性做完合并别急着画图先算一下舆情指标和次日涨跌幅的相关系数。如果当期相关高、滞后一期就掉下去说明舆情更多是同步反映而非领先。我一般会算 0 到 3 期的滞后相关挑显著的那一期做后续特征。merged[next_ret] merged[close].pct_change().shift(-1) # 次日收益 for lag in range(0, 4): col merged[sent_mean].shift(lag) corr merged[next_ret].corr(col) print(flag{lag}, corr{corr:.4f})shift(-1)是把次日收益对齐到当天shift(lag)是让舆情往后错 lag 天。相关系数绝对值超过 0.1 在日频里已经算有信号但别只看数字要结合样本量看几百个交易日以内的相关性波动很大。这一步的意义是帮你判断该把舆情当同步特征还是领先特征直接决定后面模型怎么设计。5. 避坑与排查这套系统跑不起来时先看这几条5.1 爬虫返回空列表或条数骤降现象昨天还能抓几百条今天len(data)只有个位数。原因通常是请求头被识别、页面结构改版或者触发了频率限制。解决先打印resp.status_code和resp.text[:500]确认是不是返回了验证页再把User-Agent换成完整浏览器串间隔调到 2 到 3 秒如果结构变了用浏览器开发者工具重新定位选择器别硬套旧代码。5.2 SnowNLP 首次调用报错或极慢现象第一次SnowNLP(text).sentiments卡十几秒甚至报编码错误。原因是它首次运行要加载自带语料且对非 UTF-8 文本敏感。解决确保文本是str类型入库前统一encode(utf-8, ignore).decode(utf-8)批量处理时把模型调用放在循环外初始化一次别每条都重建对象。5.3 合并后舆情列全是 0现象merged里sent_mean整列是 0。原因多半是日期类型不一致一边是datetime64一边是object的date。解决合并前统一pd.to_datetime并检查daily[trade_date].dtype。另一个常见原因是股票代码格式不同一边是600519一边是sh600519merge时对不上加一步字符串清洗。5.4 回测收益好得不真实现象加上舆情特征后策略年化高得离谱。原因通常是未来函数——用了收盘后的帖子或者用了当天收盘价算当天信号。解决严格执行 15:00 切分信号用 T 日舆情收益用 T1 日开盘到收盘别用 T 日收盘价。把这两条卡死收益会回落到合理区间。5.5 可视化中文乱码现象matplotlib图上中文全是方框。原因是默认字体不含中文。解决在绘图前设置plt.rcParams[font.sans-serif] [SimHei]和plt.rcParams[axes.unicode_minus] FalseWindows 下用SimHeimacOS 换成Arial Unicode MS。这一步不设答辩时图没法看。6. 从作业到能讲清楚把舆情因子接进一个最小可视化面板走到这一步系统已经能跑通采集、情感、合并和验证。最后一个技巧是把这些串成一个能演示的最小面板不用上重型框架Streamlit十几行就够。它的价值在于答辩时你能现场改股票代码、改日期图立刻刷新比静态截图有说服力得多。import streamlit as st import matplotlib.pyplot as plt st.title(舆情与行情对照) code st.text_input(股票代码, 600519) # merged 为前面合并好的 DataFrame fig, ax1 plt.subplots(figsize(10, 4)) ax1.plot(merged[date], merged[close], colorblack, label收盘价) ax1.set_ylabel(收盘价) ax2 ax1.twinx() ax2.bar(merged[date], merged[heat_norm], alpha0.3, colororange, label舆情热度) ax2.set_ylabel(热度) plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False st.pyplot(fig) st.dataframe(merged[[date, close, sent_mean, pos_ratio, heat_norm]].tail(20))twinx()让价格和热度共用横轴但各自纵轴这是这类对照图的标准做法。alpha0.3让柱状图不挡住价格线。st.dataframe放最近 20 行方便现场解释某一天舆情为什么跳升。跑之前确认merged已经在会话里否则会报未定义。参数上我踩过的坑是热度归一化如果按全样本做演示时换股票会失真正确做法是按当前展示窗口重新归一化。另外SimHei在部分 Linux 环境没有部署前先fc-list :langzh查一下可用中文字体别到演示现场才发现方框。这套东西做下来我的习惯是先把数据链路跑通再谈模型舆情因子有没有用滞后相关那一步就能给出答案不用等回测。代码不用多能把「采集—情感—对齐—验证—展示」这条线讲清楚分数和实用性都不会差。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑