☰
Python实现A股股市情感分析:从股吧数据到选股因子
2026/10/2 12:25:46 网站建设 项目流程

简介:这份资源面向对量化投资与自然语言处理感兴趣的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.txt

data/raw放的是原始文本,data/processed是脚本跑出来的中间产物,dict里的情感词典决定了打分走向。config.py是最该先打开的文件,股票代码、日期范围、情感阈值一般都在这里改,不用去动核心逻辑。

2.2 依赖安装与 Python 环境

依赖通常集中在requirements.txt,核心就几个:pandas、jieba、numpy、matplotlib,有的版本会带snownlp或wordcloud。安装前先确认 Python 版本,这类项目一般在 3.8~3.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保留前导零,如 000001
stock_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(..., errors='coerce')处理,解析失败的置为 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(r"http[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", encoding="utf-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", index=False, encoding="utf-8-sig")

逻辑说明:load_userdict要在第一次lcut之前调用,否则不生效,这是最常见的翻车点。停用词过滤掉"的""了""是"这类高频无意义词,单字过滤是因为中文单字歧义太大。encoding="utf-8-sig"是为了 Excel 打开不乱码,如果你只用 pandas 读,用utf-8也行。金融词典建议自己补:把近期热门概念词(如"算力""固态电池")加进去,情感匹配命中率会明显提升。

3.3 情感打分:词典法 vs 模型法怎么选

这份资源默认走的是词典法,理由很实际:可解释、无需标注、跑得快。词典法的核心是给每个词打极性分,再按句子聚合。

def load_sentiment_dict(pos_path, neg_path): pos = set(open(pos_path, encoding="utf-8").read().split()) neg = set(open(neg_path, encoding="utf-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"], errors="coerce") 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_periods=1).mean()) # 横截面标准化:同一天不同股票之间可比 daily["sentiment_z"] = daily.groupby("trade_date")["sentiment_ma5"] \ .transform(lambda x: (x - x.mean()) / x.std())

逻辑说明:rolling(5)做 5 日平滑,min_periods=1保证开头几天也有值。横截面标准化(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, encoding='gbk')试一次,读不出来再换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, labels=False, duplicates="drop")) layer_ret = daily.groupby("group")["ret"].mean() print(layer_ret)

逻辑说明:qcut按分位数切 5 组,duplicates="drop"处理情绪分大量重复导致的分箱失败。layer_ret就是各组的平均收益,单调性越强,因子越可用。参数上,分组数 5 是惯例,样本少可以改 3。

再做相关性检验。计算情绪因子和未来收益的 Rank IC(秩相关系数),按日算再取均值。IC 均值在 0.02~0.05 之间算有一定预测力,超过 0.1 要警惕是不是有未来函数。这一步能快速判断因子是真有用还是数据泄漏。

验证指标含义参考区间
Rank IC 均值因子与收益的秩相关0.02~0.05 可用
ICIRIC 均值 / IC 标准差大于 0.3 较稳
分层单调性各组收益是否递增越单调越好
换手率因子排名变动频率过高则交易成本大

最后提醒一句:情绪因子单独用效果有限,常见做法是和技术面、基本面因子组合,做多因子打分。词典法的精度天花板不高,如果要做实盘,建议后续换成金融领域预训练模型,但那份代码包里的清洗、聚合、对齐框架可以直接复用,换的只是打分模块。从那以后我每次拿到新的文本因子,都强制先跑一遍分层和 IC,确认没有未来函数再往下做。希望这份拆解帮到你,代码包和数据集可以直接拿去跑,改config.py里的股票池就能换成你自己关注的标的。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询