
简介这份资源是面向Python数据分析初学者与量化投资爱好者的实战项目源码围绕上市公司网络舆情与股票价格之间的相关性展开。项目通过采集新闻文本数据对上市公司舆情进行量化评分再与对应股票价格序列做相关性计算并以图形化方式呈现分析结果帮助读者理解从数据获取到结论输出的完整链路。压缩包共10个文件约136KB包含5个Python脚本分别负责数据采集、评分与分析2个xlsx数据表、2个txt说明与建表语句以及1份doc使用文档结构紧凑、注解详细并附运行截图便于对照。目前已有6300人学习下载。读者可获得一套可复用的舆情评分与相关性分析方案涵盖爬虫解析、评分逻辑、统计计算与可视化展示同时提供网站变动时的排错提示适合作为课程设计、毕业项目或量化入门练手参考。1. 从一份“能跑起来”的源码说起上市公司舆情与股价相关性到底怎么落地很多人第一次接触“python上市公司网络舆情与股票相关性分析项目源码”这个标题脑子里冒出来的画面是爬一堆新闻算个相关系数画条曲线收工。真上手才发现舆情数据不是标准表格股票行情又有复权、停牌、交易日对齐一堆坑最后算出来的相关系数要么高得离谱要么低得毫无解释力。这个方向真正要解决的问题是把非结构化的文本情绪变成能和日频收益率对齐的数值序列再在统计上验证两者是否存在稳定关系。它适合两类人一类是想拿一个完整项目练手的 python 爬虫与数据分析学习者另一类是想给量化策略加一个舆情因子的从业者。源码、文档、注解、运行截图这些交付物本质是帮你跳过环境搭建和接口调试直接看到数据怎么从网页流到相关系数矩阵。但能不能用取决于你有没有搞懂中间每一步的假设。2. 数据链路拆解从舆情文本到可对齐的数值序列2.1 舆情数据抓取requests xpath 的最小可用组合这个项目里舆情数据的来源通常是财经新闻站、股吧帖子或公告页。常见做法是用 requests 发请求lxml 的 xpath 提取标题、发布时间、正文。热词里提到的“python xpath爬虫 text函数”在这里很关键因为很多页面标题和正文混在嵌套标签里直接取 text() 会拿到空串或拼接错乱。import requests from lxml import etree import time 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_news_list(stock_code, page1): # 以某财经搜索页为例实际项目里替换成目标站点URL url fhttps://example.com/search?code{stock_code}page{page} resp requests.get(url, headersHEADERS, timeout10) resp.encoding resp.apparent_encoding # 防止中文乱码 html etree.HTML(resp.text) items html.xpath(//div[classnews-item]) results [] for item in items: title item.xpath(.//h3//text()) title .join(title).strip() if title else pub_time item.xpath(.//span[classtime]/text()) pub_time pub_time[0].strip() if pub_time else results.append({title: title, pub_time: pub_time}) time.sleep(1.5) # 控制频率避免触发风控 return results逻辑说明先设置 UA 伪装成浏览器再用 xpath 定位列表容器逐条提取标题和时间。text()返回的是列表必须 join 后再 strip否则会带换行和空格。参数上timeout建议 10 秒sleep间隔不要低于 1 秒否则很容易被临时封 IP。这个函数只负责列表页正文页需要再发一次请求用同样的 xpath 思路取//div[classcontent]//text()。提示如果目标站点是动态渲染的requests 拿不到数据需要换用能执行 JavaScript 的方案或者直接找它的数据接口。不要硬啃 HTML。2.2 股票行情获取与复权处理行情数据常见来源是公开接口或本地 CSV。关键点是复权。不复权的价格在除权除息日会出现跳空直接算收益率会把除权当成暴跌相关系数完全失真。项目里一般用前复权收盘价。import pandas as pd def load_price(csv_path): df pd.read_csv(csv_path, parse_dates[trade_date]) df df.sort_values(trade_date) # 假设已有前复权收盘价字段 adj_close df[ret] df[adj_close].pct_change() df df.dropna(subset[ret]) return df[[trade_date, adj_close, ret]]逻辑说明pct_change()算日收益率第一行必然是 NaN直接 drop。参数上parse_dates必须指定否则日期是字符串后面 merge 会对不上。如果只有不复权价格需要自己用复权因子换算常见做法是adj_close close * factor因子从行情接口的复权信息里取。2.3 舆情情绪打分从文本到数值这一步是整个项目最容易被低估的环节。常见做法有三种基于词典如金融情感词典、基于预训练模型、基于大模型 API。源码项目里为了可复现通常用词典法加简单规则。POS_WORDS [增长, 盈利, 中标, 回购, 增持] NEG_WORDS [亏损, 减持, 处罚, 诉讼, 退市] def sentiment_score(text): if not text: return 0 pos sum(1 for w in POS_WORDS if w in text) neg sum(1 for w in NEG_WORDS if w in text) total pos neg if total 0: return 0 return (pos - neg) / total # 范围 [-1, 1]逻辑说明遍历词典统计正负词命中次数归一化到 [-1,1]。参数上词典需要根据行业调整比如医药行业的“集采”通常是负面但词典里没有就会漏掉。更稳的做法是给每个词加权而不是简单计数。2.4 按交易日聚合与对齐舆情是按时间戳来的行情是按交易日来的。必须把舆情按交易日聚合再和行情 merge。def align_sentiment(news_df, price_df): news_df[trade_date] pd.to_datetime(news_df[pub_time]).dt.normalize() daily news_df.groupby(trade_date)[score].mean().reset_index() merged pd.merge(price_df, daily, ontrade_date, howleft) merged[score] merged[score].fillna(0) # 无舆情日填0 return merged逻辑说明normalize()把时间戳截断到日期groupby取均值作为当日情绪。howleft保证行情日不丢无舆情的交易日填 0代表中性。参数上如果舆情量很大可以考虑取中位数或加权平均避免极端值影响。3. 相关性分析怎么做才不是自欺欺人3.1 相关系数的选择Pearson、Spearman 还是互信息很多人上来就df[ret].corr(df[score])默认 Pearson。但舆情分数和收益率的关系往往不是线性的Pearson 会低估。常见做法是同时算 Spearman 秩相关它对单调非线性更稳健。from scipy.stats import pearsonr, spearmanr def corr_report(df): p_r, p_p pearsonr(df[ret], df[score]) s_r, s_p spearmanr(df[ret], df[score]) print(fPearson: r{p_r:.4f}, p{p_p:.4f}) print(fSpearman: r{s_r:.4f}, p{s_p:.4f})逻辑说明pearsonr和spearmanr都返回相关系数和 p 值。p 值小于 0.05 才考虑统计显著。参数上样本量至少 60 个交易日以上否则 p 值不可靠。如果数据有大量 0 值无舆情日Spearman 会更合适。3.2 滞后相关舆情影响的是今天还是明天舆情和股价的关系很可能有时滞。今天的新闻可能明天才反映在价格里。做法是把情绪序列 shift 若干天分别算相关。def lag_corr(df, max_lag5): results [] for lag in range(0, max_lag 1): shifted df[score].shift(lag) valid df[ret].notna() shifted.notna() if valid.sum() 30: continue r, p spearmanr(df.loc[valid, ret], shifted[valid]) results.append({lag: lag, r: r, p: p}) return pd.DataFrame(results)逻辑说明shift(lag)把情绪往后移lag1 表示用昨天的情绪解释今天的收益。参数上max_lag一般不超过 5太长就失去交易意义。有效样本少于 30 的 lag 直接跳过避免噪声。3.3 分组对比高舆情日 vs 低舆情日相关系数只是一个数分组对比更直观。把情绪分数按分位数分成高低两组比较两组次日收益的均值。def group_compare(df, q0.3): threshold_low df[score].quantile(q) threshold_high df[score].quantile(1 - q) low_group df[df[score] threshold_low][ret] high_group df[df[score] threshold_high][ret] print(f低情绪组 次日收益均值: {low_group.mean():.4f}, 样本: {len(low_group)}) print(f高情绪组 次日收益均值: {high_group.mean():.4f}, 样本: {len(high_group)})逻辑说明quantile取分位点默认 30% 和 70%。参数上q 可以根据样本量调整样本少就取 0.2。如果两组均值差异明显且方向符合预期说明舆情因子有区分度。4. 避坑与排查那些让相关系数翻车的细节4.1 现象相关系数高达 0.8 以上但策略回测不赚钱原因舆情分数和收益率可能同时受某个第三方因素影响比如大盘涨跌。大盘涨的时候新闻偏正面个股也涨算出来相关性很高但这不是舆情驱动的。解决把个股收益率减去大盘收益率用超额收益再算相关。或者把大盘收益作为控制变量做偏相关。4.2 现象某只股票舆情数据特别多拉高整体相关原因不同股票的舆情数量差异很大热门股一天几十条冷门股几天一条。直接混合所有股票算相关热门股的权重过大。解决按股票分别算相关再看分布的均值和显著性比例。或者对每只股票的情绪分数做标准化后再合并。4.3 现象p 值很小但相关系数只有 0.05原因样本量太大。几千个交易日下微弱的相关系数也会显著。解决不要只看 p 值要看效应量。相关系数低于 0.1 在实际交易中基本没有意义扣除交易成本后更不可能盈利。4.4 现象舆情日期和交易日对不上merge 后大量缺失原因新闻发布时间是自然日行情是交易日。周末和节假日的新闻被分到非交易日merge 时对不上。解决把非交易日的舆情顺延到下一个交易日或者按自然日聚合后再用行情日左连接。常见做法是news_df[trade_date] news_df[pub_time].dt.normalize()后用pd.merge_asof做最近匹配。4.5 现象爬虫跑一段时间后返回空列表或验证码页面原因请求频率过高触发风控或者 UA 被识别。解决降低频率随机化 sleep 间隔轮换 UA。如果目标站点有公开 API优先用 API。不要用多线程猛刷封得更快。5. 把相关性变成可验证的因子分层回测与稳健性检查相关系数算完只是第一步。真正要判断这个舆情因子值不值得投入得做分层回测。具体做法每个交易日按当日情绪分数把所有股票分成 5 组持有到次日收盘看第 1 组和第 5 组的收益差是否稳定为正。如果只有某一年有效其他年份无效说明是过拟合。def layered_backtest(df, n_group5): df df.copy() df[group] df.groupby(trade_date)[score].transform( lambda x: pd.qcut(x, n_group, labelsFalse, duplicatesdrop) ) daily_ret df.groupby([trade_date, group])[ret].mean().unstack() long_short daily_ret[n_group - 1] - daily_ret[0] print(f多空组合日均收益: {long_short.mean():.4f}) print(f多空组合胜率: {(long_short 0).mean():.2%}) return daily_ret逻辑说明qcut按情绪分数分组unstack把每组收益展开成时间序列。多空组合是最高分组减低分组。参数上n_group常用 5 或 10样本少就用 3。胜率低于 55% 的多空组合基本不用考虑。稳健性检查至少做三件事一是换情绪词典看结果是否一致二是换滞后天数看是否只在某个 lag 显著三是分年度看避免某一年极端行情主导。我自己的习惯是任何因子在投入实盘前必须通过这三项检查否则宁可不用。舆情数据噪声大相关性时有时无能稳定跑赢交易成本的因子非常少。希望帮到你。本文还有配套的精品资源点击获取