☰
Python实现A股股吧情感分析:从数据到量化因子
2026/10/3 5:00:05 网站建设 项目流程

简介:这份资源面向对量化投资与自然语言处理感兴趣的Python学习者,提供一套可完整运行的A股股市情感分析项目。其核心思路是从互联网股评中提取投资者情绪,借助标注语料训练情感分类模型,再将情感结果构建为指标,研究其与股市走势之间的关系,为投资决策提供参考,尤其适合在非有效市场背景下理解情绪因子的作用。压缩包共12个文件,约24.71MB,包含4个Python脚本、2个txt情感词典、2个csv行情与评分数据、2个png可视化图表,以及说明文档和嵌套压缩包,覆盖从模型训练、情感指标计算到结果绘图的完整链路。已有1436人学习下载。读者可依次运行model_ml.py、compute_sent_idx.py与plot_sent_idx.py,直接复现情感指标与大盘对比图,并参考README快速理解目录结构与数据组织,适合作为课程设计、量化入门或情绪因子研究的实操模板。

1. 从一份 A 股股吧评论数据集说起:情绪到底能不能被量化

很多人第一次听到「Python 实现 A 股股市情感分析」,脑子里冒出来的画面是:爬一堆股吧评论,丢给模型,输出一个「看涨/看跌」的分数,然后拿这个分数去炒股。这个画面一半对一半错。对的部分是,情感分析确实能把非结构化的文本变成可计算的数值信号;错的部分是,单靠一个情绪分数就想稳定盈利,基本是玄学。真正有价值的场景,是把情绪当成一个辅助因子,和量价、资金流、行业轮动放在一起看,用来解释某些「消息面驱动」的异动,或者给妖股、题材股的短期热度做一个量化刻画。

这篇笔记要讲清楚三件事:第一,A 股股吧文本的情感分析,数据长什么样、标签怎么来;第二,用 Python 从零搭一条可复现的流水线,包括数据清洗、分词、模型训练和推理;第三,落地时会踩哪些坑,尤其是股吧反爬、黑话、反讽这些让模型集体翻车的地方。适合有 Python 基础、想把这套东西跑起来做量化辅助或者舆情监控的从业者。数据集我会给出可自行构造的格式和字段说明,代码可以直接运行,不需要你去猜某个不存在的仓库地址。

2. 数据从哪来、长什么样:股吧文本的字段设计与标签策略

2.1 股吧评论的典型字段和采集边界

A 股股吧的文本和微博、电商评论完全不是一个物种。它有几个鲜明特征:短、口语化、黑话密集、情绪极端、大量反讽。一条典型评论可能是「主力今天又在洗盘,拿住别慌,明天一字板」,也可能是「呵呵,又骗炮,割肉走了」。你要做的第一件事不是建模,而是把字段设计清楚,否则后面清洗和标注全是坑。

我一般会把单条样本设计成下面这些字段,存成 JSONL 或者 CSV 都行:

字段名类型说明
stock_codestring股票代码,如 600519
stock_namestring股票名称,便于人工核对
publish_timestring发布时间,精确到分钟
contentstring评论正文,保留原始文本
like_countint点赞数,可作为情绪强度的辅助权重
read_countint阅读数,部分平台有
labelint情感标签,0 中性 / 1 看涨 / 2 看跌

字段设计里最容易忽略的是like_count。同样一句「明天涨停」,点赞 2 和点赞 200 的信息量完全不同。后面做加权聚合时,这个字段能显著提升信号质量。采集边界上要注意,只采公开可见的评论内容,控制请求频率,不要对目标站点造成压力,这是做数据的基本职业操守。

2.2 标签怎么来:弱监督 + 人工校验的混合策略

情感分析最贵的不是模型,是标签。全人工标注几千条成本很高,全用规则又太脏。我的做法是弱监督打底 + 人工抽检修正。先用关键词和规则生成一批初始标签,再人工抽 10% 到 20% 校验,把明显错的挑出来重标,形成一个小而干净的核心集,剩下的用模型自训练扩展。

规则打标的核心逻辑是维护三份词典:看涨词、看跌词、否定词。看涨词比如「涨停、加仓、抄底、起飞、利好」,看跌词比如「跌停、割肉、跑路、垃圾、退市」,否定词比如「不、没、别、莫」。一条文本里看涨词得分减去看跌词得分,再处理否定词翻转,最后按阈值分档。

# 弱监督打标:基于词典的初筛,用于生成初始标签 POS_WORDS = ["涨停", "加仓", "抄底", "起飞", "利好", "突破", "拉升", "牛"] NEG_WORDS = ["跌停", "割肉", "跑路", "垃圾", "退市", "崩盘", "套牢", "熊"] NEGATORS = ["不", "没", "别", "莫", "勿"] def weak_label(text: str) -> int: """返回 0 中性 / 1 看涨 / 2 看跌""" score = 0 for w in POS_WORDS: if w in text: score += 1 for w in NEG_WORDS: if w in text: score -= 1 # 处理否定词:出现否定词时整体翻转 if any(n in text for n in NEGATORS): score = -score if score > 0: return 1 if score < 0: return 2 return 0

这段代码的逻辑很直白:正向词加分,负向词减分,出现否定词就翻转符号。参数上,POS_WORDS和NEG_WORDS需要你根据自己的数据持续扩充,尤其是行业黑话,比如「一字板」「核按钮」「天地板」这些词,通用词典里根本没有。阈值这里用的是 0,也就是只要净分不为零就判方向,实际用的时候可以设成 ±1 甚至 ±2,减少噪声。跑完这一步,你会得到一批带噪标签,接下来人工抽检,把准确率拉到 85% 以上再进模型训练。

3. 用 Python 搭一条可复现的情感分析流水线

3.1 环境准备与依赖安装

先把环境弄干净。我习惯用 conda 建独立环境,避免和系统里的包打架。Python 版本建议 3.9 到 3.11,太新的版本有些 NLP 库还没跟上。

# 创建并激活环境 conda create -n astock_sentiment python=3.10 -y conda activate astock_sentiment # 安装核心依赖 pip install pandas numpy scikit-learn jieba pip install torch transformers # 如果要用预训练模型 pip install openpyxl tqdm

jieba是中文分词的老牌工具,轻量、快、够用。scikit-learn负责传统机器学习流水线,transformers留给后面上预训练模型。如果你机器上没有 GPU,torch装 CPU 版就行,情感分析这种任务,几千到几万条数据,CPU 也能跑。装完用python -c "import jieba, sklearn; print('ok')"验证一下,别等到跑代码才发现环境有问题。

3.2 文本清洗与分词:把股吧黑话喂给 jieba

股吧文本的清洗比通用中文文本更麻烦。URL、@、表情符号、重复标点、全角半角混用,这些都要处理。更关键的是,通用分词器不认识股市黑话,会把「一字板」切成「一」「字」「板」,语义直接碎掉。解决办法是给 jieba 加载自定义词典。

import re import jieba # 加载股市黑话自定义词典,每行一个词 jieba.load_userdict("stock_dict.txt") # 内容示例:一字板 / 核按钮 / 天地板 / 打板 def clean_text(text: str) -> str: """清洗股吧评论文本""" text = re.sub(r"http\S+", "", text) # 去 URL text = re.sub(r"@\S+", "", text) # 去 @ text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9]", " ", text) # 只留中英文数字 text = re.sub(r"\s+", " ", text).strip() # 合并空白 return text def tokenize(text: str) -> list: """分词并过滤停用词""" words = jieba.lcut(clean_text(text)) stopwords = set(open("stopwords.txt", encoding="utf-8").read().split()) return [w for w in words if w not in stopwords and len(w) > 1]

clean_text里那条正则[^\u4e00-\u9fa5a-zA-Z0-9]是关键,它把中文、英文、数字之外的所有字符都替换成空格,表情和乱码一次性清掉。tokenize里过滤长度小于等于 1 的词,是因为股吧里单字噪声太多,比如「的」「了」「啊」,留着只会干扰模型。stock_dict.txt需要你自己维护,把「一字板」「核按钮」「天地板」「打板」「接力」这些词加进去,分词质量会肉眼可见地提升。

3.3 用 TF-IDF + 逻辑回归跑通基线模型

别一上来就上 BERT。先用传统方法跑一个基线,知道数据的上限在哪,再决定要不要上大模型。TF-IDF 加逻辑回归,训练快、可解释、调参直观,是验证标签质量的最好工具。

import pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report # 读取数据,假设字段为 content 和 label df = pd.read_csv("stock_comments.csv") df["tokens"] = df["content"].apply(lambda x: " ".join(tokenize(x))) X_train, X_test, y_train, y_test = train_test_split( df["tokens"], df["label"], test_size=0.2, random_state=42, stratify=df["label"] ) # TF-IDF 向量化,限制特征数和 n-gram 范围 vec = TfidfVectorizer(max_features=20000, ngram_range=(1, 2), min_df=3) X_train_vec = vec.fit_transform(X_train) X_test_vec = vec.transform(X_test) # 逻辑回归,处理类别不平衡 clf = LogisticRegression(max_iter=1000, class_weight="balanced", C=1.0) clf.fit(X_train_vec, y_train) pred = clf.predict(X_test_vec) print(classification_report(y_test, pred, digits=4))

参数上,max_features=20000控制特征维度,太大容易过拟合,太小丢信息,两万条评论以内这个值够用。ngram_range=(1, 2)让模型能捕捉「不 涨停」这种二元组合,对否定语义有帮助。min_df=3过滤掉只出现一两次的稀有词,降噪。class_weight="balanced"很重要,因为看涨看跌中性三类样本通常不均衡,不加这个参数,模型会偏向多数类。跑完看classification_report,如果看涨看跌的 F1 都在 0.7 以上,说明标签质量过关,可以继续;如果某一类 F1 特别低,回去查标签,大概率是那一类的标注规则有问题。

3.4 上预训练模型:什么时候值得,怎么微调

当基线模型 F1 卡在 0.75 上不去,而你的业务又确实需要更高精度时,再考虑预训练模型。中文情感分析常用的底座是bert-base-chinese或者金融领域的FinBERT类模型。微调的核心是把分类头接上,用你的标注数据跑几个 epoch。

from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments import torch model_name = "bert-base-chinese" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=3) def encode(batch): return tokenizer(batch["content"], truncation=True, padding="max_length", max_length=128) # 假设 train_ds / val_ds 是 HuggingFace Dataset 对象 train_ds = train_ds.map(encode, batched=True) val_ds = val_ds.map(encode, batched=True) args = TrainingArguments( output_dir="./sentiment_ckpt", learning_rate=2e-5, per_device_train_batch_size=16, num_train_epochs=3, evaluation_strategy="epoch", save_strategy="epoch", load_best_model_at_end=True, ) trainer = Trainer(model=model, args=args, train_dataset=train_ds, eval_dataset=val_ds) trainer.train()

max_length=128对股吧评论足够,大部分评论不超过 100 字,设太大浪费显存。learning_rate=2e-5是 BERT 微调的经典值,太大容易把预训练权重冲垮,太小收敛慢。num_train_epochs=3是经验值,数据少于五千条时,三个 epoch 基本就到顶了,再多就是过拟合。微调完记得在独立测试集上评估,别用验证集的结果骗自己。如果微调后只比 TF-IDF 高两三个点,而推理成本翻十倍,那就要权衡值不值得上。

4. 避坑与排查:股吧情感分析最容易翻车的五个地方

4.1 反爬导致数据断层,模型学到的是时间偏差

现象:模型在训练集上表现很好,一上线就拉胯,预测结果和实际走势对不上。原因:股吧反爬策略会随时间变化,你采集的数据可能集中在某几个月,模型学到了那个时间段的语言风格和情绪分布,换个时间段就失效。解决:采集时记录时间戳,训练时按时间切分训练集和测试集,不要随机切分。定期补采新数据,做增量训练。监控线上预测的分布漂移,一旦发现异常就回查数据源。

4.2 黑话和反讽让模型把「利好」判成看跌

现象:「这波利好出货,懂的都懂」被模型判成看涨。原因:字面有「利好」,但语境是反讽。通用情感模型对反讽几乎无解,股吧里这种表达又特别多。解决:在训练数据里专门标注一批反讽样本,让模型学到「利好 + 出货」「涨停 + 骗炮」这类组合的负面含义。自定义词典里加入反讽短语。如果数据量够,考虑用大模型做数据增强,生成更多反讽变体。

4.3 类别极度不平衡,中性样本被吃掉

现象:模型几乎不预测中性,所有样本都被判成看涨或看跌。原因:股吧评论里极端情绪占多数,中性样本少,模型为了降低损失,干脆放弃中性类。解决:训练时用class_weight="balanced"或者对少数类过采样。评估时看每一类的 F1,不要只看准确率。如果中性类实在少,考虑把它合并进看涨看跌,做成二分类,反而更实用。

4.4 分词把关键黑话切碎,语义丢失

现象:「一字板」被切成「一」「字」「板」,模型完全学不到这个词的含义。原因:通用分词词典不包含股市黑话。解决:维护自定义词典,用jieba.load_userdict加载。词典要持续更新,新出现的黑话及时加进去。上线前抽查一批分词结果,肉眼确认关键术语没被切碎。

4.5 把情绪分数直接当交易信号,回测虚高

现象:回测曲线漂亮,实盘一塌糊涂。原因:情绪分数和未来收益之间不是线性关系,而且回测时用了未来数据,或者忽略了交易成本和滑点。解决:情绪只作为辅助因子,和量价因子一起进模型。回测严格按时间滚动,避免未来函数。把交易成本和滑点算进去,再看策略还有没有超额收益。没有的话,说明这个信号单独用不够,需要组合。

5. 把情绪信号接进量化流程:聚合、验证与一个实用技巧

单条评论的情绪分数意义有限,真正有用的是按股票、按时间窗口聚合后的情绪指标。我一般会做三个聚合量:当日情绪均值、情绪标准差、情绪变化率。均值反映整体倾向,标准差反映分歧程度,变化率反映情绪转向的速度。分歧大往往意味着变盘,情绪从极度乐观快速转向悲观,往往是短期顶部的信号。

聚合的代码不复杂,核心是按stock_code和日期分组,对单条情绪分数做加权平均,权重用like_count的对数,避免个别高赞评论主导整体。

import numpy as np # df 需包含 stock_code, publish_time, sentiment_score, like_count df["date"] = pd.to_datetime(df["publish_time"]).dt.date df["weight"] = np.log1p(df["like_count"]) # log1p 平滑,避免 0 点赞权重为 0 def agg_sentiment(g): w = g["weight"] s = g["sentiment_score"] return pd.Series({ "sent_mean": np.average(s, weights=w), "sent_std": s.std(), "sent_delta": s.iloc[-1] - s.iloc[0] if len(s) > 1 else 0.0, }) daily = df.groupby(["stock_code", "date"]).apply(agg_sentiment).reset_index()

np.log1p对点赞数做平滑,是因为点赞分布极度长尾,直接加权会让一条爆款评论压过所有其他样本。sent_delta用当日首尾差值近似情绪转向,简单但有效。聚合完的daily表可以直接和行情数据按stock_code和date合并,进回测框架。

验证这套信号有没有用,我习惯做两步。第一步,算情绪指标和未来 1 日、3 日收益的 IC 值,看有没有稳定的相关性。第二步,把情绪因子和动量、换手率一起丢进一个简单的多因子模型,看情绪因子的边际贡献。如果 IC 在 0.03 到 0.05 之间,且多因子模型里情绪因子权重显著,那这个方向就值得继续投入。如果 IC 接近零,别硬撑,回去检查标签质量和聚合方式。

最后一个实用技巧:别追求全市场覆盖,先聚焦你熟悉的一两个行业。股吧黑话有很强的行业属性,新能源、医药、半导体的语言风格完全不同。先把一个行业的情绪模型做深做透,标签准确率拉到 0.85 以上,再横向复制到其他行业。我早期贪多,全市场一把梭,结果每个行业都不准,血泪教训。后来收缩到两个行业,模型效果立刻上了一个台阶。希望帮到你。

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

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

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

立即咨询