资讯详情 基于百度AI情感分析与MATLAB的股吧评论情绪量化实战
📅 2026/11/4 23:16:09
简介这份资源面向对量化分析、自然语言处理与金融数据挖掘感兴趣的学习者围绕股票评论与股价涨跌之间的关联展开整合了Python爬虫抓取评论、百度AI情感分析、MATLAB数据处理与Excel可视化等环节适合作为课程设计或小型研究项目的参考范例。压缩包共10个文件约1.71MB包含2个Python脚本分别负责评论采集与情感分析1个MATLAB脚本用于时间序列处理另有CSV评论数据、2个Excel结果表以及4张PNG图表覆盖从原始数据到分析结论的完整链路。目前已有6466人学习下载说明该方向具有较高的关注度。读者可从中获得一套可复用的采集与分析脚本、情感标注后的结构化数据以及股价走势与评论情感分布的对照图表便于理解文本情绪与市场波动之间的潜在联系并在此基础上调整爬取字段或替换分析模型。1. 从股吧评论到涨跌曲线一条被低估的量化情绪链路去年帮一个做量化的朋友复盘他自用的一个小系统起因很朴素他发现自己盯盘时总觉得“评论区一骂第二天就反弹”但真拿这个感觉去做决策十次里错七次。于是他干脆把股吧评论爬下来用百度 AI 做情感打分再用 MATLAB 把情绪曲线和 K 线叠在一起看相关性。这套东西不复杂但链路完整Python 负责采集和语义分析MATLAB 负责数据处理和可视化中间靠 CSV 打通。它解决的不是“预测涨跌”这种大问题而是把“感觉”变成可量化、可回看的情绪指标。适合有 Python 和 MATLAB 基础、想验证市场情绪假设的从业者也适合拿它当爬虫加 NLP 加数据分析的综合练手项目。下面按我实际拆解的顺序讲重点放在参数怎么设、坑在哪。2. 爬虫层股吧评论怎么抓、抓多少、怎么存2.1 目标页面结构与请求策略股吧这类页面的评论通常分页加载列表页给的是标题和跳转链接详情页才是正文和发布时间。常见做法是先抓列表页拿到帖子 URL 列表再逐条进详情页取评论。这里第一个决策点是“抓列表还是抓详情”只抓列表标题信息量太少情感分析容易误判抓详情正文请求量翻几倍但语义质量高得多。我一般选后者但会限制每只股票每天最多抓 N 页避免请求量失控。请求头里 User-Agent 必须带Referer 建议带上列表页地址否则部分站点会返回空内容。频率控制是血泪经验不加延时连续请求轻则被限流返回验证页重则 IP 被临时封。我的习惯是每次请求后sleep1 到 3 秒随机单线程跑别一上来就上多线程。import requests, time, random, csv, re from bs4 import BeautifulSoup HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://example.com/list # 换成实际列表页 } def fetch(url, retry3): for i in range(retry): try: r requests.get(url, headersHEADERS, timeout10) r.encoding r.apparent_encoding if r.status_code 200 and 评论 in r.text: return r.text except Exception as e: print(请求失败, url, e) time.sleep(random.uniform(2, 4)) return None def parse_detail(html): soup BeautifulSoup(html, html.parser) items [] for node in soup.select(.comment-item): # 选择器按实际页面改 content node.select_one(.content).get_text(stripTrue) ts node.select_one(.time).get_text(stripTrue) items.append({content: content, time: ts}) return itemsfetch里做了三件事设置编码防止中文乱码、用内容关键词判断是否拿到真实页面、失败重试三次并随机延时。parse_detail的选择器是占位实际要按目标页面的 class 改这是新手最容易翻车的地方——选择器写错不会报错只会安静地返回空列表。参数上timeout别设太小股吧页面偶尔慢10 秒比较稳重试次数 3 次足够再多说明对方已经在拦你。2.2 数据落库与字段设计抓下来的数据别直接扔进分析脚本先落 CSV字段至少要有股票代码、评论内容、发布时间、抓取时间。发布时间要统一成YYYY-MM-DD HH:MM:SS因为后面 MATLAB 做时间对齐全靠它。评论内容里会有换行、表情符号、 用户清洗时去掉 HTML 标签和多余空白但别把标点删光——情感分析模型对感叹号、问号是有感知的。def save_csv(rows, path): with open(path, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[code, content, publish_time, crawl_time]) writer.writeheader() writer.writerows(rows)utf-8-sig是为了让 Excel 直接打开不乱码这个细节很多人忽略结果交付给不写代码的同事时被吐槽。字段顺序固定后面 MATLAB 用readtable读的时候按列名取不依赖位置。3. 语义分析层百度 AI 情感接口怎么调、阈值怎么定3.1 接口调用与批量处理百度 AI 的情感倾向分析接口输入一段文本返回 positive、negative、neutral 的概率和置信度。单条调用延迟大概几百毫秒几千条评论串行跑要十几分钟所以实际做法是批量提交接口一般支持一次传多条。注意免费额度和 QPS 限制超了会返回错误码脚本里要捕获并退避重试。import requests, json, time AK your_api_key SK your_secret_key TOKEN_URL https://aip.baidubce.com/oauth/2.0/token def get_token(): params {grant_type: client_credentials, client_id: AK, client_secret: SK} r requests.post(TOKEN_URL, paramsparams, timeout10) return r.json()[access_token] def sentiment_batch(texts, token): url https://aip.baidubce.com/rpc/2.0/nlp/v1/sentiment_classify results [] for i in range(0, len(texts), 20): # 每批 20 条 batch texts[i:i20] payload {text: batch} r requests.post(url ?access_token token, datajson.dumps(payload), timeout15) data r.json() if error_code in data: print(接口报错, data[error_code], data.get(error_msg)) time.sleep(5) continue results.extend(data.get(items, [])) time.sleep(0.5) return resultsget_token拿到的 token 有效期约 30 天别每次请求都重新拿缓存起来。批量大小 20 是保守值太大容易触发长度限制。error_code出现时先看是不是 QPS 超限一般是 18 或 17 这类退避 5 秒再试。返回的items里每条有sentiment0 负向、1 中性、2 正向和confidence这两个字段都要存后面做加权用。3.2 情感分数怎么转成可用指标接口给的是类别和置信度直接按类别计数太粗。我一般转成一个连续分数正向记confidence负向记-confidence中性记 0然后按天聚合求均值得到“日情绪分”。这个分数落在 -1 到 1 之间正负方向直观。阈值上置信度低于 0.6 的样本建议单独标记别直接参与均值否则中性文本被误判会污染曲线。字段含义建议处理sentiment0 负向 / 1 中性 / 2 正向映射为 -1 / 0 / 1confidence置信度 0~1低于 0.6 降权或剔除日情绪分当日所有评论加权均值与涨跌幅做相关分析这一步的坑在于评论时间和交易时间不对齐。周末、节假日的评论会堆到下一个交易日聚合时要按“最近交易日”归并而不是简单按自然日。常见做法是维护一张交易日历表把非交易日的评论归到其后第一个交易日。4. MATLAB 数据处理情绪曲线和涨跌幅怎么对齐4.1 数据导入与时间对齐MATLAB 这边用readtable读两个 CSV一个是情绪日聚合表一个是行情表日期、收盘价、涨跌幅。关键是日期列要转成datetime类型然后用timetable做对齐。行情表通常只有交易日情绪表可能有缺失用synchronize按交易日历合并缺失值用前值填充或置零具体看你的假设。emo readtable(sentiment_daily.csv); emo.date datetime(emo.date, InputFormat, yyyy-MM-dd); emo_tt table2timetable(emo(:, {date,score}), RowTimes, date); px readtable(price_daily.csv); px.date datetime(px.date, InputFormat, yyyy-MM-dd); px_tt table2timetable(px(:, {date,pct_chg}), RowTimes, date); aligned synchronize(emo_tt, px_tt, daily, fillwithconstant, 0);synchronize的daily会按日历日展开非交易日情绪分填 0 会拉低均值更稳的做法是先用交易日历过滤再union合并。fillwithconstant只是兜底别指望它解决对齐问题。对齐后建议画一眼两条曲线的叠加图肉眼先看有没有明显错位再谈相关系数。4.2 相关性分析与滞后检验直接算同日相关系数往往很低因为情绪影响可能有滞后。我的做法是算情绪分对涨跌幅在 0 到 3 天滞后下的互相关系数看哪个滞后期绝对值最大。用xcorr或手动 shift 都行手动 shift 更直观。lags 0:3; corrs zeros(size(lags)); for k 1:numel(lags) lag lags(k); s aligned.score(1:end-lag); p aligned.pct_chg(1lag:end); valid ~isnan(s) ~isnan(p); corrs(k) corr(s(valid), p(valid)); end disp(table(lags, corrs, VariableNames, {lag,corr}));这段代码算的是情绪领先涨跌 lag 天的相关系数。注意样本量会随 lag 减小lag 太大时统计意义下降一般不超过 3 到 5 天。相关系数绝对值 0.1 到 0.2 在这个场景里就算有信号了别期待 0.8那基本是过拟合或者数据泄漏。如果发现某个 lag 相关系数异常高先查是不是把未来数据混进来了——这是量化里最经典的翻车点。5. 避坑与排查这条链路上最容易翻车的五件事5.1 评论抓成空壳分析全是中性现象情感分析结果里中性占比超过 90%曲线一条直线。原因详情页选择器写错抓到的content是空字符串或导航文字接口对空文本一律返回中性。解决抓完先打印前 10 条内容人工看一眼加一条断言len(content) 10不满足的丢弃并记录 URL回头修选择器。5.2 接口报错但脚本不报结果缺一大块现象最终情绪表某些日期整段缺失。原因批量调用时遇到 QPS 超限continue跳过了整批日志里只有一行 print没中断。解决把失败批次写入单独文件跑完后统计失败率超过 5% 就重跑失败部分同时在聚合前检查每日样本量低于阈值比如 10 条的日期标记为不可靠。5.3 时间对齐把周末评论算错日子现象周一情绪分异常高或低和周末新闻对不上。原因按自然日聚合周末评论被单独成行synchronize后又填了 0。解决聚合阶段就用交易日历映射把周六周日评论归到周一MATLAB 侧用union而非daily避免生成非交易日行。5.4 相关系数虚高其实是数据泄漏现象lag0 相关系数 0.6 以上兴奋之后实盘完全无效。原因行情表的涨跌幅用了收盘后数据而评论抓取时间覆盖了盘中等于用未来信息解释过去。解决严格按“评论发布时间早于当日收盘”过滤或者统一用 T 日评论对 T1 涨跌幅lag 至少为 1。5.5 MATLAB 读中文 CSV 乱码现象readtable读进来评论列全是问号或方块。原因文件编码和 MATLAB 默认编码不一致。解决Python 侧统一存utf-8-sigMATLAB 侧readtable加Encoding,UTF-8如果还不行用detectImportOptions显式指定编码和分隔符。6. 进阶技巧把情绪分做成可回看的信号面板链路跑通后真正有价值的是把它做成每天能自动更新、能回看的面板而不是一次性脚本。我的习惯是加一个“信号强度”列日情绪分绝对值超过 0.3 且样本量大于 30 时标记为强信号否则弱信号再按 lag1 的相关系数符号决定方向。这样每天打开 CSV 就能看到哪些股票情绪异动而不是每次重跑分析。验证方法上别只看全样本相关系数做滚动窗口每 60 个交易日算一次 lag1 相关看符号是否稳定。如果符号来回跳说明这个信号不稳定别拿去实盘。下面这段是滚动相关的骨架win 60; roll_corr nan(height(aligned)-win, 1); for i 1:height(aligned)-win s aligned.score(i:iwin-1); p aligned.pct_chg(i1:iwin); valid ~isnan(s) ~isnan(p); if sum(valid) 30 roll_corr(i) corr(s(valid), p(valid)); end end plot(aligned.date(1:numel(roll_corr)), roll_corr); yline(0, --);窗口 60 是经验值太短噪声大太长反应慢。sum(valid) 30是防止样本太少算出假相关。画出来如果曲线长期在 0 附近晃就老实承认这个标的的情绪信号弱换标的比调参数有用。这套东西我从头到尾跑过几轮最大的教训是爬虫的稳定性比分析模型重要得多数据缺一块后面所有相关系数都是玄学。从那以后我每次改完选择器或接口参数都强制先跑 50 条样本人工核对内容、时间和情感标签确认无误再放量。希望帮到你。本文还有配套的精品资源点击获取