我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

Python爬虫+百度AI+MATLAB:股票评论情绪分析与涨跌预测实战

Python爬虫+百度AI+MATLAB:股票评论情绪分析与涨跌预测实战 简介这份资源面向对量化分析与文本挖掘感兴趣的学习者围绕股票评论与股价涨跌之间的关联展开整合了Python爬虫采集、百度AI情感分析与MATLAB数据处理三条技术路线适合具备一定Python与数据分析基础、希望完成一个完整小项目实战的读者参考。压缩包共10个文件约1.71MB包含2个py脚本分别负责评论抓取与情感分析1个m脚本用于时间序列处理另有csv与xlsx数据文件承载评论及情感结果以及4张png图表呈现股价走势与情感分布。已有6466人学习下载说明该案例在同类项目中具有一定参考热度。资源的价值在于把数据采集、情感标注、统计建模到可视化呈现串成一条可复用的分析链路读者可据此理解评论情绪如何量化、如何与股价序列对齐比较并借鉴其数据处理与图表呈现思路用于课程作业、个人研究或投资情绪分析的入门实践。1. 从一条“评论能不能预测涨跌”的疑问说起做量化方向的同学大多有过这个念头股吧、论坛里每天几千条帖子情绪明显偏热的时候第二天股价是不是更容易冲高这个想法听起来像玄学但拆开看其实是一条完整的数据链路——采集评论、做语义打分、对齐交易日、统计相关性。这套方案要解决的就是把这条链路跑通并且可复现用 Python 爬虫抓取股票评论调用百度 AI 的语义接口把文本转成情绪分再用 MATLAB 做时间序列对齐和涨跌相关性分析。它适合两类人一是要做毕设或课程设计、需要一套从采集到分析完整闭环的学生二是想快速验证“舆情因子”有没有增量信息的从业者。整套东西不追求高频交易级别的精度追求的是链路清晰、每一步都能解释、结论能站得住。2. 需求拆解与技术选型为什么是 Python 采集 百度 AI 打分 MATLAB 分析先把“评论和涨跌有没有关系”翻译成工程语言。它其实是三个子问题第一评论从哪来、怎么稳定拿到第二一段中文评论的情绪怎么变成可计算的数值第三评论是按自然日产生的股价是按交易日走的两者怎么对齐才能做统计。三个子问题对应三段技术栈选型不是拍脑袋而是各自解决一个明确约束。2.1 三段链路的职责边界Python 负责采集和清洗原因是请求库、解析库、反爬处理生态最全写起来快调试成本低。百度 AI 负责语义分析原因是中文情绪分类自己训模型需要标注数据而现成接口对口语化、带表情、带黑话的股吧文本鲁棒性明显好于通用词典法。MATLAB 负责数据处理和统计建模原因是时间序列对齐、相关性检验、回归和绘图在 MATLAB 里几行就能出结果做毕设答辩时图表也好看。这里有个容易翻车的认知不要把三段写成三个互不相干的脚本靠手工导 CSV 串起来。我一般会约定一个中间格式Python 输出date, stock_code, comment, sentiment_score, confidence这样的宽表MATLAB 只认这个格式。这样任何一段出问题都能单独替换不用推倒重来。2.2 评论采集的字段设计与翻页策略采集阶段最容易被忽略的是字段设计。只存评论文本是不够的后面做对齐时你会后悔。通常的做法是至少保留评论发布时间精确到分钟、所属股票代码、评论正文、点赞数、作者标识脱敏后、抓取时间戳。发布时间决定它归到哪个交易日点赞数可以用来做加权抓取时间戳用于排查数据缺口。翻页策略上股吧类页面一般是页码参数或游标参数。页码翻页简单但容易在数据更新时重复或漏抓游标翻页更稳。下面是一个带重试和去重的采集骨架参数都做了外置import time import hashlib import requests from requests.adapters import HTTPAdapter # 会话复用 重试避免频繁建连被限流 session requests.Session() session.mount(https://, HTTPAdapter(max_retries3)) HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Accept-Language: zh-CN,zh;q0.9, } def fetch_page(stock_code, page, page_size50, sleep1.5): 抓取单页评论返回结构化列表 url https://example-forum-api.com/comment/list params {code: stock_code, page: page, size: page_size} resp session.get(url, paramsparams, headersHEADERS, timeout10) resp.raise_for_status() items resp.json().get(data, {}).get(list, []) result [] for it in items: text (it.get(content) or ).strip() if len(text) 5: # 过滤“顶”“沙发”这类无信息短评 continue result.append({ stock_code: stock_code, publish_time: it.get(createTime), content: text, like_count: it.get(likeCount, 0), fingerprint: hashlib.md5(text.encode(utf-8)).hexdigest(), }) time.sleep(sleep) # 主动降速别把对方打挂 return result这段代码的关键点有三个。fingerprint用正文做 MD5是为了跨页去重——同一只股票的热帖会在多页重复出现不去重会让情绪分被少数帖子带偏。sleep参数控制请求间隔1.5 秒是经验值太激进会触发限流太保守采集效率低。raise_for_status让 HTTP 错误直接抛出配合HTTPAdapter的重试网络抖动不会静默丢数据。字段里保留like_count是为了后面做加权情绪一条高赞帖和一条零赞帖对市场情绪的代表性显然不同。2.3 百度 AI 语义接口的调用与限流控制语义分析这一步接口返回的一般是情绪极性正向/负向/中性加置信度。真正要落库的不是“正向”这个字符串而是一个连续分数方便后面做相关分析。通常的做法是把极性映射成 1 / 0 / -1再乘以置信度得到sentiment_score ∈ [-1, 1]。这样一条“非常看多”的帖子得 0.9一条“略微看空”的得 -0.4粒度比三分类细。import json import time import requests def analyze_sentiment(text, token, max_retry3): 调用语义接口返回 [-1,1] 的情绪分与置信度 url https://aip.example.com/rpc/2.0/nlp/v1/sentiment_classify payload {text: text[:500]} # 接口一般有长度上限截断保护 for attempt in range(max_retry): try: resp requests.post( url, params{access_token: token}, datajson.dumps(payload, ensure_asciiFalse).encode(utf-8), headers{Content-Type: application/json}, timeout8, ) data resp.json() item data[items][0] polarity {positive: 1, negative: -1, neutral: 0} score polarity.get(item[sentiment], 0) * item[confidence] return round(score, 4), item[confidence] except (KeyError, IndexError, requests.RequestException): time.sleep(2 ** attempt) # 指数退避接口偶发超时很常见 return 0.0, 0.0 # 多次失败给中性分不污染整体这里有两个设计取舍值得说清楚。截断到 500 字是因为长帖后半段往往是复读和情绪宣泄对极性判断贡献不大还能省调用量。失败返回 0 分而不是丢弃是为了保持样本量——一条评论分析失败就丢掉会让某些日期的样本骤减统计时出现空洞。指数退避的2 ** attempt让重试间隔是 1、2、4 秒避免接口抖动时连续打过去。提示接口有 QPS 和日调用量限制正式跑之前先用几百条做一次全量试跑估算总调用量和耗时别等跑到一半额度没了。2.4 MATLAB 侧的数据对齐与相关性建模MATLAB 这一段的难点不在算法在对齐。评论按自然日产生周末和节假日也有大量讨论但股价只在交易日变动。如果直接把周末评论算进周一情绪会被稀释如果直接丢掉周末评论又会损失信息。我一般用“前向填充 交易日聚合”的做法把每条评论按发布时间归到“下一个交易日”也就是周五收盘后到周一开盘前的评论都算周一的预期情绪这样逻辑上更贴近“盘前情绪影响当日走势”。% 读取 Python 输出的宽表按交易日聚合情绪 T readtable(sentiment_wide.csv); T.publish_time datetime(T.publish_time, InputFormat, yyyy-MM-dd HH:mm:ss); % 交易日历示例从行情数据里取真实交易日 tradeDays readtable(trade_calendar.csv); tradeDays.date datetime(tradeDays.date, InputFormat, yyyy-MM-dd); % 每条评论映射到其后的第一个交易日 T.trade_date NaT(height(T),1); for i 1:height(T) idx find(tradeDays.date dateshift(T.publish_time(i),start,day), 1); if ~isempty(idx) T.trade_date(i) tradeDays.date(idx); end end % 按交易日聚合均值情绪 加权情绪 评论量 G groupsummary(T, trade_date, {mean,sum}, sentiment_score); G.weighted splitapply((s,w) sum(s.*w)/sum(w), ... T.sentiment_score, T.like_count1, findgroups(T.trade_date)); % 与次日涨跌幅做 Spearman 相关不假设线性 ret readtable(daily_return.csv); M innerjoin(G, ret, Keys,trade_date); [rho, pval] corr(M.mean_sentiment_score, M.next_day_return, Type,Spearman); fprintf(情绪与次日收益 Spearman rho%.3f, p%.4f\n, rho, pval);这段代码里几个参数值得解释。like_count1是加权时的平滑避免零赞帖权重为 0 被完全忽略。用 Spearman 而不是 Pearson是因为情绪分和收益率的关系大概率不是线性的极端情绪日的影响会被 Pearson 放大。next_day_return而不是当日收益是因为盘前和盘中评论对当日的影响已经被价格消化真正有预测意义的是次日。innerjoin保证只保留两边都有的交易日避免缺失值干扰检验。3. 总体架构与模块划分四层结构怎么落地链路讲清楚之后要把散落的脚本收成一套能交付的结构。毕设答辩时老师最常问的一句是“你的系统分几层、每层干什么”答不上来就很被动。这套方案按四层划分采集层、语义层、存储层、分析层层与层之间只通过约定的数据格式通信。3.1 四层架构与数据流采集层负责请求调度、去重、断点续爬输出原始评论表。语义层负责批量调用接口、失败重试、分数映射输出带情绪分的评论表。存储层用一张主表加若干维表主表是评论粒度维表是股票信息和交易日历。分析层读主表做聚合、对齐、统计和绘图。数据流是单向的采集 → 语义 → 存储 → 分析不允许分析层回头去调采集层否则一处改动全链路重跑。这种分层的好处是替换成本低。比如语义接口换了供应商只改语义层存储和分析不动比如想加一个“评论长度”特征只在采集层加字段下游按需读取。我见过不少同学把所有逻辑塞进一个 notebook跑到一半内存爆了连哪一步出的问题都定位不了这就是没分层的代价。3.2 目录结构与配置管理工程目录建议按职责分而不是按文件类型分。下面这个结构是我常用的直接照搬也能用stock_sentiment/ ├── config/ │ ├── settings.yaml # 接口地址、token、限流参数 │ └── stocks.csv # 股票池与代码映射 ├── crawler/ │ ├── fetch.py # 翻页采集 │ └── dedup.py # 指纹去重 ├── nlp/ │ ├── analyze.py # 语义接口封装 │ └── batch_runner.py # 批量调度与断点 ├── storage/ │ ├── schema.sql # 建表语句 │ └── loader.py # 入库与校验 ├── analysis/ │ ├── align.m # 交易日对齐 │ └── correlation.m # 相关与回归 └── output/ ├── figures/ # 图表 └── reports/ # 统计结果配置外置是血泪经验。token、接口地址、限流间隔、股票池这些一变就要改代码的东西全部放settings.yaml代码里只读配置。这样换一只股票、换一个接口地址不用碰逻辑代码也避免了把密钥硬编码进脚本再传到公开仓库的尴尬。3.3 数据库表结构设计存储层用关系库就够评论量在百万级以内单机完全扛得住。核心是三张表评论主表、股票维表、交易日历表。主表的设计要点是把“原始文本”和“分析结果”分开存分析结果可以重跑覆盖原始文本只写一次。CREATE TABLE comment_raw ( id BIGINT PRIMARY KEY AUTO_INCREMENT, stock_code VARCHAR(10) NOT NULL, publish_time DATETIME NOT NULL, content TEXT NOT NULL, like_count INT DEFAULT 0, fingerprint CHAR(32) NOT NULL, crawl_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_fp (fingerprint, stock_code), KEY idx_time (stock_code, publish_time) ); CREATE TABLE comment_sentiment ( comment_id BIGINT PRIMARY KEY, sentiment_score DECIMAL(6,4), -- [-1,1] confidence DECIMAL(6,4), model_version VARCHAR(32), update_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (comment_id) REFERENCES comment_raw(id) );uk_fp唯一索引把去重下沉到数据库层比在 Python 里维护集合更可靠程序中断重启也不会重复入库。idx_time支撑按股票和时间范围查询分析层聚合时走这个索引。情绪分单独一张表是为了接口升级或换模型时可以整表重算原始评论不受影响。model_version记录用的是哪版接口答辩时被问到“结果可复现吗”这个字段就是答案。4. 关键实现细节从采集到统计的四个硬骨头架构搭好只是骨架真正决定结论可信度的是几个实现细节。这一章挑四个最容易出问题、也最影响结果的地方展开反爬与合规采集、情绪分的日聚合口径、涨跌标签的定义、以及统计显著性的判断。4.1 采集稳定性限流、断点与增量采集跑一整晚是常态中途断掉很常见。断点续爬的做法是记录每只股票已抓到的最大发布时间下次从该时间之后继续。增量采集则是在每次运行前先查库里该股票的最新publish_time只抓更新的部分。两者结合既不会重复抓历史也不会因为中断而从头再来。限流方面单 IP 的请求频率要有上限通常的做法是给每只股票之间加随机间隔而不是固定间隔。固定间隔容易被识别出机器节奏随机间隔更像人。下面是一个带断点记录的调度片段import random import yaml with open(config/settings.yaml, encodingutf-8) as f: cfg yaml.safe_load(f) def crawl_stock(stock_code, last_time): page 1 while True: rows fetch_page(stock_code, page, cfg[page_size], cfg[sleep]) if not rows: break fresh [r for r in rows if r[publish_time] last_time] save_to_db(fresh) if len(fresh) len(rows): # 已翻到历史数据停止 break page 1 time.sleep(random.uniform(1.0, 2.5)) # 随机间隔last_time从数据库查fresh过滤掉已入库的部分len(fresh) len(rows)是停止条件——一旦某页出现旧数据说明已经翻过了增量边界继续翻只是浪费请求。随机间隔用uniform(1.0, 2.5)比固定 1.5 秒更自然。4.2 情绪聚合口径均值、加权与分歧度日度情绪不是一个数而是一组数。均值反映整体倾向加权均值反映“有影响力的声音”标准差反映分歧程度。很多同学只算均值结果发现相关性很弱就放弃了其实分歧度往往比均值更有信息量——多空吵得越凶波动越大这个规律在实证里经常出现。我一般会同时算三个指标mean_score简单均值、weighted_score按点赞加权、std_score情绪标准差。在 MATLAB 里用groupsummary一次就能出前面 2.4 的代码已经覆盖。要注意的是评论量本身也是一个特征comment_count突然放大往往对应事件驱动把它作为控制变量放进回归能减少遗漏变量偏误。4.3 涨跌标签的定义收益率还是方向“涨跌”这两个字看着简单定义不同结论可能完全相反。用当日收盘对前一日收盘算收益率是最常见的做法用次日开盘对当日收盘算更贴近“盘前情绪”的假设用方向标签涨/跌/平做分类则要处理“平”的阈值。我的建议是主分析用连续收益率辅助分析用方向标签两者结论一致才敢下判断。方向标签的阈值不能拍脑袋。A 股有涨跌停限制日常波动在 1% 以内很常见把 ±0.5% 以内都算“平”比较合理。阈值定太窄样本几乎全是涨或跌分类失去意义定太宽“平”占了大半同样没信息。这个阈值要在报告里写清楚答辩时被问到能答上来。4.4 显著性检验与多重比较跑出相关系数不等于有关系。样本量小的时候rho0.2 也可能 p0.05但换个时间段就消失了。稳妥的做法是分训练段和验证段前 70% 交易日估参数后 30% 看是否稳定。如果两段符号相反基本可以判定是噪声。还有一个坑是多重比较。如果你同时试了 5 只股票、3 种情绪口径、2 种收益定义一共 30 个组合总有几个“显著”的这是概率问题不是发现。通常的做法是对 p 值做 Bonferroni 校正把显著性门槛除以检验次数或者干脆只报预先设定的那一组主结果其余作为探索性分析标注清楚。5. 避坑与常见问题排查五个最容易翻车的地方这套链路我前后调过几轮下面五个问题几乎每次都会遇到按“现象 → 原因 → 解决”写清楚照着排查能省不少时间。5.1 情绪分全是 0 或全是中性现象跑完几千条评论sentiment_score大面积为 0相关性自然算不出来。原因通常是接口返回结构和你解析的字段对不上或者 token 失效返回了错误码而代码里的except把异常吞掉后统一返回了 0。解决先在analyze_sentiment里打印一次原始响应确认items结构和字段名把“接口报错”和“中性情绪”区分开前者应该抛异常或记日志不能静默变成 0 分。我一般会加一个失败计数器失败率超过 5% 就中止任务。5.2 评论时间和交易日对不齐现象MATLAB 里innerjoin之后样本量骤减或者某些日期情绪分明显异常。原因多半是时区或格式问题——接口返回的时间可能是 UTC也可能是字符串带毫秒datetime解析失败后变成NaT聚合时被静默丢弃。解决入库前统一转成东八区、统一格式解析失败的行单独落一张错误表不要直接丢。对齐前先统计一下NaT的比例超过 1% 就要回头查采集端。5.3 重复评论把情绪带偏现象某只股票某天的情绪分极端偏高点进去看是同一段话被转发了上百次。原因是只按正文去重没考虑转发和复制粘贴。解决指纹用“正文 作者 时间窗口”组合或者对同一作者在短时间内的相似文本只保留一条。更简单的做法是对情绪分做截尾把当日最高和最低各 1% 去掉再算均值极端值的影响就压下去了。5.4 相关性显著但方向反了现象算出来情绪越高、次日跌得越多和直觉相反。先别急着改结论检查是不是用了“当日收益”而不是“次日收益”——当日收益里已经包含了评论对价格的反向影响涨了才有人吹存在内生性。解决明确用次日收益做因变量并且把当日收益作为控制变量放进回归看情绪项的系数是否还显著。如果还是反的可能是样本期太短换更长的时间段再看。5.5 接口额度跑一半没了现象批量任务跑到中途大量失败日志里是额度超限。原因是没做调用量预估或者重试逻辑在额度耗尽时还在疯狂重试把剩余额度也耗光。解决跑之前用总评论数 / 单次批量上限估算调用次数和日额度对比重试只针对超时和 5xx额度类错误通常是特定错误码直接中止并告警不要重试。批量任务做成可断点续跑额度恢复后从断点继续。6. 验收与答辩怎么证明这套方案站得住验收这套东西核心不是“代码跑通了”而是“结论可信、过程可复现、边界说得清”。答辩时老师最爱问的三类问题——数据从哪来、方法为什么这么选、结论有多大把握——都要在验收环节准备好答案。我一般按下面这张表逐项过一遍每项都有明确的通过标准不靠感觉。验收项检查内容通过标准数据完整性各股票评论量、时间覆盖目标区间无超过 3 个交易日的空洞去重有效性重复率统计去重后重复率低于 2%语义覆盖率成功分析占比失败率低于 5%失败样本有记录对齐正确性交易日映射抽查随机抽 20 条人工核对归属日期统计稳健性分训练/验证段两段相关系数符号一致可复现性配置与版本记录换机器按文档能重跑出同结果表格之外答辩时有两个技巧很实用。第一主动交代局限样本期多长、覆盖多少只股票、接口本身的准确率上限在哪这些说在前面比被问出来强。第二准备一张“反例图”故意展示一个相关性不显著的股票或时间段说明你知道结论不是普适的这反而增加可信度。评审最怕的是一上来就宣称“情绪能预测涨跌”最欣赏的是“在什么条件下、多大幅度上、有多少把握”。还有一个容易被忽略的点是复现文档。把settings.yaml的字段含义、每张表的字段说明、MATLAB 脚本的输入输出约定写成一页纸附在报告附录里。评审翻到这一页基本就认可你是按工程规范做的而不是拼凑出来的。从那以后我每次带这类“文本 行情”的交叉分析项目都强制先跑一遍小样本全链路——几十条评论、三五天数据从采集到相关系数完整走通再放大规模。小样本跑通能提前暴露 80% 的格式和对齐问题比跑到一半才发现字段对不上省太多时间。希望这套结构能帮到你照着改改就能变成你自己的方案。本文还有配套的精品资源点击获取
返回列表