ARTICLE DETAIL

资讯详情

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

基于Python的股市评论情感分析:从爬虫到BERT微调实战

基于Python的股市评论情感分析:从爬虫到BERT微调实战 简介这份资源面向计算机相关专业正在做毕业设计的学生以及需要项目实战练习的学习者提供一套基于Python机器学习与深度学习实现股市评论情感分析的完整方案可用于毕业设计、课程设计或期末大作业。压缩包共12个文件约24.72MB包含4个py脚本、2个txt情感词典、2个csv行情与评分数据、2个png可视化图表以及说明文档与数据压缩包覆盖数据读取、模型训练、情感指数计算与结果绘图等环节。内容预览显示项目按股票行情数据、正负情感词典、机器学习与深度学习双模型脚本、情感指数计算及BI指标绘图等模块组织结构清晰便于理解从评论情感到市场指标的分析链路。目前已有394人学习适合希望快速掌握文本情感分析流程、对照代码复现实验并完成论文撰写的读者参考。1. 股市评论情感分析从一条股吧帖子到可复现的毕业设计一条股吧帖子写着今天这走势主力又在洗盘拿住别慌另一条写着业绩暴雷明天一字板跑不掉。这两句话对股价的暗示完全相反但机器一开始分不清——它只看到一堆汉字。股市评论情感分析要解决的就是把这种口语化、带黑话、充满反讽的文本自动判成看多、看空还是中性。它属于文本分类里最经典的一支用 Python 机器学习或深度学习都能做也是毕业设计里少见的数据能自己爬、模型能自己调、结论能自己讲的方向。这个方向适合两类人一类是刚学完 Python 和机器学习、想找一个完整项目把爬虫、清洗、特征、模型、评估串起来的同学另一类是做过图像分类、想换到 NLP 赛道看看文本预处理的坑到底在哪的工程师。整套东西的核心链路是爬股市评论 → 清洗去噪 → 人工或规则打标 → 传统模型TF-IDF 朴素贝叶斯/ SVM或深度学习模型TextCNN / BiLSTM / BERT 微调→ 评估与可视化。下面按这条链路一层层拆开把能抄的代码、该调的参数和真正会翻车的地方都讲清楚。2. 数据从哪来股市评论的爬取与清洗2.1 评论语料的三个来源与取舍做情感分析第一道坎不是模型是数据。股市评论常见来源有三类股吧类论坛帖子、财经资讯下的用户评论、以及带情绪标签的研报摘要。前两类是真实散户语言口语、黑话、反讽多最贴近股市评论这个场景第三类偏正式情感表达克制训练出来的模型放到股吧里会水土不服。我一般优先选股吧类帖子因为它的情感极性最鲜明标注成本低。采集时按股票代码分页抓每只股票抓最近若干页保证正负样本都有——只抓一只票容易全是骂声或全是吹捧类别失衡会让模型直接学成永远预测多数类。采集要注意频率别把人家服务器打挂也别抓登录后才可见的内容。用requests加BeautifulSoup就够遇到动态渲染的页面再上selenium。下面是最小可用的采集骨架import requests from bs4 import BeautifulSoup import time, csv, random HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } def fetch_page(url): # 加超时和重试避免个别请求卡死整个脚本 for _ in range(3): try: r requests.get(url, headersHEADERS, timeout8) r.encoding r.apparent_encoding # 中文页面编码经常识别错手动兜底 return r.text except requests.RequestException: time.sleep(random.uniform(1, 3)) return def parse_posts(html): soup BeautifulSoup(html, html.parser) posts [] for div in soup.select(.article-text): # 选择器按目标站点实际结构调整 text div.get_text(stripTrue) if len(text) 5: # 太短的顶沙发直接丢 posts.append(text) return posts if __name__ __main__: all_posts [] for page in range(1, 21): html fetch_page(fhttps://example.com/stock/600000?page{page}) all_posts.extend(parse_posts(html)) time.sleep(random.uniform(1.5, 4)) # 随机间隔别用固定 sleep with open(raw_comments.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([text]) writer.writerows([[p] for p in all_posts])这段代码的关键点有三个apparent_encoding解决中文乱码随机sleep降低被封风险长度过滤提前砍掉无信息短文本。参数上页数按你需要的数据量定一般 5000 到 20000 条足够跑通一个毕业设计间隔别低于 1 秒。2.2 清洗把噪音和信号分开爬下来的原始文本脏得超出想象HTML 残留、表情符号、某人、重复刷屏、广告。清洗的目标不是把文本洗得干干净净而是保留情感信号。比如666牛冲这类词要留点击链接领牛股这种广告要删。清洗流程我固定成几步去 HTML 标签和 URL → 去 和话题符号 → 全角转半角 → 只保留中文、数字和必要标点 → 去重。注意别急着去停用词情感分析里不没别这些否定词是命根子去掉就等着模型把不涨判成涨。import re import pandas as pd def clean_text(text): text re.sub(r[^], , text) # 去 HTML 标签 text re.sub(rhttp\S|www\.\S, , text) # 去 URL text re.sub(r[\w\u4e00-\u9fa5], , text) # 去 用户 text re.sub(r#.*?#, , text) # 去话题 text text.replace(\u3000, ) # 全角空格 text re.sub(r[^\u4e00-\u9fa5A-Za-z0-9。、], , text) text re.sub(r\s, , text).strip() return text df pd.read_csv(raw_comments.csv) df[clean] df[text].astype(str).map(clean_text) df df[df[clean].str.len() 5].drop_duplicates(subsetclean) df.to_csv(clean_comments.csv, indexFalse, encodingutf-8-sig) print(df.shape)[^\u4e00-\u9fa5A-Za-z0-9。、]这个正则决定了保留哪些字符中文区间是\u4e00-\u9fa5标点只留句读类感叹号和问号对情感判断有用所以保留。跑完看df.shape如果清洗后剩不到原始的一半说明正则太狠回头检查是不是把数字或英文全删了。3. 标注与特征让机器看懂看多和看空3.1 情感标签怎么定才不返工标签体系是毕业设计里最容易埋雷的地方。常见做法是三分类看多1、看空0、中性2。但中性最难标很多句子既不看多也不看空硬塞进中性会让标注一致性很差。我的经验是先做二分类看多/看空把明显中性的句子直接剔除等模型跑通再考虑加中性类。标注方式上纯人工标 5000 条太累可以用规则预标注 人工修正含涨停、利好、加仓、抄底的预标看多含跌停、利空、割肉、跑路的预标看空然后人工过一遍改错。这样能把标注时间压到原来的三分之一。标注一致性要抽查随机抽 200 条让两个人各标一遍算 Kappa 系数低于 0.6 说明标签定义太模糊得回去改规则。这一步别省标签错了后面模型再强也是白搭。3.2 TF-IDF 特征传统模型的起跑线传统机器学习模型吃的是数值特征文本得先转成向量。TF-IDF 是最稳的起点它衡量一个词在当前文档里出现多频繁、在整个语料里多稀有稀有且频繁的词权重高正好对应涨停暴雷这种情感强词。import jieba from sklearn.feature_extraction.text import TfidfVectorizer def cut(text): # 用 jieba 分词过滤单字和空白 return .join([w for w in jieba.lcut(text) if len(w) 1]) df[seg] df[clean].map(cut) vectorizer TfidfVectorizer( max_features8000, # 词表上限太大容易过拟合 ngram_range(1, 2), # 加入二元词组捕捉不 涨这类否定搭配 min_df3, # 出现少于3次的词丢掉降噪 max_df0.9 # 出现在90%以上文档的词丢掉多为无意义高频词 ) X vectorizer.fit_transform(df[seg]) print(X.shape)ngram_range(1,2)是这里最值钱的参数因为中文情感里不动词的否定结构靠单个词根本表达不了。max_features设 8000 到 20000 之间比较稳语料小就调小。分词时过滤单字是因为单字噪音大但跌涨这种单字其实有信号如果发现模型对涨跌不敏感可以把单字放回来再试。4. 模型选型从朴素贝叶斯到 BERT 微调4.1 传统模型快、可解释、够用如果只是毕业设计跑通朴素贝叶斯和线性 SVM 完全够。朴素贝叶斯假设特征独立虽然不成立但在短文本分类上出奇地稳SVM 在小样本高维特征下边界清晰泛化好。两个都跑一遍对比报告里就有内容可写。from sklearn.model_selection import train_test_split from sklearn.naive_bayes import MultinomialNB from sklearn.svm import LinearSVC from sklearn.metrics import classification_report X_train, X_test, y_train, y_test train_test_split( X, df[label], test_size0.2, random_state42, stratifydf[label] ) nb MultinomialNB(alpha0.5) # alpha 是平滑系数语料小就调大 nb.fit(X_train, y_train) print(NB:\n, classification_report(y_test, nb.predict(X_test))) svm LinearSVC(C1.0, max_iter5000) # C 越大越拟合训练集容易过拟合 svm.fit(X_train, y_train) print(SVM:\n, classification_report(y_test, svm.predict(X_test)))stratifydf[label]保证训练测试集类别比例一致不然评估结果会飘。alpha和C是这两个模型的核心调参点用网格搜索在验证集上选别在测试集上调。传统模型的好处是训练几秒钟出结果还能直接看哪个词权重最高报告里画个词云或权重条形图很直观。4.2 深度学习TextCNN 与 BiLSTM 怎么选想要更高准确率、报告更高级就上深度学习。文本分类里最常用的两个结构是 TextCNN 和 BiLSTM。TextCNN 用不同尺寸的卷积核抓 n-gram 特征训练快、参数少BiLSTM 按顺序读文本能记住上下文对长句和反讽更友好但训练慢。下面是一个 PyTorch 版 TextCNN 的核心结构import torch import torch.nn as nn class TextCNN(nn.Module): def __init__(self, vocab_size, embed_dim128, num_classes2): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim, padding_idx0) # 三种卷积核尺寸分别抓 2、3、4 元词组特征 self.convs nn.ModuleList([ nn.Conv2d(1, 128, (k, embed_dim)) for k in (2, 3, 4) ]) self.dropout nn.Dropout(0.5) self.fc nn.Linear(128 * 3, num_classes) def forward(self, x): x self.embedding(x) # [B, L, E] x x.unsqueeze(1) # [B, 1, L, E] x [torch.relu(conv(x)).squeeze(3) for conv in self.convs] x [torch.max_pool1d(f, f.size(2)).squeeze(2) for f in x] x torch.cat(x, dim1) return self.fc(self.dropout(x))embed_dim128是常见起点语料大可以上 256卷积核数量 128 够用Dropout(0.5)是防过拟合的关键别省。训练时用CrossEntropyLoss加Adam学习率 1e-3batch size 64跑 10 到 20 个 epoch看验证集 loss 不再下降就停。如果显存不够把embed_dim和卷积核数量减半。BiLSTM 的结构差异在于把卷积换成nn.LSTM(embed_dim, hidden, bidirectionalTrue)取最后时刻的隐藏状态接全连接。它更容易过拟合Dropout和早停都要用上。想再往上冲就上 BERT 微调用transformers加载中文预训练模型接一个分类头学习率调到 2e-5 量级两三个 epoch 就能超过前两者代价是训练时间和显存。5. 避坑与排查情感分析里最容易翻车的五件事5.1 类别失衡导致模型只会猜多数类现象准确率看着有 85%但看空类的召回率接近 0模型几乎全预测看多。原因股吧里看多评论天然多于看空训练集失衡。解决训练时给损失函数加类别权重CrossEntropyLoss(weighttorch.tensor([1.0, 2.5]))或者对少数类过采样评估时别只看准确率重点看每一类的 F1。5.2 否定词被停用词表误删现象模型把不看好判成看多。原因清洗时用了通用停用词表把不没别删了。解决情感分析禁用通用停用词表或者自建一个只含的、了、是这类真正无意义词的短表否定词一律保留。5.3 训练集和测试集来自同一时间段指标虚高现象离线评估 90%换个时间段的评论一测掉到 70%。原因同一时段的评论用词高度相似模型记住了时段特征而非情感。解决按时间切分数据集用早期数据训练、后期数据测试指标会难看但真实。5.4 分词把股市黑话切碎现象一字板割韭菜被切成单字特征失效。原因jieba 默认词典没有这些新词。解决用jieba.add_word(一字板)把领域词加进自定义词典或者加载一份股市词表分词前先jieba.load_userdict(stock_dict.txt)。5.5 深度学习模型在小语料上过拟合现象训练集准确率 99%验证集 65%。原因语料只有几千条模型参数太多。解决先降模型规模减层数、减维度加Dropout和 L2 正则或者干脆退回传统模型——几千条数据上TF-IDF SVM 经常打得过 TextCNN。6. 让结果站得住评估、可视化与一个提分技巧模型跑出来只是开始毕业设计要的是结论可信。评估上除了准确率、精确率、召回率、F1一定要画混淆矩阵看清楚模型到底把哪两类搞混——很多时候是中性和看多混而不是看多和看空混这直接决定你后面往哪个方向优化。可视化我一般做三样情感分布饼图看数据本身偏不偏、词云看多和看空各一张对比强烈、混淆矩阵热力图。词云别用默认停用词把股票今天这类高频无意义词手动剔掉不然满屏都是废话。这里给一个真正能提分的技巧把 TF-IDF 特征和深度学习 embedding 拼在一起。做法是用传统模型跑一遍把预测概率作为额外特征和 BERT 的[CLS]向量拼接后接一个全连接层。这个模型融合思路在文本分类比赛里很常见通常能比单模型高 1 到 3 个点。代价是工程复杂度上升代码要维护两条前向链路调试时先分别跑通再合并。# 伪代码示意概率特征 深度特征拼接 tfidf_prob svm.predict_proba(X_test) # [N, 2] bert_feat bert_model(X_test_ids) # [N, 768] fused torch.cat([torch.tensor(tfidf_prob), bert_feat], dim1) logits fusion_fc(fused) # 最终分类拼接前记得把两组特征各自归一化量纲差太大会让网络偏向数值大的那组。融合模型训练时学习率调小1e-4 量级不然预训练权重容易被冲垮。我自己做这类项目最大的教训是别一上来就冲 BERT。第一次做的时候直接上预训练模型调了三天参数最后发现数据标注本身就有 15% 的错标模型再好也救不回来。后来养成习惯先用传统模型跑一遍看混淆矩阵和错例把标注问题揪出来再决定要不要上深度模型。数据质量永远比模型结构重要这个顺序别搞反。希望帮到你。本文还有配套的精品资源点击获取
返回列表