简介面向股票舆情分析与爬虫入门者这是一套围绕“股票涨跌与评论关系”的小型完整项目演示用Python爬虫抓取股票评论、调用百度AI接口进行情感语义分析、再以MATLAB处理数据和验证关联。RAR压缩包共10个文件约1.71MB主要包含Python脚本爬虫与评论分析、MATLAB脚本时间序列/数组处理、CSV评论数据、Excel结果表情感与日期以及多张PNG可视化图整体可按数据采集、情感标注、统计关联和图表展示四步理解。目前已有6458人下载学习。资源提供可直接运行的爬虫与分析代码清晰展示从发送请求、解析页面到情感标注、统计对比的步骤Excel文件便于核对中间结果图片则直观呈现股价走势、情感分布及二者关系适合希望快速搭建同类分析流程的数据爱好者与金融科技初学者。附带的CSV与Excel数据还能用于二次实验方便扩展自己的分析思路。1. 股票评论情绪分析从爬虫到 MATLAB 的完整落地链路把股票评论抓下来、用语义分析打情绪分、再放进 MATLAB 和涨跌数据做关联——这套流程听起来带点玄学但它确实是一条可以完整复现的“爬虫 文本分析 统计验证”链路。我拆这份资料时最关心三个问题评论源选什么反爬最轻、百度 AI 的情感接口怎么批量调用不翻车、MATLAB 对齐涨跌数据时到底有多少时间坑。这篇文章按落地顺序把这三个点逐个拆开顺带写清五条我实际踩过的坑。适合正在做课程设计、想给策略加另类数据源或者单纯想验证“情绪是否领先价格”的开发者。2. 爬虫层拉取股票评论的三个关键选型与反爬处理2.1 评论源选型公开、稳定、有分页是底线评论源是整个链路的起点选错后面全白干。我一般按三个标准筛第一页面公开可访问不需要登录态否则爬虫要处理 Cookie 和会话保持复杂度直接翻倍第二有稳定的分页结构最好是page1这种纯参数翻页解析规则写一次就能复用第三反爬强度不能太高评论这种 UGC 内容通常比行情数据宽松但也要做好限流准备。实际操作里很多公开讨论区都满足这些条件。选型时有一个容易忽略的点评论的时间字段必须完整。很多站点只显示“今天”“昨天”这种相对时间或者列表页只显示日期不显示时分这对后续做“当日情绪和当日涨跌”的对齐非常不利。我在拆这套资料时优先选带完整yyyy-MM-dd HH:mm时间戳的评论源宁可少拿一点评论数也要保证时间精度。另外要提醒一句评论源的数据质量直接决定语义分析的上限。灌水、广告、重复刷屏占多的评论打出来的情感分数噪声很大后面算相关系数大概率是一片空白。我一般会在爬虫阶段就做一层简单过滤文本长度少于 10 个字符的丢弃纯表情或纯链接的丢弃重复文本只保留每页第一次出现的那条。2.2 爬虫实现requests 加正则解析评论正文评论页的结构通常比行情页简单最省事的方案就是requests拉 HTML正则提取评论正文和时间字段。下面这段代码是我实际改过的版本抓取一个公开讨论区某只股票的评论列表翻 50 页。import requests import time import csv import re import random # 目标站点的评论列表URLpage 为页码参数 url https://example.com/stock-comments?code600XXXpage{} headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://example.com/, } csv_file open(comments.csv, a, newline, encodingutf-8) writer csv.writer(csv_file) # 只打印进展不处理业务逻辑方便中断后追加 for page in range(1, 51): try: resp requests.get(url.format(page), headersheaders, timeout10) if resp.status_code ! 200: print(fpage {page} - status {resp.status_code}, sleep 30s) time.sleep(30) continue html resp.text # 评论文本在 div[classcomment-content] 内 contents re.findall(rdiv classcomment-content(.*?)/div, html, re.S) # 发布时间在 span[classcomment-time] 内 times re.findall(rspan classcomment-time(.*?)/span, html, re.S) for content, t in zip(contents, times): text re.sub(r[^], , content).strip() if text and len(text) 10: writer.writerow([600XXX, t, text]) print(fpage {page} done, got {len(contents)} comments) time.sleep(random.uniform(1, 3)) # 主动限速 except Exception as e: print(fpage {page} error: {e}) time.sleep(10) csv_file.close()这段代码的逻辑是遍历 1 到 50 页每次请求后先用正则把评论正文和发布时间分别抠出来再逐条清理 HTML 标签、过滤短文本最后写进 CSV。zip(contents, times)这里有个隐含假设——正文和时间数量一致且顺序对应实际大部分列表页都是这个结构。几个参数值得注意。timeout10是请求超时设太短容易被慢页面误伤设太长会拖慢整体速度encodingutf-8是写文件的编码爬虫阶段就固定 UTF-8能避免后面 MATLAB 读 CSV 时出现中文乱码time.sleep(random.uniform(1, 3))是每页的随机间隔用来把请求节奏打散。有人嫌慢会把它去掉但我不建议这么做——评论区页面虽然反爬松短时间高频翻页照样会触发限流后面专门讲这个坑。2.3 落盘设计原始评论比情感分数更值钱爬虫阶段存什么、不存什么直接决定后面能不能补做分析。我的习惯是永远存原始评论文本而不是爬完就立刻只存情感得分。原因很简单百度 AI 的返回结果格式、字段名、分类口径都可能随版本调整原始文本抓在手里随时可以重新批量调用不存在数据不可逆的问题。CSV 字段我定为四列code、time、content、source。code是股票标识time是 UTC8 的评论时间content是清理后的正文source是评论源标记。加source列是因为后面如果换了一个评论源可以方便对比不同群体的情绪差异这步对于做稳健性检验很有用。断点续跑也很重要。上面这段代码用a模式打开 CSV意味着中断后重新运行会在文件末尾追加不会覆盖已有数据。但要注意一个问题重复运行同一页会写入重复评论。我一般在写完 CSV 后跑一遍简单去重按(time, content)两列联合去重把重复行删掉。这个操作在 Python 里用pandas.read_csv加drop_duplicates十行以内就能搞定不必在爬虫阶段做得太复杂。3. 语义分析层调用百度 AI 情感倾向分析的完整流程3.1 接口选型情感倾向分析能做什么、不能做什么百度 AI 开放平台的情感倾向分析接口本质是一个预训练的中文短文本分类器输入一句话输出情感类别积极、消极、中性和置信度。对股票评论这个场景它解决的是“这条评论到底是看多还是看空”的问题比如“基本面没问题坚定持有”会被判为积极“业绩暴雷跑都来不及”会被判为消极。但这个接口有边界必须先讲清楚。第一它是短文本模型单条文本太长时效果会下降通常需要截断到 2000 字节左右一条股票评论动辄几百字的情况不多但有长文分析时先截断再送进去。第二它识别的是通用情感不是金融情感。像“跌成这样反而放心了”这种带反讽的表达通用模型很容易判错这是模型本身的天花板不是代码能解决的。第三免费配额有 QPS 限制并发开大了会直接报错。我一般把调用频率控制在每秒不超过 10 条保守一点更安全。我的建议是不要把情感分类的准确率当成首要目标而是把所有评论都跑一遍、打分口径一致。对“情绪和涨跌的关系”这个研究目标来说误差的方向一致比绝对准确更重要。3.2 获取 access_token 并封装调用函数调用百度 AI 接口的第一步是拿 access_token。它的机制是用 API Key 和 Secret Key 换一个临时 tokentoken 有过期时间通常有效期一个月代码里缓存复用就行不用每次重新请求。import requests import json import time API_KEY 你的API Key # 在百度AI开放平台控制台创建应用后获取 SECRET_KEY 你的Secret Key def get_access_token(): 获取百度AI接口的access_tokentoken可缓存复用 url https://aip.baidubce.com/oauth/2.0/token params { grant_type: client_credentials, client_id: API_KEY, client_secret: SECRET_KEY, } resp requests.post(url, paramsparams, timeout10) resp.raise_for_status() return resp.json()[access_token] def sentiment(text, token): 单条文本情感分析返回 (sentiment, confidence) sentiment: 0积极, 1消极, 2中性 url https://aip.baidubce.com/rpc/2.0/nlp/v1/sentiment headers {Content-Type: application/json} params {charset: UTF-8, access_token: token} body {text: text[:2000]} # 截断超长文本 resp requests.post(url, paramsparams, headersheaders, jsonbody, timeout10) resp.raise_for_status() result resp.json() print(result) # 第一次运行时先打印确认返回字段名 sentiment result.get(sentiment) confidence result.get(confidence) return sentiment, confidence逻辑不复杂get_access_token用 OAuth 的 client_credentials 模式换 tokensentiment函数把文本 POST 给情感分析接口解析返回的sentiment和confidence。调用时需要注意print(result)那行——不同时期接口返回的 JSON 嵌套结构可能不一样有些版本把结果包在items数组里字段名也可能是items[0].sentiment。我每次接新项目都会先打印一次原始返回用真实响应调整解析代码不盲信文档。3.3 批量打分限速、重试和断点续跑单条调用没问题之后批量循环就顺理成章了。这步的核心是控制节奏以及处理失败时不丢数据。import csv import time token get_access_token() reader csv.reader(open(comments.csv, encodingutf-8)) out_file open(comments_with_sentiment.csv, w, newline, encodingutf-8) writer csv.writer(out_file) writer.writerow([code, time, content, sentiment, confidence]) for row in reader: if row[0] code: # 跳过表头 continue code, t, content row for attempt in range(3): # 最多重试3次 try: sentiment_val, confidence sentiment(content, token) writer.writerow([code, t, content, sentiment_val, confidence]) break except Exception as e: print(fattempt {attempt1} failed: {e}, text{content[:30]}) time.sleep(2 * (attempt 1)) # 退避等待 time.sleep(0.15) # 约每秒6条给QPS留余量 out_file.close()循环里做了三件事逐行读评论、调情感接口、把结果和新字段一起写回新文件。for attempt in range(3)是重试机制失败后等待2 * (attempt 1)秒做简单的指数退避。time.sleep(0.15)是全局限速算下来每秒 6 条左右足够避开大部分免费配额限制。这里有一个我明确强调过的点保留content原始列。很多教程只存情感得分我强烈不建议。情感接口的评分口径会变保留原文才能后面重新打分对比不同批次的结果。4. MATLAB 数据处理情绪指数构建与相关性计算4.1 读入数据并计算涨跌幅编码和日期格式是两座大山到了 MATLAB 这步第一件事是把带情感标注的评论 CSV 读进来同时准备对应股票的历史行情数据。这里最常见的翻车点就是中文乱码和日期格式对不上。% 读入情感分析结果必须指定UTF-8编码 % 如果文件带BOMEncoding指定为UTF-8就能正确识别 comments readtable(comments_with_sentiment.csv, Encoding, UTF-8); % 模拟行情数据真实场景建议从行情终端导出或使用本地数据库 % 字段date(交易日期), close(收盘价) dates datetime(2024, 4, 1):datetime(2024, 5, 31); dates dates(~ismember(weekday(dates), [1, 7])); % 去掉周末粗略交易日 close 10 cumsum(randn(length(dates), 1) * 0.2); % 模拟收盘价序列 pct_change [NaN; close(2:end) ./ close(1:end-1) - 1] * 100; prices table(dates, close, pct_change);readtable的Encoding参数是中文不乱码的关键。如果 CSV 是 Python 用encodingutf-8写的MATLAB 里指定UTF-8就行如果文件在编辑软件里被转换成了 ANSI 编码但内容还是中文MATLAB 就无能为力了。weekday(dates)那行是把周末剔除——严格说还有节假日要处理真实场景应该直接读交易日历文件我在这里用去掉周末的近似值做演示。涨跌幅的计算核心是close(2:end) ./ close(1:end-1) - 1第一行用NaN占位后续计算相关性时 MATLAB 会自动忽略它。4.2 把每一条评论聚合成每日情绪指数评论情感是逐条的行情是逐日的两者要比较必须先把评论聚合到日粒度。这里我定义的情绪指数是某一天所有评论中积极评论的比例也就是用sentiment 0的条数除以当天总条数。这个指数比平均置信度更直观也和“看多比例”的概念吻合。% 把评论时间截取到日 comments.comment_date datetime(comments.time, InputFormat, yyyy-MM-dd HH:mm); comments.comment_date dateshift(comments.comment_date, start, day); % 只保留情感标签为积极的评论 % 0积极, 1消极, 2中性 comments.positive (comments.sentiment 0); % 按日期分组每天评论总数、积极评论数 g findgroups(comments.comment_date); daily_count splitapply(numel, comments.comment_date, g); daily_positive splitapply(sum, comments.positive, g); % 情绪指数 积极评论 / 总评论 daily_sentiment table(unique(comments.comment_date), daily_count, daily_positive, ... daily_positive ./ daily_count, ... VariableNames, {Date, Count, Positive, SentimentIndex});这步的逻辑用findgroups按日期分组再分别统计每天的评论总数和积极评论数最后算出比例。这里有个细节注释里要写清楚情感标签的取值含义尤其 2 代表中性——中性评论在计算比例时被直接丢弃因为中性既不能反映看多也不能反映看空硬塞进比例反而会拉低敏感度。有些做法是把中性评论作为分母的一部分我倾向于不算它这样情绪指数的含义更干净。4.3 对齐涨跌数据并计算相关性用 innerjoin 保证日期一致聚合完成后把情绪指数表和行情表做内连接只保留两边都存在的日期。% 按日期内连接自动丢弃只有评论没有行情、或只有行情没有评论的日子 merged innerjoin(daily_sentiment, prices, Keys, Date); % 当日相关性情绪指数 vs 当日涨跌幅 [r, p] corrcoef(merged.SentimentIndex, merged.pct_change); fprintf(当日情绪与涨跌幅: r%.3f, p%.3f\n, r(1,2), p(1,2));innerjoin是这里最关键的一步。它按Date变量精确对齐两个表只保留两边都有的日期这样评论日和交易日不重叠的脏数据会被自动剔除。corrcoef返回的r(1,2)是相关系数p(1,2)是对应的 p 值。到这里“评论和涨跌有没有关系”已经有了第一个量化答案。但要注意当日相关性是混在一起的——当天的评论包含了当天的盘中信息这种“当日关系”更多是同步验证而不是预测。所以下一步要做滞后分析看前一天的评论情绪对今天的涨跌幅有没有解释力。我会在第 4 章末尾和最后一章展开。5. 避坑与常见问题五个实际翻车记录5.1 评论量骤降甚至请求被拒单 IP 高频翻页触发限流现象爬虫跑到第 40 页左右单页评论数从 20 条突然掉到 0 条再往后请求返回 403。原因短时间连续翻页即使每页间隔 1 到 3 秒突然的连续请求仍会被站点识别为脚本行为。解决把随机间隔拉长到 3 到 8 秒并在遇到 403 后强制等待 60 秒再继续有条件的话准备两个 IP 轮换。从那以后我把“每 50 页停一次、停 5 分钟”写成固定习惯反爬问题基本绝迹。5.2 百度 AI 接口报错 QPS 超限免费配额远比你想的低现象批量打分跑到两百条左右请求开始返回“QPS 超限”或 HTTP 429。原因免费配额的每秒请求数上限很低代码里逐条循环没有做限速瞬时流量还是太高。解决在循环里固定加time.sleep(0.15)把频率降到每秒 6 条左右如果还报错直接降到每秒 3 条。后来我加了一个全局计数器每 10 秒最多发 50 个请求从根上杜绝超限。5.3 MATLAB 读中文 CSV 乱码编码不统一是在哪一步引入的现象注释字段全是乱码或者开头有一个 “鈥媤” 之类的字符。原因CSV 在某个环节被改了编码——最常见的是爬虫阶段没指定encodingutf-8或者中间用 Excel 打开并另存过编码被转成了 ANSI。解决从爬虫写出那一刻就强制encodingutf-8MATLAB 读取时再指定EncodingUTF-8中间绝不让任何编辑软件碰这个文件。如果乱码已经发生只能回到原始数据重新生成 CSV没有后悔药。5.4 相关系数永远接近 0很大概率是日期对齐出了问题现象不管怎么换股票、换时间窗口当日情绪和涨跌幅的相关系数始终在 -0.05 到 0.05 之间波动。原因两个最可能的坑——评论时间用的是自然日行情日程用的是交易日评论在周六、周日的部分直接错位或者评论时间是字符串MATLAB 的datetime转换失败导致所有日期变成 NaT。解决用平台提供的交易日历文件对齐而非简单剔除周末转换datetime后先sum(isnat(comments.comment_date))检查解析成功了多少再继续后面的计算。5.5 算出来相关性高得不敢信可能碰到了数据泄漏现象某次训练结果相关性高达 0.7换了时间窗口还是很高但稍微换一只股票就崩到 0。原因评论时间混进了当天盘中信息。比如“早盘大跌午后反弹”这类评论发生在交易日 14:00它反映的是当天的价格信息用一个包含盘中评论的情绪指数去和当天涨跌幅做相关本质是拿已经发生的事解释已经发生的事不是真正的预测关系。解决把评论按时间分成盘中评论和盘后评论预测任务只用 T 日 15:00 收盘之后到 T1 日开盘之前的评论去解释 T1 日的涨跌幅。只有这样“评论情绪领先涨跌”才成立。6. 进阶给结论加一道置换检验的保险6.1 核心问题你怎么知道相关系数不是碰巧出来的就算滞后的相关性显著也不能直接认定“情绪领先涨跌”。情绪指数本身是序列数据相邻日期有很强的自相关普通 t 检验的自由度会被高估p 值容易偏小。我接下来的习惯是任何相关系数结论出来之后强制跑一遍置换检验再决定要不要相信。置换检验的思路很直观把情绪指数的顺序随机打乱上千次每次重新计算相关系数看真实相关系数在随机分布里落在什么位置。如果真实的相关系数比 95% 的随机值都极端那说明它不是稀疏偶然才值得进一步研究。6.2 用 MATLAB 实现一次 1000 次的置换检验% 假设 merged 已经按日期对齐x 是情绪指数y 是涨跌幅 x merged.SentimentIndex; y merged.pct_change; n length(x); % 真实的滞后相关性lag1昨天情绪 vs 今天涨跌 y_lag y(2:end); x_lag x(1:end-1); r_real corrcoef(x_lag, y_lag); r_real r_real(1,2); % 置换检验随机打乱x_lag重算相关系数 rng(42); perm_r zeros(1000, 1); for i 1:1000 perm_x x_lag(randperm(n-1)); tmp corrcoef(perm_x, y_lag); perm_r(i) tmp(1,2); end % p值 随机相关系数绝对值超过真实值的比例 p_value mean(abs(perm_r) abs(r_real)); fprintf(真实 r%.3f, 置换检验 p%.3f\n, r_real, p_value);代码逻辑分三步先计算真实的滞后相关系数再用randperm打乱情绪序列的顺序重新计算 1000 次相关系数最后统计随机结果超过真实结果的比例。rng(42)是固定随机种子保证任何人在任何机器上跑出来的结果都一样。置换检验的 p 值解读和普通 p 值一样小于 0.05 说明真实相关在随机分布里处于比较极端的尾部不像是纯随机波动。但要注意置换检验打乱的是评论之间的相对顺序它不能消除评论数据本身的偏差——比如某段时间评论量暴增、样本分布不均匀这些还是要靠前面说的时间对齐和样本外验证来控制。6.3 一个更稳健的验证习惯分状态看子样本置换检验通过后我还会再做一个简单分组把涨跌幅按绝对值高低分成大涨、大跌、平常三组比较三组的情绪指数均值有没有显著差异。做法是 MATLAB 的findgroups加splitapply逻辑与第 4 章一致。这一步能回答“是不是只有极端行情时评论情绪才有解释力”比单一相关系数更有说服力。这套链路走到这里从评论采集、情感打分、MATLAB 对齐到显著性验证就闭环了。说实话真正把情绪和涨跌的稳健关系做出来的人很少更多时候跑出来的结果都是“不显著”。这可能才是这个课题最真实的结论。但至少做完这套验证你能清晰地知道自己得到的是信号还是噪声。我后来每做一个情绪类分析都会强制走一遍日期对齐自检和置换检验这两步救了我不少次。希望帮到你。本文还有配套的精品资源点击获取