行为金融学研究表明,散户情绪往往是短期股价波动的重要驱动因素。东方财富股吧作为国内活跃度最高的散户交流社区之一,每天产生海量帖子,这些文本数据中蕴含着丰富的情绪信号。用R语言抓取并分析这些数据,量化股民情绪与股价之间的关联,是文本量化领域一个经典且实用的入门课题。本文将完整演示从数据抓取到相关性分析的端到端流程。
一、用rvest抓取东方财富股吧帖子数据
抓取股吧数据的核心工具是rvest包,它封装了HTML解析和CSS选择器查询能力。东方财富股吧的帖子列表页URL结构比较规整,例如某只股票的帖子列表地址形如 https://guba.eastmoney.com/list,600519.html,其中600519是股票代码。翻页时只需在后面加上页码,如 list,600519,2.html,这为批量抓取提供了便利。
抓取前需要先分析页面结构。股吧列表页中每条帖子的标题和链接通常位于特定的class节点下,可以用浏览器开发者工具定位。需要注意的是,东方财富部分页面采用JavaScript动态渲染,遇到这种情况可以改用它提供的JSON接口,直接请求返回结构化数据,比解析HTML更稳定。
library(rvest)
library(dplyr)
library(stringr)
# 股吧帖子列表抓取函数
crawl_guba <- function(stock_code, max_page = 5) {
base_url <- "https://guba.eastmoney.com/list,"
all_posts <- list()
for (page in 1:max_page) {
url <- paste0(base_url, stock_code, ",", page, ".html")
Sys.sleep(runif(1, 2, 5)) # 随机休眠,降低被封风险
page_html <- tryCatch({
read_html(url)
}, error = function(e) NULL)
if (is.null(page_html)) next
# 根据实际页面结构调整CSS选择器
titles <- page_html %>%
html_nodes(".titletxt") %>%
html_text()
read_counts <- page_html %>%
html_nodes(".read") %>%
html_text()
if (length(titles) == 0) next
all_posts[[page]] <- data.frame(
code = stock_code,
title = titles,
page = page,
stringsAsFactors = FALSE
)
}
bind_rows(all_posts)
}
# 抓取贵州吧前5页数据
posts_df <- crawl_guba("600519", max_page = 5)
head(posts_df)反爬虫是绕不开的问题。东方财富对高频请求比较敏感,建议做好三件事:一是控制请求频率,每次请求之间加入2到5秒的随机延时;二是设置合理的请求头,模拟正常浏览器访问;三是把抓取任务分散到不同时段执行。如果数据量需求大,更好的方案是调用它的移动端接口,返回的是JSON格式,解析效率高且不容易触发反爬。
二、文本清洗与中文情绪打分
拿到原始文本后不能直接使用,需要经过清洗和分词两个步骤。股吧文本的特点是口语化严重、夹杂大量表情符号和标点,还有"yyds"、“抄底”这类网络用语。清洗时要去除无关符号、过滤掉过短的帖子(比如少于5个字的帖子信息量很低),然后使用jiebaR包进行中文分词。
情绪打分采用词典法,这是最简单也最常用的方案。基本思路是准备一个正向情绪词典和一个负向情绪词典,统计每条帖子中正负词出现的次数,用正向词数减去负向词数再除以总词数,得到归一化的情绪得分,取值范围控制在-1到1之间。中文金融情绪词典可以参考知网HowNet情感词典或大连理工情感词汇本体库,再手动补充一些股市特有词汇,例如“涨停”、“加仓”归为正向,“跌停”、“割肉”归为负向。
library(jiebaR)
library(dplyr)
# 初始化分词器
cutter <- worker(bylines = TRUE)
# 文本清洗函数
clean_text <- function(text) {
text %>%
str_replace_all("[^[:alnum:][:punct:]\\u4e00-\\u9fa5]", " ") %>%
str_trim()
}
# 加载情绪词典(需自行准备pos_words.txt和neg_words.txt)
pos_words <- readLines("pos_words.txt")
neg_words <- readLines("neg_words.txt")
# 情绪打分函数
score_sentiment <- function(text) {
words <- segment(text, cutter)
pos_count <- sum(words %in% pos_words)
neg_count <- sum(words %in% neg_words)
total <- length(words)
ifelse(total == 0, 0, (pos_count - neg_count) / total)
}
posts_df <- posts_df %>%
mutate(clean_title = clean_text(title),
sentiment = sapply(clean_title, score_sentiment))
summary(posts_df$sentiment)词典法的优点是可解释性强、计算快,缺点是对讽刺、反语这类复杂表达无能为力。如果预算允许,可以尝试调用大语言模型API对每条帖子做精细情绪分类,准确率会明显提升,但成本和速度是需要权衡的因素。实际项目中,词典法作为基线方案已经能满足大多数分析需求。
三、情绪指数与股价的相关性分析
有了每条帖子的情绪得分,下一步是聚合成日度情绪指数,再与股价数据对齐做相关分析。股价数据可以用quantmod包从公开数据源直接获取,按交易日取收盘价和涨跌幅。聚合时建议对情绪得分做加权处理,比如用阅读量或评论数作为权重,热门帖子对市场情绪的代表性更强。此外,过滤掉重复发帖和广告帖能有效降低噪声。
library(quantmod)
library(lubridate)
library(ggplot2)
# 获取股价数据
getSymbols("600519.SS", src = "yahoo", from = "2023-01-01")
price_df <- data.frame(
date = index(`600519.SS`),
close = as.numeric(Cl(`600519.SS`))
)
# 假设posts_df已包含发帖日期字段post_date
daily_sentiment <- posts_df %>%
group_by(date = as.Date(post_date)) %>%
summarise(sentiment_index = mean(sentiment), n_posts = n())
# 合并情绪与价格数据
merged <- merge(price_df, daily_sentiment, by = "date", all = FALSE)
# 计算当日情绪与当日、次日收益率的相关性
merged <- merged %>%
mutate(ret = c(NA, diff(log(close))),
ret_next = lead(ret))
cor_same <- cor(merged$sentiment_index, merged$ret, use = "complete.obs")
cor_next <- cor(merged$sentiment_index, merged$ret_next, use = "complete.obs")
cat("当日相关系数:", round(cor_same, 3), "\n")
cat("次日相关系数:", round(cor_next, 3), "\n")
# 可视化双轴走势
ggplot(merged, aes(x = date)) +
geom_line(aes(y = sentiment_index, color = "情绪指数")) +
geom_line(aes(y = ret * 20, color = "收益率")) +
scale_y_continuous(sec.axis = sec_axis(~./20, name = "收益率"))相关性分析中有两个关键细节值得注意。第一是滞后效应:股民情绪往往滞后于价格变化,也就是"涨了才乐观",因此除了当日相关,还应该计算情绪领先一天、两天的领先滞后相关系数,用ccf函数做互相关分析能一次性看清楚两序列在不同滞后阶数下的关联强度。第二是平稳性检验:价格序列是非平稳的,直接算相关系数可能出现伪相关,正确做法是对价格取对数收益率后再分析,或者对两个序列都做差分处理。
四、分析结果的解读与实践建议
从多数实证研究的结果来看,股吧情绪与股价存在弱到中等程度的相关性,且通常呈现双向影响:情绪既受当日涨跌驱动,也在一定程度上预示次日的波动。一般规律是负面情绪的极端值往往对应短期底部区域,而情绪过度亢奋反而可能预示回调风险,这就是经典的反向指标逻辑。
如果把这套分析用于实际策略,有几点经验值得参考。首先,单只股票的帖子量可能不足以构建稳定指标,建议扩展到指数成分股的股吧整体情绪;其次,情绪指标最好做标准化处理,比如转换成近20日的分位数,方便跨时间比较;最后,任何基于情绪的信号都应该先经过充分的历史回测,确认其在扣除交易成本后仍有超额收益,再考虑投入模拟盘验证。文本量化是一个持续迭代的过程,词典需要不断维护,抓取逻辑也要随网站改版而更新,保持代码的模块化能让后期维护轻松很多。