尧图精选

Python实现A股股市情感分析:从股吧数据到选股因子

🕒 发布时间:2026/10/2 12:25:47 📁 来源:尧图网络
简介这份资源面向对量化投资与自然语言处理感兴趣的Python学习者提供一套完整的A股股市情感分析实战方案。项目思路是从互联网股评中提取投资者情绪借助标注语料训练情感分类模型再将情感结果构建为指标研究其与股市走势之间的关系适合作为金融文本挖掘的入门练手项目。压缩包共12个文件约24.71MB包含4个Python脚本、2个txt情感词典、2个csv行情与评分数据、2张结果图表及说明文档脚本覆盖模型训练、情感指标计算与可视化绘图三个环节数据与代码齐备可直接运行复现。目前已有1436人学习下载。读者可借此掌握从文本标注、机器学习建模到指标构建与行情对比的完整流程理解非有效市场中情绪因子的分析价值并在此基础上替换数据或调整模型拓展自己的研究思路。1. 从一份能直接跑的 A 股情感分析代码包说起很多人第一次接触股市情感分析都是被用舆情预测涨跌这个说法吸引进来的。但真到动手阶段卡点往往不在模型而在数据东方财富股吧的帖子怎么批量拿、拿了之后怎么清洗、中文金融文本用什么分词、情感标签从哪来。这份 Python 实现 A 股股市情感分析的项目把数据集和可运行代码一起打包了省掉了最耗时的数据准备环节。它适合两类人一类是想快速跑通一条文本→情感分数→可视化链路的 Python 学习者另一类是想把舆情因子接进自己选股或回测流程的量化从业者。下面我按实际拆包、跑通、改参数的顺序把这份资源讲透。2. 拆开代码包目录结构、依赖与数据格式拿到一个源码包我习惯先不跑先看目录和依赖这一步能省掉后面一半的报错。2.1 典型目录结构与各文件职责这类 A 股情感分析项目结构通常长这样不同版本文件名可能略有差异以实际包内为准stock_sentiment/ ├── data/ │ ├── raw/ # 原始股吧帖子、新闻标题 │ │ └── stock_comments.csv │ ├── processed/ # 清洗、分词后的中间数据 │ │ └── comments_clean.csv │ └── dict/ # 情感词典、停用词 │ ├── positive.txt │ ├── negative.txt │ └── stopwords.txt ├── src/ │ ├── preprocess.py # 清洗 分词 │ ├── sentiment.py # 情感打分核心逻辑 │ ├── analyze.py # 按股票/日期聚合 │ └── visualize.py # 出图 ├── config.py # 路径、阈值、股票代码配置 ├── main.py # 一键入口 └── requirements.txtdata/raw放的是原始文本data/processed是脚本跑出来的中间产物dict里的情感词典决定了打分走向。config.py是最该先打开的文件股票代码、日期范围、情感阈值一般都在这里改不用去动核心逻辑。2.2 依赖安装与 Python 环境依赖通常集中在requirements.txt核心就几个pandas、jieba、numpy、matplotlib有的版本会带snownlp或wordcloud。安装前先确认 Python 版本这类项目一般在 3.83.11 之间最稳。# 建议先建虚拟环境避免污染全局 python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate # 安装依赖 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple逻辑说明虚拟环境是为了隔离依赖金融文本处理经常要装jieba、snownlp这类包版本冲突很常见。-i指定国内镜像源能明显加快下载。参数上如果你的机器同时装了多个 Python务必用python -m venv而不是直接venv避免绑到错误的解释器。2.3 数据集字段说明打开data/raw/stock_comments.csv字段一般包含以下几列这是后面所有处理的输入契约字段名含义类型注意点stock_code股票代码str保留前导零如 000001stock_name股票名称str可能含 ST、*ST 前缀publish_time发帖时间datetime格式不统一需统一解析title帖子标题str短文本信息密度高content帖子正文str可能为空需过滤read_count阅读数int可作权重非必需stock_code一定要按字符串读用pd.read_csv(..., dtype{stock_code: str})否则000001会被读成1后面按代码聚合全乱。publish_time常见坑是混着2024-01-01和2024/01/01两种格式统一用pd.to_datetime(..., errorscoerce)处理解析失败的置为 NaT 再丢弃。3. 文本清洗与中文分词把股吧黑话变成可算的输入情感分析的上限由数据质量决定股吧文本尤其脏这一步做不干净后面模型再花哨也是玄学。3.1 清洗规则与正则实现股吧帖子常见噪声HTML 标签、某人、表情符号、连续空格、广告链接。清洗逻辑要按顺序来先删结构再删字符。import re import pandas as pd def clean_text(text: str) - str: if not isinstance(text, str): return # 1. 去掉 HTML 标签 text re.sub(r[^], , text) # 2. 去掉 URL text re.sub(rhttp[s]?://\S, , text) # 3. 去掉 用户 和 #话题# text re.sub(r[\w\u4e00-\u9fa5], , text) text re.sub(r#.*?#, , text) # 4. 只保留中文、数字、字母和基本标点 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。、], , text) # 5. 压缩连续空白 text re.sub(r\s, , text).strip() return text df pd.read_csv(data/raw/stock_comments.csv, dtype{stock_code: str}) df[clean_content] df[content].fillna().apply(clean_text) df df[df[clean_content].str.len() 5] # 过滤过短文本逻辑说明正则顺序不能乱先删 HTML 再删标签内容否则标签里的字符会被误留。第 4 步的正则保留了中文、英文、数字和常用中文标点把表情、特殊符号全清掉。参数上len() 5这个阈值是经验值太短的帖子如顶沙发没有情感信息留着只会增加噪声。如果你的数据里英文代码多可以把字母范围放宽。3.2 jieba 分词与自定义金融词典通用分词器不认识涨停板割肉北向资金这类词必须加自定义词典否则会被切碎情感词匹配直接失效。import jieba # 加载自定义金融词典每行一个词 jieba.load_userdict(data/dict/finance_dict.txt) # 加载停用词 with open(data/dict/stopwords.txt, encodingutf-8) as f: stopwords set(line.strip() for line in f) def tokenize(text: str) - list: words jieba.lcut(text) # 过滤停用词、单字和纯数字 return [w for w in words if w not in stopwords and len(w) 1 and not w.isdigit()] df[tokens] df[clean_content].apply(tokenize) df.to_csv(data/processed/comments_clean.csv, indexFalse, encodingutf-8-sig)逻辑说明load_userdict要在第一次lcut之前调用否则不生效这是最常见的翻车点。停用词过滤掉的了是这类高频无意义词单字过滤是因为中文单字歧义太大。encodingutf-8-sig是为了 Excel 打开不乱码如果你只用 pandas 读用utf-8也行。金融词典建议自己补把近期热门概念词如算力固态电池加进去情感匹配命中率会明显提升。3.3 情感打分词典法 vs 模型法怎么选这份资源默认走的是词典法理由很实际可解释、无需标注、跑得快。词典法的核心是给每个词打极性分再按句子聚合。def load_sentiment_dict(pos_path, neg_path): pos set(open(pos_path, encodingutf-8).read().split()) neg set(open(neg_path, encodingutf-8).read().split()) return pos, neg pos_words, neg_words load_sentiment_dict( data/dict/positive.txt, data/dict/negative.txt) def score_tokens(tokens: list) - float: score 0 for w in tokens: if w in pos_words: score 1 elif w in neg_words: score - 1 # 归一化到 [-1, 1]避免长文本分数虚高 return score / len(tokens) if tokens else 0.0 df[sentiment] df[tokens].apply(score_tokens)逻辑说明这里用的是最朴素的极性累加1/-1计数后除以词数做归一化。归一化很关键否则一条 200 字的帖子天然比 20 字的分数绝对值大跨帖子比较就失真了。参数上如果你想要更细的粒度可以把1/-1换成情感强度值如暴涨2上涨1词典文件改成词,分值格式。模型法如 SnowNLP、FinBERT精度更高但需要标注数据或大模型推理开销词典法在快速验证阶段性价比最高。4. 按股票和日期聚合把帖子分数变成可用因子单条帖子的情感分没有意义真正有用的是某只股票在某一天的整体情绪这一步是把文本转成量化可用的时间序列。4.1 时间对齐与日频聚合股吧帖子时间戳精确到秒但选股和回测通常按天需要先归到交易日。df[publish_time] pd.to_datetime(df[publish_time], errorscoerce) df df.dropna(subset[publish_time]) # 按股票 日期聚合 daily df.groupby( [stock_code, df[publish_time].dt.date] ).agg( sentiment_mean(sentiment, mean), sentiment_std(sentiment, std), post_count(sentiment, count) ).reset_index().rename(columns{publish_time: trade_date}) # 帖子太少的日子情绪分不可信直接过滤 daily daily[daily[post_count] 5]逻辑说明groupby同时按股票和日期分组聚合出均值、标准差和帖子数三个指标。均值代表整体情绪标准差代表分歧度分歧大往往对应波动大帖子数是置信度权重。参数上post_count 5是过滤阈值低于这个数的日子样本太少情绪分容易被单条极端帖子带偏。如果你的股票池很大可以把这个阈值降到 3但要接受更多噪声。4.2 情绪因子的平滑与标准化日频情绪波动大直接拿去和收益率做相关噪声会淹没信号常见做法是做移动平均。daily daily.sort_values([stock_code, trade_date]) # 按股票分组做 5 日移动平均 daily[sentiment_ma5] daily.groupby(stock_code)[sentiment_mean] \ .transform(lambda x: x.rolling(5, min_periods1).mean()) # 横截面标准化同一天不同股票之间可比 daily[sentiment_z] daily.groupby(trade_date)[sentiment_ma5] \ .transform(lambda x: (x - x.mean()) / x.std())逻辑说明rolling(5)做 5 日平滑min_periods1保证开头几天也有值。横截面标准化z-score是量化里的标准操作把同一天所有股票的情绪分拉到同一尺度才能做排序选股。参数上平滑窗口 5 是常用值短线可以改 3中长线可以改 10。注意transform和apply的区别transform返回与原表等长的序列能直接赋值成新列apply不行。4.3 与行情数据对齐的注意点情绪因子最终要和收益率对齐这里有两个硬约束交易日历和停牌。对齐问题现象处理方式非交易日发帖周末帖子归到周一用交易日历 reindex向前填充停牌期间有情绪无行情以行情表为主表 left join时间戳时区跨时区数据错位统一转成东八区再取日期未来函数用了当天收盘后的帖子帖子时间晚于收盘的归到下一交易日最后一条最容易被忽略如果一条帖子是晚上 8 点发的它影响的是第二天的交易不能算进当天。常见做法是设一个收盘时间阈值如 15:00晚于阈值的帖子日期 1。这个细节不做回测结果会虚高属于典型的血泪经验。5. 避坑与排查跑不通时先看这几条这一章是我实际跑这类项目时踩过的坑按现象→原因→解决整理遇到报错先对照。5.1 中文乱码与编码报错现象读 CSV 报UnicodeDecodeError或中文显示成乱码。原因原始文件可能是 GBK 编码而 pandas 默认 UTF-8。解决先探测编码pd.read_csv(path, encodinggbk)试一次读不出来再换gb18030。写出时统一用utf-8-sig兼顾 Excel。5.2 jieba 自定义词典不生效现象加了金融词典涨停板还是被切成涨停和板。原因load_userdict调用晚于第一次分词或词典文件路径不对、编码不是 UTF-8。解决把load_userdict放在所有lcut之前词典文件存成 UTF-8每行一个词不要带 BOM。5.3 情感分全为 0 或分布异常现象跑完发现sentiment列全是 0或几乎全正。原因情感词典没加载成功路径错、文件空或分词后没有一个词命中词典。解决先打印词典长度len(pos_words)再抽样看tokens里有没有情感词。如果命中率极低说明分词粒度或词典覆盖不够补词典。5.4 股票代码前导零丢失现象000001变成1聚合结果对不上。原因read_csv默认把数字列推断成 int。解决读取时显式指定dtype{stock_code: str}或在代码前加df[stock_code] df[stock_code].astype(str).str.zfill(6)。5.5 回测收益虚高现象情绪因子和收益率相关性高得离谱。原因用了未来信息比如把当天收盘后的帖子算进了当天或用了未来几天的行情做对齐。解决严格按帖子发布时间切分晚于收盘的归下一交易日对齐行情时以行情表为主表避免引入未来数据。这条不排查整个策略结论都是假的。6. 进阶把情绪因子接进选股与验证跑通基础链路后真正决定这份资源价值的是能不能把情绪分变成一个可验证的因子。我一般会做两件事分层回测和因子相关性检验。先做分层。把每天所有股票按sentiment_z从高到低分成 5 组看每组未来 1 日、5 日的平均收益。如果情绪因子有效通常能看到单调性——高情绪组收益高于低情绪组。代码上就是按日期分组后qcut打标签再和收益率 merge。# 假设 ret 是未来一日收益率已按 stock_code trade_date 对齐 daily[group] daily.groupby(trade_date)[sentiment_z] \ .transform(lambda x: pd.qcut(x, 5, labelsFalse, duplicatesdrop)) layer_ret daily.groupby(group)[ret].mean() print(layer_ret)逻辑说明qcut按分位数切 5 组duplicatesdrop处理情绪分大量重复导致的分箱失败。layer_ret就是各组的平均收益单调性越强因子越可用。参数上分组数 5 是惯例样本少可以改 3。再做相关性检验。计算情绪因子和未来收益的 Rank IC秩相关系数按日算再取均值。IC 均值在 0.020.05 之间算有一定预测力超过 0.1 要警惕是不是有未来函数。这一步能快速判断因子是真有用还是数据泄漏。验证指标含义参考区间Rank IC 均值因子与收益的秩相关0.020.05 可用ICIRIC 均值 / IC 标准差大于 0.3 较稳分层单调性各组收益是否递增越单调越好换手率因子排名变动频率过高则交易成本大最后提醒一句情绪因子单独用效果有限常见做法是和技术面、基本面因子组合做多因子打分。词典法的精度天花板不高如果要做实盘建议后续换成金融领域预训练模型但那份代码包里的清洗、聚合、对齐框架可以直接复用换的只是打分模块。从那以后我每次拿到新的文本因子都强制先跑一遍分层和 IC确认没有未来函数再往下做。希望这份拆解帮到你代码包和数据集可以直接拿去跑改config.py里的股票池就能换成你自己关注的标的。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →