Python爬虫+大模型+MATLAB:股票评论情绪分析与股价相关性实战 1. 项目缘起与整体设计思路最早动这个念头是因为身边做量化研究的朋友总在抱怨一件事每天收盘后翻遍各大财经社区的评论区想看看散户情绪到底偏多还是偏空结果翻了两个小时眼睛都花了最后还是凭感觉拍脑袋。我当时就想这套流程完全可以用代码串起来——评论区的文本抓下来交给模型打分再把分数和行情数据做相关性计算最后出一张能直接放进周报的图。整个链条听起来环节不少但拆开看每一步都不复杂难的是把它们顺畅地接在一起。这个项目的核心目标很明确把非结构化的股票评论文字转化成可量化、可统计、可验证的情绪指标。具体来说输入是某只股票在财经社区下的评论文本输出是一张情绪得分与股价走势的对照图外加一份相关系数表。适合谁来参考我认为有三类人比较对口一是做量化研究但懒得自己搭文本处理管线的朋友二是想给自己的交易策略加一个情绪因子的独立开发者三是单纯想练手爬虫AI数据分析全链路的学生或转行者。哪怕你只对其中某一个环节感兴趣也可以把对应部分单独拆出来用。为什么选这条技术路线我对比过几种方案。纯人工标注情绪当然最准但一天几百上千条评论人力成本扛不住而且主观偏差大。用传统词典法比如基于情感词典匹配正负词速度快但股票评论里充斥着利好出尽利空落地这票要起飞这类反讽和黑话词典法误判率很高。所以最终我选了大模型判分作为核心环节它在理解语境和反讽上明显更靠谱。至于相关性计算用MATLAB、出图用Excel纯粹是因为目标用户群体里这两样工具普及率最高交付出去别人能直接改不用再装一堆环境。整个流程我把它拆成四个阶段用一张表先理清楚每个阶段干什么、用什么工具、产出什么阶段核心任务主要工具产出物数据采集抓取股票评论区文本Python爬虫原始评论CSV情感判分逐条评论打情绪分大模型API带分数的评论表相关性计算情绪分与行情做相关MATLAB相关系数矩阵可视化出图生成对照图表Excel走势对照图这个拆法的好处是每个阶段解耦。爬虫挂了不影响判分判分换模型也不用动后面的代码。我踩过的坑是一开始图省事把所有逻辑写在一个脚本里结果调试的时候改一处崩三处后来老老实实按阶段拆成独立模块维护成本立刻降下来。提示阶段解耦不只是代码组织问题更重要的是数据格式要提前约定好。我在第一阶段就固定了输出CSV的列名和日期格式后面三个阶段全部按这个约定来读省了无数对齐字段的时间。2. 数据采集环节的实操要点2.1 目标页面分析与请求构造采集这一步核心是搞清楚评论数据到底藏在哪。财经社区的评论通常有两种加载方式一种是服务端渲染评论直接写在HTML里用常规的解析库就能抠出来另一种是前端异步加载评论通过接口返回JSON你得找到那个接口地址。我遇到的大部分社区属于后者所以思路是先抓接口再解析JSON比硬啃HTML稳得多。具体操作上打开浏览器的开发者工具切到网络面板筛选XHR或Fetch请求然后手动翻一页评论观察哪个请求的响应里带着评论内容。找到之后重点看三样东西请求URL、请求方法GET还是POST、以及请求头里有没有必须带的字段比如Referer、User-Agent、某些自定义token。把这些原样复制到代码里基本就能复现请求了。请求构造我一般用requests库配合一个会话对象保持连接。下面是一个通用的请求骨架参数需要你按实际接口替换import requests import time import random session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://example-community.com/stock/000001, Accept: application/json, text/plain, */* }) def fetch_comments(stock_code, page): url https://example-community.com/api/comments params { code: stock_code, page: page, page_size: 50, sort: time } resp session.get(url, paramsparams, timeout10) resp.raise_for_status() return resp.json()这里有几个细节值得说。分页参数一定要先手动翻几页确认清楚有的社区用page从1开始有的从0开始还有的用游标cursor而不是页码。page_size别贪大我试过设成200结果接口直接返回空设成50就正常说明服务端有上限。超时必须设不然网络一抖整个脚本就卡死。2.2 反爬应对与采集节奏控制采集环节最容易被忽视、也最容易翻车的就是节奏控制。很多人写完请求就直接for循环狂发结果没几十条就被限流甚至封IP。我的经验是把采集当成模拟真人浏览来设计而不是尽可能快地拉数据。具体做法有三条。第一请求间隔加随机抖动。固定间隔比如每秒一次反而容易被识别出机器特征我一般用random.uniform(1.5, 3.5)这样的随机区间。第二控制单次会话的采集量。一次别抓太多页抓个十几二十页就停一停隔几分钟再继续。第三准备好失败重试机制遇到超时或非200状态码退避几秒再重试重试超过三次就跳过这一页并记录下来。def fetch_with_retry(stock_code, page, max_retry3): for attempt in range(max_retry): try: data fetch_comments(stock_code, page) return data except Exception as e: wait (attempt 1) * 3 random.uniform(0, 2) print(f第{page}页第{attempt1}次失败: {e}, 等待{wait:.1f}秒) time.sleep(wait) return None采集节奏上我实测下来一个比较稳的参数组合是每页间隔2到4秒每抓10页休息30到60秒单只股票一次采集不超过500条评论。这个量级对于日频情绪分析已经够用了再多边际收益很低反而增加风险。注意采集到的评论里往往混着大量噪声比如纯表情、无意义的顶沙发、广告刷屏、重复灌水。这些在采集阶段可以先不管但一定要在CSV里保留原始文本清洗放到判分阶段之前做这样万一清洗规则有问题还能回溯。2.3 数据落盘与字段设计采集完的数据怎么存直接决定了后面几个阶段顺不顺。我强烈建议用CSV而不是数据库因为这个项目的规模单只股票几百到几千条用CSV完全够而且Excel能直接打开交付给不写代码的同事也方便。字段设计上我固定了这么几列comment_id评论唯一标识、stock_code股票代码、publish_time发布时间统一成YYYY-MM-DD HH:MM:SS格式、content评论文本、like_count点赞数可选但有用。其中publish_time的格式统一特别重要因为后面要和行情数据按日期对齐格式不一致会导致匹配失败。import csv def save_comments(comments, filename): with open(filename, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[ comment_id, stock_code, publish_time, content, like_count ]) writer.writeheader() for c in comments: writer.writerow(c)这里encodingutf-8-sig是个小技巧带BOM的UTF-8能让Excel直接正确识别中文不然打开就是乱码。这个坑我踩过不止一次后来养成习惯凡是给Excel用的CSV一律加-sig。3. AI情感判分的核心实现3.1 为什么用大模型而不是词典情感判分这一步是整个项目里技术含量最高、也最影响最终结果质量的一环。前面提过股票评论的语言特点决定了词典法不好使。我举个真实例子一条评论写这走势怕是要上天台了词典法看到上天可能判成正面但实际是极度悲观。再比如主力终于舍得拉一把了字面没有明显情感词但结合语境是偏正面的期待。这类需要语境理解的句子只有大模型能处理得比较准。大模型判分的另一个好处是可以自定义评分维度。我不满足于简单的正负二分而是让模型输出一个-1到1之间的连续分数-1代表极度悲观0代表中性1代表极度乐观。这样后续做相关性计算时信息量比二分类大得多。如果你还想更细可以让模型同时输出情绪强度和情绪类型比如恐慌、贪婪、观望不过维度一多判分稳定性和成本都会上升我建议先从单一情绪分做起。3.2 判分提示词的设计与调优判分准不准八成看提示词。我前后改了七八版总结出几个关键点。第一给模型明确的角色和任务边界别让它自由发挥。第二给出评分标准和示例尤其是边界情况比如纯提问、纯转发新闻怎么打分。第三要求模型只输出分数不要输出解释否则解析起来很麻烦。下面是我最终用的提示词模板效果比较稳定PROMPT_TEMPLATE 你是一个股票社区情绪分析助手。请对下面这条股票评论进行情绪打分。 评分标准 - 1.0极度乐观强烈看涨 - 0.5偏乐观温和看涨 - 0.0中性无明显情绪或纯信息陈述 - -0.5偏悲观温和看跌 - -1.0极度悲观强烈看跌 注意 1. 只输出一个数字范围-1到1保留两位小数 2. 不要输出任何解释文字 3. 遇到反讽、黑话要结合语境判断 4. 纯提问、纯转发新闻按0.0处理 评论内容{content} 分数这个模板里**只输出一个数字**这条约束非常关键。我早期没加这条模型动不动就回一段这条评论表达了……因此我给出0.3分解析的时候还得写正则去抠数字徒增麻烦。加上之后直接float()就能转干净利落。3.3 批量判分与结果校验批量判分的时候有两个问题必须处理并发控制和失败兜底。并发太高容易被限流太低又慢。我一般用线程池并发数控制在5到10之间配合重试机制。失败兜底的意思是某条评论判分失败比如超时、返回格式异常不能让它把整个流程卡住而是给个默认值我一般给0.0并记录下来事后可以单独复查。from concurrent.futures import ThreadPoolExecutor import re def score_comment(content): prompt PROMPT_TEMPLATE.format(contentcontent[:500]) try: resp call_llm_api(prompt) match re.search(r-?\d\.?\d*, resp) if match: score float(match.group()) return max(-1.0, min(1.0, score)) except Exception as e: print(f判分失败: {e}) return 0.0 def batch_score(comments, max_workers8): with ThreadPoolExecutor(max_workersmax_workers) as executor: scores list(executor.map(score_comment, comments)) return scores注意content[:500]这个截断评论一般不会太长截断到500字符既能覆盖绝大部分内容又能控制token消耗。另外max(-1.0, min(1.0, score))是防止模型偶尔输出超范围的分数做个钳位保护。判分完成后我强烈建议抽样人工校验。随机抽20到30条自己看一眼分数合不合理。如果发现系统性偏差比如整体偏正说明提示词需要调整。我做过一次校验发现模型对割肉套牢这类词判得偏轻后来在提示词里补了几个示例偏差就纠正过来了。提示判分结果一定要和原始评论存在同一张表里别分开存。后面做相关性分析时你需要按日期聚合情绪分如果分数和文本分离聚合时还得做一次join容易出错。4. MATLAB相关性计算与Excel出图4.1 数据对齐与日频聚合到了这一步你手上有两份数据一份是带情绪分的评论表一份是股票行情数据日期、开盘、收盘、成交量等。要做相关性计算第一步是把两份数据按日期对齐。评论是逐条的行情是逐日的所以得先把评论按天聚合算出每天的平均情绪分和评论数量。聚合逻辑很简单按publish_time的日期部分分组对情绪分求均值。但有个细节要注意——非交易日怎么办。周末和节假日没有行情数据但评论可能照样有。我的处理方式是非交易日的评论情绪归并到下一个交易日。这样既保留了信息又不会因为日期对不上而丢数据。% 读取评论数据 opts detectImportOptions(comments_scored.csv); opts.VariableNamingRule preserve; T readtable(comments_scored.csv, opts); % 提取日期部分 T.date dateshift(T.publish_time, start, day); % 按日期聚合情绪分 [G, dateGroups] findgroups(T.date); dailySentiment splitapply(mean, T.score, G); dailyCount splitapply(numel, T.score, G); % 组装成表 sentimentTable table(dateGroups, dailySentiment, dailyCount, ... VariableNames, {date, avg_sentiment, comment_count});MATLAB的findgroups和splitapply组合是处理分组聚合的利器比写循环清爽得多。dateshift函数把时间戳截断到天也是常用操作。4.2 相关系数的计算与解读数据对齐之后就可以算相关系数了。我一般算三个情绪分与当日涨跌幅的相关性、情绪分与次日涨跌幅的相关性、评论数量与成交量的相关性。第一个反映的是情绪和当天走势同步性第二个反映的是情绪有没有预测性第三个反映的是关注度和交易活跃度的关系。% 读取行情数据 priceTable readtable(price_data.csv); priceTable.date datetime(priceTable.date, InputFormat, yyyy-MM-dd); % 合并两张表 merged innerjoin(sentimentTable, priceTable, Keys, date); % 计算涨跌幅 merged.pct_change [NaN; diff(merged.close) ./ merged.close(1:end-1)]; % 计算相关系数 [r1, p1] corr(merged.avg_sentiment, merged.pct_change, Rows, complete); [r2, p2] corr(merged.avg_sentiment(1:end-1), merged.pct_change(2:end), Rows, complete); [r3, p3] corr(merged.comment_count, merged.volume, Rows, complete); fprintf(情绪与当日涨跌相关性: r%.3f, p%.3f\n, r1, p1); fprintf(情绪与次日涨跌相关性: r%.3f, p%.3f\n, r2, p2); fprintf(评论量与成交量相关性: r%.3f, p%.3f\n, r3, p3);解读相关系数时有几个经验值可以参考。r的绝对值在0.3以下算弱相关0.3到0.6算中等0.6以上算强相关。股票情绪和涨跌的相关性通常落在0.2到0.5之间能到0.4以上就算不错了。p值小于0.05说明统计显著但如果样本量很小比如只有二三十个交易日即使r看着不小p值也可能不显著这时候结论要谨慎。我实测下来情绪分与当日涨跌的相关性通常比与次日的高这符合直觉——评论大多是对当天走势的反应。但偶尔也会出现次日相关性更高的情况那可能说明市场情绪有一定的惯性或滞后反应值得进一步研究。4.3 Excel图表的制作与美化算完相关系数最后一步是出图。为什么用Excel而不是MATLAB直接画因为交付对象往往是不写代码的人Excel图表他们能直接改颜色、调坐标轴、加标注灵活性高。我的做法是MATLAB把合并好的数据导出成CSVExcel打开后做双轴折线图。具体操作把日期放X轴情绪分和收盘价各放一个Y轴次坐标轴这样两条量纲完全不同的曲线能画在一张图上。情绪分用折线收盘价用面积图或折线视觉上区分开。再叠加一条情绪分的移动平均线比如5日均线能过滤掉单日的噪声趋势更清晰。Excel出图有几个美化技巧值得分享。坐标轴标签别用默认的系列1系列2改成情绪分收盘价读者一眼就懂。网格线调淡一点别喧宾夺主。数据点标记如果太密就关掉只保留线条。图例放右上角或底部别压在数据上。这些细节看着小但直接决定了图表能不能放进正式报告。注意Excel打开CSV时日期列有时会被识别成文本导致X轴排序错乱。解决办法是在Excel里选中日期列用分列功能强制转成日期格式或者干脆在MATLAB导出时就写成Excel格式writetable直接写.xlsx省去转换麻烦。5. 常见问题与排查技巧实录5.1 采集与判分阶段的典型故障这个项目跑下来遇到的问题五花八门我挑几个最有代表性的整理成速查表方便你对号入座。问题现象可能原因排查与解决请求返回403或空数据请求头缺失或过期重新抓包更新Referer和token采集到一半突然全失败触发限流加大间隔换时间段重试判分结果全是0.0API调用失败或解析异常检查API返回内容确认正则匹配判分整体偏正/偏负提示词示例不足补充边界示例重新校准日期对不齐时间格式不一致统一转成YYYY-MM-DD再聚合相关系数异常高数据泄漏检查是否用了未来数据其中数据泄漏这个坑最隐蔽也最致命。什么叫数据泄漏举个例子你算情绪分与次日涨跌的相关性但如果情绪分里混入了次日才发布的评论那算出来的相关性就是假的因为用了未来信息。避免方法是聚合每日情绪时严格按评论的发布时间截断只用到当天收盘为止的评论。这个细节不注意回测结果会好得离谱实盘一用就亏。5.2 提升结果可信度的几个经验跑通流程只是第一步让结果可信才是难点。我总结了三条经验。第一样本量要够。二三十个交易日的数据算出来的相关系数波动极大至少得攒够两三个月的交易日数据再下结论。第二做分组验证。把数据按时间段切成两半分别算相关性如果两段结果差异很大说明关系不稳定不能当策略依据。第三控制混杂因素。情绪和涨跌的相关性可能被大盘整体走势带偏最好把个股涨跌减去大盘涨跌算超额收益再算相关性结果更干净。还有个小技巧情绪分的分布要先看一眼。如果大部分评论都集中在0附近说明这个社区的用户发言比较理性中性情绪因子的区分度就低如果分布很分散两头都有不少极端值那这个因子的信息量就大。用histogram画一下分布心里就有数了。5.3 流程自动化与后续扩展如果你打算长期跑这套流程建议把它做成定时任务。每天收盘后自动采集、判分、更新数据、重算相关性、刷新图表。用系统的定时任务工具比如Linux的cron或Windows的任务计划挂一个入口脚本就行。入口脚本里做好日志记录哪天哪步失败了翻日志一目了然。后续扩展方向我想到几个。一是多股票对比把同一套流程套到多只股票上横向比较哪只股票的情绪因子更有效。二是情绪因子的回测把情绪分做成一个简单的择时信号比如情绪分高于某阈值就持有看看历史收益如何。三是换更细的判分维度比如区分理性分析和情绪宣泄因为后者的噪声更大。这些扩展都不需要推翻现有框架在对应阶段加模块就行。我个人在实际操作中的体会是这套流程最大的价值不在于某个环节多精妙而在于它把一件原本靠感觉的事变成了可重复、可验证的流程。哪怕相关系数只有0.2只要它稳定显著就比拍脑袋强。最后再分享一个小技巧——每次调整提示词或清洗规则后别急着看最终相关性先抽样看判分结果有没有变好因为最终相关性受太多因素影响用它来调中间环节反馈太慢。