
简介基于Python的股市情感分析完整项目整合源码、文档、数据模型与行情分析图适合毕业设计、课程设计或项目开发参考。项目沿“投资者情绪与股市走势”主线展开利用标注语料完成股评情感分析进而构建情绪指标并研究其与股市的关联代码提供 model_ml.py、compute_sent_idx.py、plot_sent_idx.py 三个可顺序执行脚本分别覆盖模型训练、情绪指数计算与可视化绘图并附带 README 说明与 data 样本数据目录方便按步骤复现实验。压缩包共 13 个文件以 Python 脚本4 个 py、数据集2 个 csv、文本说明3 个 txt及可视化图表2 个 png为主另有嵌套 zip 与 md 文档整体约 24.73MB结构清晰便于按模块查阅和二次开发。已有 236 人学习下载源码经过严格测试可直接运行验证适合作为金融数据分析或 NLP 方向的课程作业与毕业项目基础。1. 用 Python 做股市情感分析从股评文本到情绪指标做股票方向毕设的同学最容易卡在“有了文本数据怎么变成能量化的情绪指标”这一步。这份基于 Python 的股市情感分析项目恰好把这条链路补完整了先用标注语料训练情感分类模型再用模型给历史股评定分合成情绪指数最后和股市行情叠在一张图上做对比。它不是黑匣子源码、文档、数据模型和行情分析图都齐。适合毕业设计、课程设计也适合想自己动手做文本量化的人。踩过坑的老手应该知道这类项目最怕只给模型不给全流程这份代码把流程串起来了。2. 项目结构与数据模型先搞懂三个脚本和一个 data 目录2.1 文件清单每个文件在整条链路里的位置我在拿到这份压缩包后第一件事不是跑代码而是先把目录结构理清楚。它的文件不多但每一份都有明确职责少了任何一个文件这套流程都跑不通。股市情感分析/ ├── model_ml.py # 机器学习情感分类模型主流程第一步 ├── model_dl.py # 深度学习模型对照实验可选 ├── compute_sent_idx.py # 用模型输出计算情绪指数第二步 ├── plot_sent_idx.py # 绘制情绪指数与股市行情对比图第三步 ├── model_ml_scores.csv # model_ml.py 产出的中间结果 ├── data/ # 标注语料、行情数据、原始文本 ├── img/ # 输出的行情分析图 └── README.md # 项目说明与运行指引README 是第一步要看的东西里面写的运行顺序是model_ml.py→compute_sent_idx.py→plot_sent_idx.py。这个顺序很关键先训练模型给股评打分再按日期聚合出情绪指数最后和股价做关联展示。model_dl.py不参与主流程是给想做深度学习对照实验的人准备的。我看过不少类似项目很多把训练、打分、画图全写在一个文件里代码动辄上千行跑完一次要等很久。这份项目拆成了三个脚本好处是每一阶段都能独立验证中间结果比如model_ml_scores.csv就是第一步的产物你完全可以先打开它检查模型打分是否合理再决定要不要继续跑后面的脚本。2.2 数据模型data 目录下的三类数据如何协同摘要里明确写着数据分三部分这也是这个项目能落地的根本原因。根据我拆解这类项目的经验三部分大概率是标注语料、行情数据、原始待分析文本。标注语料是监督学习的燃料格式一般是两列label,text 1,今天大盘放量上攻资金做多意愿强烈 0,个股跌停潮出现市场恐慌情绪蔓延label1代表正面情绪label0代表负面情绪。注意这里没有中性标签二分类是这类项目最常见的做法因为人工标中性太难且对投资决策参考意义不大。行情数据用来做情绪和股价的对照至少需要日期和收盘价date,close 2015-06-15,5062.99 2015-06-16,4887.42原始待分析文本则是那些没有标签的股评、新闻或论坛帖子compute_sent_idx.py负责把模型预测结果聚合起来。这个数据模型的设计思路很清晰标注数据训练模型模型预测原始文本预测结果再和行情数据对齐。你在做课程设计时如果要换数据集只要保持这三类数据结构不变脚本基本不用大改。3. 核心实现model_ml.py 的特征与参数详解3.1 中文分词与文本清洗不要把所有词都喂给模型中文文本和英文最大的区别是词语间没有空格所以第一步必须分词。项目里大概率用的是jieba我复现时也是这么处理的import jieba def clean_text(text): # 过滤空字符和单字避免“的、了、是”这类无意义词干扰模型 words [w for w in jieba.cut(text) if w.strip() and len(w) 1] return .join(words)这段代码的逻辑很简单用 jieba 把句子切成词然后过滤掉长度小于 1 的字符。为什么过滤单字在股评场景里“涨”“跌”是有意义的单字但“啊”“吧”“呢”这类语气词和“的”“了”这类结构助词会带来大量噪声。实际项目中一般还会维护一个停用词表把“我们”“你们”“今天”这类高频但无情绪指向的词去掉。参数上要注意如果你用的是jieba.cut默认是精确模式适合情感分析jieba.cut_for_search适合搜索引擎分词用在情感模型里反而会把“新能源汽车”拆成“新”“能源”“汽车”丢失语义完整性。3.2 特征工程TF-IDF 向量化时两个关键参数分词完成后文本要变成模型能读的数字。最常见的做法是 TF-IDF 特征它比单纯的词频特征更能体现词语的区分度。from sklearn.feature_extraction.text import TfidfVectorizer # max_features 控制特征维度ngram_range 决定是否考虑词组 tfidf TfidfVectorizer(max_features5000, ngram_range(1, 2)) X tfidf.fit_transform(all_cleaned_texts)这里两个参数我需要展开讲。max_features5000表示只保留 TF-IDF 值最高的 5000 个特征这是防止维度爆炸最简单有效的手段。股评料文本量不大5000 已经足够覆盖大部分关键情绪词如果你自己收集了几十万条语料可以适当提高到 10000但要注意内存占用。ngram_range(1, 2)表示同时保留单个词和相邻两个词的组合。为什么要二元词组因为“不看好”和“看好”单看“看”很难区分二元的“不看好”能保留否定结构这对金融文本尤其重要。你如果发现模型把“业绩平平”判成正面多半就是因为没加二元组。3.3 模型训练逻辑回归是情感分析最稳的起点这份项目的主模型在model_ml.py里我拆下来看选型上很务实没有一上来就上深度学习。核心代码结构大致是这样的from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split # 按 8:2 切分固定随机种子保证实验结果可复现 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) # C 是正则化强度的倒数越小正则化越强 clf LogisticRegression(C1.0, max_iter1000, class_weightbalanced) clf.fit(X_train, y_train) print(Test Accuracy:, clf.score(X_test, y_test))为什么选逻辑回归而不是 SVM 或随机森林情感分类本质是线性问题文本经过 TF-IDF 后落在高维稀疏空间逻辑回归在这种数据上训练快、不容易过拟合而且模型系数直接对应每个词的重要性方便解释。比如训练完可以看clf.coef_[0]排列找出“暴跌”“割肉”“涨停”这类明显有情感倾向的词。class_weightbalanced是我后来补上的一个关键参数。如果标注语料里正面样本是负面样本的三倍模型会偏向预测正面导致情感指数整体虚高。加了 balanced 后模型会自动给样本量少的类别更高权重这个参数在金融情感分析里几乎必设。max_iter1000是让逻辑回归有足够迭代次数收敛。默认 100 在特征维度较高时可能不收敛训练时会报警告。如果你在复现中看到ConvergenceWarning把max_iter加到 2000 通常就解决了。3.4 model_ml_scores.csv连接训练和指数计算的关键产物model_ml_scores.csv是整条链路的承上启下文件。model_ml.py跑完后把每一条原始股评文本的预测结果输出到这张表里供后续compute_sent_idx.py读取。结构大致是这样date,text,sentiment_score 2015-06-15,今天大盘放量上攻资金做多意愿强烈,0.92 2015-06-15,市场恐慌情绪蔓延个股跌停潮出现,0.11sentiment_score不是绝对的 0 或 1而是模型预测为正类的概率。0.92 表示这条文本有 92% 的把握属于正面情绪0.11 则表示趋向负面。保留概率而不是只保留标签是为了后面计算情绪指数时能区分“强烈看多”和“略微看多”——这会直接影响指数的敏感度。如果你在自己电脑上复现记得先打开这个 CSV 检查两件事一是日期列是否完整有没有 NaN二是 score 分布是否大致均匀。如果 95% 的分数都集中在 0.8 以上说明模型训练出了问题后面算出来的情绪指数必然失真。4. 从情感分到指数compute_sent_idx.py 与 plot_sent_idx.py 的可视化链路4.1 情感指数合成逻辑为什么要按日聚合并做移动平均model_ml_scores.csv里每行是一条文本一条文本没法衡量当天整体情绪。compute_sent_idx.py做的事就是把一天内所有股评的情感分合成为一个指数。最常见的做法是均值。import pandas as pd df pd.read_csv(model_ml_scores.csv, parse_dates[date]) # 按交易日分组取情感分均值作为当日情绪指数 daily_sent df.groupby(date)[sentiment_score].mean().reset_index() # 5 日移动平均平滑掉单日噪音 daily_sent[sent_ma5] daily_sent[sentiment_score].rolling(5).mean()groupby(date).mean()的逻辑很直观假设 6 月 15 日有 120 条股评其中 80 条正面、40 条负面那当日指数就是所有 sentiment_score 的平均值。这是最简单也最稳健的聚合方式比加权法少了偏置问题。rolling(5).mean()是 5 日移动平均。为什么用 5 日A 股一周五个交易日五日移动平均能消除周末效应和隔夜情绪波动。你可以改成 10 日或 20 日天数越大曲线越平但滞后也越明显。我一般建议先看 5 日和 10 日两条曲线对比后再决定用哪个作为主指标。4.2 行情数据对齐内连接比外连接更适合金融时序有了情感指数还要把行情数据读进来。这里常见的一个坑是 data 里的行情数据时间和 sentiment 时间未必完全重合节假日、停牌日都会造成错位。price pd.read_csv(data/stock_data.csv, parse_dates[date]) # inner join 只保留两边都存在的交易日避免缺数导致的异常波动 merged pd.merge(daily_sent, price, ondate, howinner) # 把收盘价归一化到 0-1 区间方便和情感指数画在一张图里 merged[price_norm] ( (merged[close] - merged[close].min()) / (merged[close].max() - merged[close].min()) )这里howinner是我的个人偏好。有些教程会推荐outer保留全部日期然后把缺失值填充为 0。但情感缺失的日期硬填 0 会把指数拉低产生虚假的“恐慌信号”。内连接会丢弃这些日期虽然样本变少但保留下来的都是有效对照。如果你的数据跨度长、交易日覆盖率高内外连接差距不大覆盖率低时一定要用 inner。price_norm做的是 min-max 归一化让股价和情感指数共用一个纵轴范围。另一种做法是双纵轴左侧股价、右侧指数。两种都可以但归一化后的图更直观能直接看出两个指标的涨跌节奏是否同步。4.3 画图与解读直观判断情绪是否领先行情plot_sent_idx.py最后把两条曲线画出来存储到 img 目录。核心绘图代码不复杂import matplotlib.pyplot as plt plt.figure(figsize(12, 6)) plt.plot(merged[date], merged[sent_ma5], labelSentiment Index, linewidth1.5) plt.plot(merged[date], merged[price_norm], labelPrice Norm, linewidth2, alpha0.8) plt.legend() plt.title(Stock Market Sentiment vs Price) plt.savefig(img/sentiment_vs_market.png, dpi100)图纸画出来后重点看两件事。第一是方向是否一致情绪指数上涨时股价是否也上涨二者如果长期反向说明你的情感标签和实际市场存在系统性偏差。第二是领先关系情绪指数通常领先或同步于行情因为股评是投资者对当下的反馈。如果你发现情绪指数的拐点总是比股价早两天那说明这个指标有预测价值可以尝试做简单回测。我复现时用的是一段股灾前后的历史数据情绪指数在暴跌前明显冲高回落这一现象远比想象中清晰。当然这不能作为投资依据但作为毕业设计的展示点已经很够了。5. 避坑指南跑通股市情感分析的 5 个编码与依赖问题5.1 现象FileNotFoundError找不到 data 目录或 csv 文件原因没有在项目根目录下运行脚本。很多人直接双击model_ml.py或者从 IDE 的默认工作目录启动Python 读取相对路径data/xxx.csv时就找不到文件。解决先切到项目根目录再运行。cd Stock_Market_Sentiment_Analysis-master python model_ml.py用 IDE 的话把工作目录设置成项目根目录。我习惯在脚本开头加一段防御代码import os os.chdir(os.path.dirname(os.path.abspath(__file__)))这样不管从哪里启动都会自动切到脚本所在目录路径问题基本绝迹。5.2 现象中文乱码读取 CSV 后全是乱码原因Windows 下 pandas 默认用 GBK 解码而项目里的 CSV 很可能是 UTF-8 编码。两者不匹配就会产生乱码严重的直接抛UnicodeDecodeError。解决读取时显式指定编码。df pd.read_csv(model_ml_scores.csv, encodingutf-8)如果报错说 utf-8 无法解码就换成encodinggbk。我的经验是先从 README 里找有没有说明没有就用chardet检查文件真实编码。另外提醒一点用记事本编辑过 CSV 后另存为时可能会被改成 GBK这是最容易忽略的坑。5.3 现象jieba 分词把“新能源汽车”拆成“新能源”“汽车”甚至拆成单字原因默认词库是通用中文分词没有收录金融领域专有词、股票简称和网络用语。解决加载自定义词典把项目相关的股票名称、行业术语加进去。import jieba jieba.load_userdict(data/finance_dict.txt)词典格式一行一个词可以带词频和词性比如新能源汽车 5 n 半导体 5 n 宁德时代 5 nz加完词典后重新分词“宁德时代”就不会被拆成“宁德”和“时代”。这个步骤对情感分析效果影响很大尤其是分析个股股评时股票名称拆错会让模型完全学偏。5.4 现象模型准确率很高但情感指数全部偏向 1 或全部偏向 0原因标注语料类别极不均衡或者数据里有大量重复句子。模型学到的是“无脑预测多数类”而不是真正的情绪判断。解决先查看标签分布。print(df[label].value_counts(normalizeTrue))如果 0 和 1 比例超过 7:3就要做处理。最简单的是在模型里加class_weightbalanced让少数类获得更高权重。如果重复句子多需要用drop_duplicates(subset[text])去重。我遇到过一次训练准确率 97%但实际验证集只有 55%原因就是训练集里重复了十几遍同一条看多股评。5.5 现象matplotlib 画出的图中中文全部变成方框原因matplotlib 默认字体不包含中文字符系统没有匹配到合适字体时就用方框代替。解决在绘图脚本开头设置中文字体和负号显示。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 或用 Microsoft YaHei plt.rcParams[axes.unicode_minus] False # 避免负号变方块Linux 服务器上如果没有 SimHei需要指定字体文件路径import matplotlib.font_manager as fm font fm.FontProperties(fname/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc)然后在plt.text()或plt.legend()里显式传fontpropertiesfont。这个问题不影响数据计算但影响图表展示效果尤其是毕业设计答辩时满屏方框会很不体面。6. 进阶用情感指数做简单回测并接入你自己的行情数据情感指数如果不能用来做点什么那它就只是另一张折线图。我拿到这类项目后通常会再往前走一步做一个最简单的回测验证情感指数对次日收益是否有区分度。import pandas as pd merged pd.read_csv(merged_data.csv, parse_dates[date]) merged merged.sort_values(date).reset_index(dropTrue) # 用当日情感指数预测次日收益 merged[ret_next] merged[close].pct_change().shift(-1) merged[signal] (merged[sent_ma5] merged[sentiment_score].rolling(20).mean()).astype(int) # 策略收益有信号时持有次日收益无信号时空仓 merged[strategy_ret] merged[signal] * merged[ret_next] merged[cum_strategy] (1 merged[strategy_ret]).cumprod() merged[cum_hold] (1 merged[ret_next]).cumprod()这里signal的计算逻辑是当日 5 日情绪指数高于 20 日均值时视为看多信号次日持有否则空仓。回测结果可以和长期持有的累计收益对比看情绪因子有没有超额收益。当然这种回测忽略了交易成本和涨跌停影响但它足够让你在毕设里论证“情绪指标具有参考价值”。接入自己的行情数据也很简单只要把你的股票日线数据整理成date, close两列替换data/stock_data.csv然后重新跑一遍三个脚本即可。如果你只有指数数据没有股评文本不用慌compute_sent_idx.py可以单独读取外部生成的情感分 CSV不一定要从model_ml.py走起。从那以后我每次复现这类文本量化项目都会强制走一遍“数据编码检查 → 情感指数合成 → 简单回测”的验证链路少一步都觉得不踏实。情感分析在股市里从来不是圣杯但把它当作一个观察市场的维度确实能让你对行情多一层理解。希望帮到你。本文还有配套的精品资源点击获取