简介:这份资源面向对量化分析、自然语言处理与金融数据挖掘感兴趣的学习者,围绕股票评论与股价涨跌之间的关联展开,整合了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, retry=3): for i in range(retry): try: r = requests.get(url, headers=HEADERS, timeout=10) 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(strip=True) ts = node.select_one(".time").get_text(strip=True) 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="", encoding="utf-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, params=params, timeout=10) 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:i+20] payload = {"text": batch} r = requests.post(url + "?access_token=" + token, data=json.dumps(payload), timeout=15) 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里每条有sentiment(0 负向、1 中性、2 正向)和confidence,这两个字段都要存,后面做加权用。
3.2 情感分数怎么转成可用指标
接口给的是类别和置信度,直接按类别计数太粗。我一般转成一个连续分数:正向记+confidence,负向记-confidence,中性记 0,然后按天聚合求均值,得到“日情绪分”。这个分数落在 -1 到 1 之间,正负方向直观。阈值上,置信度低于 0.6 的样本建议单独标记,别直接参与均值,否则中性文本被误判会污染曲线。
| 字段 | 含义 | 建议处理 |
|---|---|---|
| sentiment | 0 负向 / 1 中性 / 2 正向 | 映射为 -1 / 0 / +1 |
| confidence | 置信度 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(1+lag: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 相关系数虚高,其实是数据泄漏
现象:lag=0 相关系数 0.6 以上,兴奋之后实盘完全无效。原因:行情表的涨跌幅用了收盘后数据,而评论抓取时间覆盖了盘中,等于用未来信息解释过去。解决:严格按“评论发布时间早于当日收盘”过滤,或者统一用 T 日评论对 T+1 涨跌幅,lag 至少为 1。
5.5 MATLAB 读中文 CSV 乱码
现象:readtable读进来评论列全是问号或方块。原因:文件编码和 MATLAB 默认编码不一致。解决:Python 侧统一存utf-8-sig,MATLAB 侧readtable加'Encoding','UTF-8';如果还不行,用detectImportOptions显式指定编码和分隔符。
6. 进阶技巧:把情绪分做成可回看的信号面板
链路跑通后,真正有价值的是把它做成每天能自动更新、能回看的面板,而不是一次性脚本。我的习惯是加一个“信号强度”列:日情绪分绝对值超过 0.3 且样本量大于 30 时标记为强信号,否则弱信号;再按 lag=1 的相关系数符号决定方向。这样每天打开 CSV 就能看到哪些股票情绪异动,而不是每次重跑分析。
验证方法上,别只看全样本相关系数,做滚动窗口:每 60 个交易日算一次 lag=1 相关,看符号是否稳定。如果符号来回跳,说明这个信号不稳定,别拿去实盘。下面这段是滚动相关的骨架:
win = 60; roll_corr = nan(height(aligned)-win, 1); for i = 1:height(aligned)-win s = aligned.score(i:i+win-1); p = aligned.pct_chg(i+1:i+win); 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 条样本人工核对内容、时间和情感标签,确认无误再放量。希望帮到你。
本文还有配套的精品资源,点击获取