ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

Python实现A股股吧情感分析:从数据到量化因子 简介这份资源面向对量化投资与自然语言处理感兴趣的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股票代码如 600519stock_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 python3.10 -y conda activate astock_sentiment # 安装核心依赖 pip install pandas numpy scikit-learn jieba pip install torch transformers # 如果要用预训练模型 pip install openpyxl tqdmjieba是中文分词的老牌工具轻量、快、够用。scikit-learn负责传统机器学习流水线transformers留给后面上预训练模型。如果你机器上没有 GPUtorch装 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(rhttp\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, encodingutf-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_size0.2, random_state42, stratifydf[label] ) # TF-IDF 向量化限制特征数和 n-gram 范围 vec TfidfVectorizer(max_features20000, ngram_range(1, 2), min_df3) X_train_vec vec.fit_transform(X_train) X_test_vec vec.transform(X_test) # 逻辑回归处理类别不平衡 clf LogisticRegression(max_iter1000, class_weightbalanced, C1.0) clf.fit(X_train_vec, y_train) pred clf.predict(X_test_vec) print(classification_report(y_test, pred, digits4))参数上max_features20000控制特征维度太大容易过拟合太小丢信息两万条评论以内这个值够用。ngram_range(1, 2)让模型能捕捉「不 涨停」这种二元组合对否定语义有帮助。min_df3过滤掉只出现一两次的稀有词降噪。class_weightbalanced很重要因为看涨看跌中性三类样本通常不均衡不加这个参数模型会偏向多数类。跑完看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_labels3) def encode(batch): return tokenizer(batch[content], truncationTrue, paddingmax_length, max_length128) # 假设 train_ds / val_ds 是 HuggingFace Dataset 对象 train_ds train_ds.map(encode, batchedTrue) val_ds val_ds.map(encode, batchedTrue) args TrainingArguments( output_dir./sentiment_ckpt, learning_rate2e-5, per_device_train_batch_size16, num_train_epochs3, evaluation_strategyepoch, save_strategyepoch, load_best_model_at_endTrue, ) trainer Trainer(modelmodel, argsargs, train_datasettrain_ds, eval_datasetval_ds) trainer.train()max_length128对股吧评论足够大部分评论不超过 100 字设太大浪费显存。learning_rate2e-5是 BERT 微调的经典值太大容易把预训练权重冲垮太小收敛慢。num_train_epochs3是经验值数据少于五千条时三个 epoch 基本就到顶了再多就是过拟合。微调完记得在独立测试集上评估别用验证集的结果骗自己。如果微调后只比 TF-IDF 高两三个点而推理成本翻十倍那就要权衡值不值得上。4. 避坑与排查股吧情感分析最容易翻车的五个地方4.1 反爬导致数据断层模型学到的是时间偏差现象模型在训练集上表现很好一上线就拉胯预测结果和实际走势对不上。原因股吧反爬策略会随时间变化你采集的数据可能集中在某几个月模型学到了那个时间段的语言风格和情绪分布换个时间段就失效。解决采集时记录时间戳训练时按时间切分训练集和测试集不要随机切分。定期补采新数据做增量训练。监控线上预测的分布漂移一旦发现异常就回查数据源。4.2 黑话和反讽让模型把「利好」判成看跌现象「这波利好出货懂的都懂」被模型判成看涨。原因字面有「利好」但语境是反讽。通用情感模型对反讽几乎无解股吧里这种表达又特别多。解决在训练数据里专门标注一批反讽样本让模型学到「利好 出货」「涨停 骗炮」这类组合的负面含义。自定义词典里加入反讽短语。如果数据量够考虑用大模型做数据增强生成更多反讽变体。4.3 类别极度不平衡中性样本被吃掉现象模型几乎不预测中性所有样本都被判成看涨或看跌。原因股吧评论里极端情绪占多数中性样本少模型为了降低损失干脆放弃中性类。解决训练时用class_weightbalanced或者对少数类过采样。评估时看每一类的 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, weightsw), 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 以上再横向复制到其他行业。我早期贪多全市场一把梭结果每个行业都不准血泪教训。后来收缩到两个行业模型效果立刻上了一个台阶。希望帮到你。本文还有配套的精品资源点击获取
返回列表