ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python上市公司舆情与股票相关性分析:从爬虫到相关性计算

Python上市公司舆情与股票相关性分析:从爬虫到相关性计算 简介一套面向金融数据分析与舆情挖掘场景的 Python 项目源码用于采集上市公司新闻文本、计算舆情评分并与股价数据做相关性分析最后用图形化展示结论。项目共 10 个文件压缩包仅 136KB5 个 .py 脚本分别完成数据采集、舆情评分、股票数据获取、相关性计算等核心逻辑2 个 .xlsx 存放舆情得分与股价数据2 个 .txt 包含建表语句和爬取异常时的排错提示1 个 .doc 为使用说明文档。代码带有详细注解便于二次开发并附运行截图方便对照验证文档中还提示了网站结构变动后如何自行修改解析部分降低上手门槛。已有 6300 人学习下载适合具备一定 Python 基础、想通过完整案例掌握舆情分析与股票相关性分析流程的读者可复用爬虫模块与评分逻辑快速跑通“文本采集—舆情评分—股价对照—相关性计算—可视化展示”全流程。1. python上市公司网络舆情与股票相关性分析先把流程走通再谈预测python上市公司网络舆情与股票相关性分析这个项目名听起来像是金融工程的活儿实际上拆开看就是一条很朴素的流水线用爬虫把上市公司的新闻抓下来按情感词打分再拿舆情分和股票日线数据做相关性计算最后用图形把结果画出来。我刚拿到这份源码的时候第一反应是五六个脚本能做出什么跑完一遍倒是意外地完整——从数据采集、入库、评分到分析输出中间一个环节都没缺。适合论文缺实证数据、想用真实新闻和行情做相关性验证的人也适合刚入门量化、想看看舆情数据怎么和股票数据对齐的人。这一篇就把脚本职责、运行顺序和数据处理的几个关键坑写清楚。2. 舆情评分是怎么算出来的新闻采集、情感打分与聚合口径2.1 数据源选型为什么新闻文本比股吧评论更适合做评分做舆情评分第一步是选语料来源。源码里提供了 baidu.py 和 sina.py 两个采集脚本分别对应百度新闻搜索和新浪财经新闻频道而不是雪球、股吧这类社区文本。这个选择在实际处理过语料之后会觉得很合理股吧文本口语化严重谐音字、梭哈站岗这类黑话满天飞情感词典很难覆盖而且一条帖子几十个字里塞满了感叹号和表情符号清洗成本远高于新闻标题。新闻文本则规矩得多标题和摘要基本都是陈述句负面信息里亏损违规处罚跌停这类词命中率很高。另一个原因是新闻有发布时间和来源URL方便按日期聚合到股票自然日的维度后面才能和行情数据对齐。社区帖子的发布时间和回复热度虽然有但文本噪声会把评分结果搅乱你会发现同一只股票在股吧里永远处于极度负面状态因为骂的人多但股价不一定跌。2.2 baidu.py 和 sina.py请求构造、解析规则与网站改版埋下的雷两个采集脚本的结构基本一致构造搜索请求、解析HTML、抽取标题与发布时间入库或落盘。核心逻辑示意如下。import requests from bs4 import BeautifulSoup HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } def fetch_baidu_news(keyword: str, page: int 1) - list: url https://www.baidu.com/s params {wd: keyword, pn: (page - 1) * 10} resp requests.get(url, paramsparams, headersHEADERS, timeout10) resp.encoding utf-8 soup BeautifulSoup(resp.text, html.parser) items [] for result in soup.select(.result): title_node result.select_one(h3 a) if not title_node: continue items.append({ title: title_node.get_text(stripTrue), url: title_node.get(href, ), }) return items这段代码里三个参数值得注意。timeout 设成 10 秒避免单个请求卡死整个采集任务resp.encoding 手动指定 utf-8因为百度搜索页面在部分环境下会被 requests 错误识别成 gbk 导致乱码select(.result) 是当前搜索结果里新闻条目的容器选择器页面改版时首先失效的就是这里。源码包里那个 .txt 文件特别注明若其他步骤都正确的情况下爬取不了数据可能是网站有变动可自行修改解析部分说的就是这段。我建议拿到源码后先跑一个最小测试打印 items 的长度和第一条 title确认解析器没失效再全量跑。否则会出现程序不报错、脚本正常结束、但结果表里全是空数据的诡异现象。2.3 score.py 的评分逻辑情感词表、强度加权与归一化新闻抓下来之后score.py 负责把文本变成分。源码用的方法是构建式情感词典——在脚本里硬编码两组词一组正面、一组负面然后对每条新闻的标题和摘要做分词匹配。核心逻辑示意如下。import jieba POS_WORDS {增长, 盈利, 突破, 中标, 回购, 增持} NEG_WORDS {亏损, 违规, 处罚, 跌停, 减持, 诉讼} def score_news(text: str) - float: words jieba.lcut(text) pos sum(1 for w in words if w in POS_WORDS) neg sum(1 for w in words if w in NEG_WORDS) total pos neg if total 0: return 50.0 # 中性没有命中任何情感词 return round(50 (pos - neg) / total * 50, 2)这里的设计逻辑是映射到 0 到 100 的区间50 分代表中性高于 50 偏正面低于 50 偏负面。分母用 pos neg 而不是总词数是为了消除标题长短的影响——一条 20 个字的标题和一条 50 个字的摘要放在同一个尺度上比较。如果你想让负面消息的惩罚更狠可以把公式改成 50 (pos - neg) / total * 60极端情况下分数波动更大但建议先跑一遍原始公式再根据结果调整强度。评分之后还要做聚合操作因为最终要拿每天一个舆情分和当日收益率对齐。常见做法是按股票代码和新闻日期分组统计新闻条数、正负面词次数、平均分或加权分。加权逻辑可以用新闻条数做权重新闻越多说明市场关注度越高舆情信号越强单纯平均分会把一条小新闻和新闻刷屏两种情况混为一谈。2.4 score.xlsx 的产出口径一条舆情记录怎么按天聚合score.py 跑完会输出 score.xlsx这就是后面相关性分析的数据基础。文件字段大致是这样字段名含义举例stock_code股票代码600519news_date新闻发布日期2024-11-20news_count当天新闻条数7pos_count正面词出现总次数3neg_count负面词出现总次数1sentiment_score当日舆情评分62.5常见的问题是聚合时要不要区分新闻发布时间到底是当天凌晨还是 15:00 收盘后。这里的口径处理会直接影响相关性结论我放到第 5 章的避坑部分专门讲。源码里用的是自然日聚合如果你要更严格一点可以把 15:00 之后的新闻归到下一个交易日。3. 股票数据与相关性计算把舆情评分和行情对齐到同一根时间轴上3.1 stock.py 的行情采集复权方式决定收益率算得对不对舆情分有了接下来是对照组——股票行情数据。源码里 stock.py 负责把目标公司的日线数据拉下来落盘成 share.xlsx。这里第一个决策点是复权。常见做法是用后复权数据因为后复权的价格序列是连续的计算历史区间的收益率不会因为中间发生过分红除权而出现跳空前复权价格受最近一次除权影响早期价格会被压得失真。我一般会在 stock.py 里加一个参数控制复权方式默认后复权同时把成交量、收盘价、涨跌幅一起拿全。如果你用的数据源不支持复权因子退而求其次的办法是只用涨跌幅pct_chg字段这个字段本身已经包含了除权除息修正算日收益率时不需要再做任何变换。注意源码只跑了单只股票如果你想换成多只做横向对比需要在外层循环里逐个请求不要试图一次拿完整个板块再拆分接口层通常会限制单次返回条数。3.2 先转收益率再算相关性价格序列是典型的非平稳数据拿到价格之后不能直接用收盘价和舆情分做 Pearson 相关系数。原因是价格序列是非平稳的——长期趋势、市场整体涨跌都会让两个序列同时走高或走低算出来的相关系数虚高得离谱。最经典的例子是随便找两只毫无业务关联的股票因为大盘同步上涨价格序列的相关性可能高达 0.8 以上但你拿这个数字去解释舆情驱动股价完全站不住脚。正确做法是先把价格转成收益率序列再用收益率和相关变量计算相关性。这一步在 analysis.py 里对应如下核心逻辑。import pandas as pd from scipy.stats import pearsonr df pd.merge(score_df, stock_df, left_onnews_date, right_ontrade_date) df df.sort_values(news_date).reset_index(dropTrue) # 用 pct_change 计算当日相对前收盘的百分比变动 df[daily_return] df[close].pct_change() # 去掉首行 NaN 和停牌导致的空值 df df.dropna(subset[daily_return, sentiment_score]) r, p_value pearsonr(df[sentiment_score], df[daily_return]) print(fPearson r {r:.4f}, p-value {p_value:.4f})pct_change() 是 pandas 里计算百分比变化的标准方法默认分母是上一个非空值。这里有一个隐藏细节如果某天停牌close 会缺失pct_change 会自动跳过吗不会它是按行位置对齐的前一天有值、后一天跳过缺失值继续算收益率会自动跨过停牌日这其实是正确的处理方式——停牌期间没有交易本来就不应该产生额外的一天收益率。dropna 的作用是清洗首行和停牌日样本避免 NaN 直接参与 pearsonr 计算。3.3 analysis.py 的输出相关系数表、散点图与 p 值怎么读analysis.py 最后会输出两样东西控制台打印的相关系数表以及一张舆情分对当日收益率的散点图。散点图用 matplotlib 的 scatter 就能画横轴是 sentiment_score纵轴是 daily_return再把线性回归拟合线叠上去。这样做的意义是让你直观看到样本点的分布形态而不是只盯一个数字。读 p-value 是新手最容易翻车的地方。p 值小于 0.05 只能说明相关性在统计上显著不等于 0不代表相关性有实际预测价值。r 0.1 配上 p 0.001在几千个交易日样本里很常见但这个系数对应的解释力只有 1%。运行时如果看到这种结果别急着下舆情显著影响股价的结论先看看散点图是不是一团云。源码默认输出的是当日舆情分与当日收益率的相关系数实际操作中这个值通常很低真正有意思的是滞后几天的相关性见最后一章。4. 环境配置与完整运行五个脚本按什么顺序跑才不算白跑4.1 依赖安装一个 requirements.txt 说清楚装什么整个项目涉及的依赖不算多但版本之间有小坑。建议直接用 pip 安装如下依赖大版本保持一致即可不要求完全锁死补丁号。requests2.31.0 beautifulsoup44.12.3 pandas2.1.4 numpy1.26.3 scipy1.11.4 matplotlib3.8.2 jieba0.42.1 openpyxl3.1.2 pymysql1.1.0openpyxl很容易漏装它是 pandas 读写 xlsx 文件的底层引擎不装的话 score.xlsx 和 share.xlsx 落地时会报ImportError: Missing optional dependency openpyxl。pymysql只在你要把新闻数据写进 MySQL 时才需要如果只跑通文件落盘可以先不装。Python 版本建议 3.8 到 3.103.11 以上跑 jieba 偶尔会出现依赖编译警告但不影响结果。4.2 执行顺序与文件流转谁产出、谁消费源码包里的脚本是按数据流水线设计的执行顺序不能乱。我按实际运行顺序整理成了下面的流程。# 1. 建库建表可选但建议先建 mysql -u root -p 建表语句.txt # 2. 采集舆情新闻数据 python baidu.py python sina.py # 3. 舆情评分产出 score.xlsx python score.py # 4. 抓取股票行情产出 share.xlsx python stock.py # 5. 合并计算相关性输出结果与图表 python analysis.py脚本之间的依赖关系是baidu.py 和 sina.py 负责往数据库或本地文件写入原始新闻score.py 消费这些原始新闻产出舆情评分表stock.py 独立运行只依赖股票代码和行情接口analysis.py 是最后一个环节同时读 score.xlsx 和 share.xlsx 做合并计算。这里最隐蔽的坑是时序如果 score.py 跑完你再改 stock.py 里的股票代码重新抓行情分析时合并用的股票代码必须和评分时一致否则 pandas 的 merge 会得到一堆空值相关性计算结果自然不对。4.3 建表语句与字段约定两张表承载全部中间数据源码里的建表语句.txt 提供了 MySQL 的表结构。即使你不想用数据库、直接落盘 xlsx也建议看一眼建表语句因为它把字段类型定义得清清楚楚反过来能帮你理解脚本内部对数据格式的假设。示意如下。CREATE TABLE stock_news ( id INT AUTO_INCREMENT PRIMARY KEY, stock_code VARCHAR(10) NOT NULL, news_title VARCHAR(500) NOT NULL, news_url VARCHAR(500), publish_time DATETIME, source VARCHAR(20) DEFAULT baidu, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, KEY idx_stock_date (stock_code, publish_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE stock_daily ( id INT AUTO_INCREMENT PRIMARY KEY, stock_code VARCHAR(10) NOT NULL, trade_date DATE NOT NULL, open_price DECIMAL(10,3), close_price DECIMAL(10,3), volume BIGINT, UNIQUE KEY uk_stock_date (stock_code, trade_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;stock_news表的联合索引idx_stock_date踩过一个坑按股票代码和时间过滤新闻是评分阶段最频繁的操作没这个索引时数据量一旦过万score.py 每处理一只股票都要全表扫描慢得让人以为死锁了。stock_daily表的唯一键uk_stock_date则是防重复的兜底——行情接口重复调用时靠它做 insert ignore 或 on duplicate key update 能避免同一交易日的记录被插两遍。5. 避坑指南新闻源改版、日期错位与空数据是三大翻车点5.1 程序不报错但 score.xlsx 里全是空数据现象baidu.py 和 sina.py 都正常执行完控制台没有任何异常但生成的 score.xlsx 只有表头一行数据都没有。 原因网站页面结构改版解析代码里写的选择器比如select(.result)已经匹配不到任何页面元素items一直是空列表而脚本里没有对空结果做告警。 解决在所有采集函数末尾加一行调试输出print(f共抓取 {len(items)} 条新闻)如果条数为 0 就立即停止后续步骤。然后用浏览器开发者工具重新查看搜索结果页的真实 DOM 结构更新选择器或正则表达式。源码里的 .txt 文件提示的自行修改解析部分说的就是这一个环节。5.2 停牌日导致合并后出现大量 NaN相关性结果直接失真现象merge 之后样本量只有预期的八成相关系数变得极不稳定。 原因股票停牌时没有行情记录但新闻照常产生舆情评分表里多出来的日期在行情表里找不到对应行pd.merge默认的 inner 模式会把这些日期全部丢掉。 解决先看合并前后的行数差异如果差异超过 5%说明停牌日比例不低。要么接受删除停牌样本但要在分析里注明要么把缺失的行情数据用前一个交易日的收盘价填充但这样会人为制造 0 收益率样本我建议后者只用于长周期分析日频相关性计算里慎用。5.3 收盘后的重大新闻被算成当天舆情相关性被严重稀释现象用当日舆情分和当日收益率算出的 r 值几乎为 0不管怎么调整情感词表都提不上去。 原因A 股 15:00 收盘之后发布的新闻被按自然日聚合到了当天但它的价格影响要等下一个交易日才能体现。比如晚上八点公告重大亏损当天收盘价早就定格了用当天收益率去对应这条利空等于把信号和结果错开了半天到一天。 解决把新闻时间按交易时段重新归档15:00 之后的时间戳顺延到下一个交易日。源码是自然日聚合我拿到后第一件事就是加了这个偏移逻辑相关性往往立刻有明显提升。5.4 情感词典漏掉辟谣和澄清负面新闻打出高评分现象公司发布关于不实报道的澄清公告标题里含违规出现在负面词表里和不实不在任何词表里score.py 给这条新闻打了低分但实际上这是利好。 原因基于词典的情感分析处理不了否定结构不违规被拆成不和违规负面词命中一次。双重否定和新闻行业的反转属性辟谣、澄清、立案调查后撤诉都会让词典模型失灵。 解决在评分前加一层否定词扫描如果负面词前一个词是不未否认就没收这次命中。这个修复虽然简单但对新闻文本特别有效因为新闻标题里的不字密度远高于日常对话。5.5 多股票跑批时数据串行相关系数高到不敢信现象一次跑 5 只股票其中一只的舆情分和另一只的收益率算出了 0.9 的相关系数。 原因score.xlsx 或 share.xlsx 在生成时没有严格按股票代码过滤后续分析脚本 merge 时只用日期做关联条件导致 A 股票的舆情分匹配上了 B 股票的收益率。 解决给每一步落盘文件都保留stock_code字段分析脚本里 merge 的 on 条件必须是[stock_code, date]两个字段同时匹配。我见过太多次只按日期合并翻车的案例这个字段是跑批场景的底线保障。6. 把相关性算得更可信滞后分析、符号稳定性与情感词典扩展相关性分析最容易犯的错是只算一条当日舆情分 vs 当日收益率然后下结论说舆情和股价无关。我在跑这个项目时试过把舆情分分别对应当天、滞后一天、滞后两天、滞后三天的收益率结果差异非常大——滞后一天的相关系数往往是当日相关系数的两倍以上。原因是信息传导需要时间新闻在盘中发酵、盘后传播真正影响开仓决策通常发生在次日。滞后相关的实现很简单核心代码就几行。import pandas as pd from scipy.stats import pearsonr df pd.merge(score_df, stock_df, left_onnews_date, right_ontrade_date) df df.sort_values(news_date).reset_index(dropTrue) df[daily_return] df[close].pct_change() for lag in range(0, 4): # shift(-lag)当前舆情分 对应 未来 lag 天的收益率 df[ffwd_return_{lag}] df[daily_return].shift(-lag) tmp df.dropna(subset[sentiment_score, ffwd_return_{lag}]) r, p pearsonr(tmp[sentiment_score], tmp[ffwd_return_{lag}]) print(flag{lag} 天 | r{r:.4f} | p{p:.4f})shift(-lag)的含义是把收益率列整体上移 lag 行让第 T 天的舆情分和 Tlag 天的收益率对齐。能跑出 p 值小于 0.05 且 r 值方向稳定连续多天同号的 lag才是值得关注的那个。方向稳定性是个很好用的经验法则如果滞后 1 天 r 为正、滞后 2 天 r 又变成负大概率只是噪声。情感词典的扩展也值得做。源码里那张词表是硬编码在 score.py 里的换一只股票或一个行业你会发现行业特有词比如医药的临床失败、芯片的流片成功一个都匹配不上。常见做法是把词表改成外部文本文件每行一个词按正负分目录存放再给每个词配一个权重值——涨停权重 2盈利权重 1亏损权重 2诉讼权重 1.5。这样评分就不会被一两个高频弱情感词主导。从那以后我每次跑完相关系数都会强制自己走一遍滞后表和符号稳定性检查再决定要不要在报告里写存在显著相关性。这个习惯帮我挡掉过至少三次不靠谱的实证结论希望也能帮到你。本文还有配套的精品资源点击获取
返回列表