数据源准备:获取股票行情数据、新闻标题与社交媒体数据

做市场情绪分析,第一步就是搞定数据源。说白了,没有数据,再牛的模型也是空中楼阁。

我个人习惯把数据源分成三类:行情数据新闻标题社交媒体数据。这三类数据各有各的脾气,获取方式也完全不同。今天我就把我在项目中踩过的坑、总结的经验,一次性讲清楚。

核心观点:数据质量决定分析上限。宁可花80%的时间在数据清洗上,也不要急着跑模型。

一、整体数据架构

先看一张图,帮你快速建立全局认知。这是我做项目时画的框架,后来一直沿用。

市场情绪分析系统 股票行情数据 tushare / akshare 新闻标题数据 财经网站 / API 社交媒体数据 微博 / 雪球 情绪指标输出 数据清洗 & 对齐

这张图的核心逻辑很简单:三条数据流分别采集,经过清洗对齐后,统一输入情绪分析引擎。嗯,这里要注意,数据对齐往往是最大的坑,后面我会细说。

二、股票行情数据获取

行情数据是情绪分析的「锚点」。你得知道股价在涨还是跌,才能判断情绪是正面还是负面。

2.1 使用 tushare

tushare 是我最早用的库,数据全,但需要 token。我记得第一次用的时候,光申请 token 就折腾了半天。

import tushare as ts

# 设置 token(需要去官网注册获取)
ts.set_token('your_token_here')
pro = ts.pro_api()

# 获取日线行情
df = pro.daily(
    ts_code='000001.SZ',  # 平安银行
    start_date='20240101',
    end_date='20241231'
)

print(df.head())

小技巧:tushare 的 pro 接口有频率限制。我一般会加个 time.sleep(0.5),避免被限流。

2.2 使用 akshare

akshare 是后来发现的宝藏库。免费、无需 token,而且数据源覆盖广。说白了,对个人开发者非常友好。

import akshare as ak

# 获取 A 股日线数据
df = ak.stock_zh_a_hist(
    symbol="000001",
    period="daily",
    start_date="20240101",
    end_date="20241231",
    adjust="qfq"  # 前复权
)

print(df.head())

避坑指南:我曾经在 akshare 上踩过一个坑——它返回的日期列是 object 类型,不是 datetime。记得手动转换:df['日期'] = pd.to_datetime(df['日期'])

2.3 两个库的对比

特性 tushare akshare
是否需要 token
数据频率限制 有(200次/分钟) 无明确限制
数据覆盖范围 A股、港股、美股、期货 A股为主,部分期货
数据质量 高(官方合作) 中(爬虫采集)
适合场景 生产环境、机构使用 个人研究、快速原型

我个人建议:做研究用 akshare,做产品用 tushare。你想想看,生产环境里数据源突然断了,那可不是闹着玩的。

三、新闻标题数据获取

新闻标题是情绪的「风向标」。一条「突发利空」的标题,可能瞬间改变市场情绪。

3.1 财经新闻 API

我常用的方案是爬取东方财富、新浪财经的新闻列表。这里给一个简单的示例:

import requests
from bs4 import BeautifulSoup
import pandas as pd

def get_eastmoney_news(page=1):
    """获取东方财富快讯"""
    url = f"https://finance.eastmoney.com/a/czqyw_{page}.html"
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    }
    
    resp = requests.get(url, headers=headers)
    soup = BeautifulSoup(resp.text, 'html.parser')
    
    news_list = []
    for item in soup.select('.news-item'):
        title = item.select_one('.title').text.strip()
        link = item.select_one('a')['href']
        news_list.append({'title': title, 'link': link, 'source': '东方财富'})
    
    return pd.DataFrame(news_list)

# 获取最近10页新闻
all_news = pd.DataFrame()
for i in range(1, 11):
    df = get_eastmoney_news(i)
    all_news = pd.concat([all_news, df], ignore_index=True)
    print(f"已获取第{i}页,共{len(df)}条")

print(f"总计获取 {len(all_news)} 条新闻标题")

关键点:新闻标题的时效性非常重要。我一般只取最近24小时内的新闻,太旧的数据对情绪分析没有意义。

3.2 数据清洗要点

新闻标题数据通常很脏。我在项目中遇到过这些问题:

  • 重复标题:同一新闻被多个平台转载
  • 特殊字符:如「【】★◆」等符号
  • 无关内容:广告、推广信息
def clean_news_title(title):
    """清洗新闻标题"""
    import re
    
    # 去除特殊字符
    title = re.sub(r'[【】★◆◆◆]', '', title)
    # 去除多余空格
    title = re.sub(r'\s+', ' ', title).strip()
    # 去除过短的标题(可能是广告)
    if len(title) < 10:
        return None
    return title

# 应用清洗
all_news['clean_title'] = all_news['title'].apply(clean_news_title)
all_news = all_news.dropna(subset=['clean_title'])

四、社交媒体数据获取

社交媒体是情绪的「放大器」。一条微博、一个帖子,可能引发连锁反应。

4.1 微博数据获取

微博的 API 现在比较严格。我常用的方法是模拟浏览器请求:

import requests
import json

def get_weibo_hot_search():
    """获取微博热搜榜"""
    url = "https://weibo.com/ajax/side/hotSearch"
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Cookie': '你的微博cookie'  # 需要登录后获取
    }
    
    resp = requests.get(url, headers=headers)
    data = resp.json()
    
    hot_list = []
    for item in data['data']['realtime']:
        hot_list.append({
            'title': item['word'],
            'hot_value': item['raw_hot'],
            'category': item.get('category', '综合')
        })
    
    return pd.DataFrame(hot_list)

# 获取热搜
hot_df = get_weibo_hot_search()
print(hot_df.head(10))

注意:微博的反爬虫机制很强。我曾经因为请求频率太高,被限制了三天。建议每次请求间隔至少 5 秒,并且使用代理 IP。

4.2 雪球数据获取

雪球是股民聚集地,情绪浓度极高。我一般获取热门帖子和评论:

import akshare as ak

def get_xueqiu_hot():
    """获取雪球热门帖子"""
    # akshare 已经封装了雪球接口
    df = ak.stock_social_hot_rank_xq(symbol="全部")
    return df

# 获取热门帖子
xueqiu_df = get_xueqiu_hot()
print(xueqiu_df[['title', 'hot_score', 'comments_count']].head())

4.3 社交媒体数据的特殊性

社交媒体数据和行情数据有个本质区别:非结构化。你想想看,一条「卧槽,跌惨了」和「市场出现系统性风险」,表达的情绪可能完全不同。

我一般会做以下预处理:

  • 文本长度过滤:太短的帖子(< 5字)通常没有分析价值
  • 情感词典匹配:用基础的情感词典做初步分类
  • 时间戳对齐:把社交媒体数据按分钟对齐到行情数据

五、数据对齐与存储

三条数据源的时间粒度不同:行情数据是日线/分钟线,新闻是小时级,社交媒体是分钟级。怎么对齐?

我常用的策略是:以行情数据的时间戳为基准,向前聚合

import pandas as pd

def align_data(stock_df, news_df, social_df):
    """将新闻和社交媒体数据对齐到行情数据"""
    
    # 确保日期格式一致
    stock_df['date'] = pd.to_datetime(stock_df['date'])
    news_df['date'] = pd.to_datetime(news_df['date'])
    social_df['date'] = pd.to_datetime(social_df['date'])
    
    # 按日期聚合新闻数量
    news_agg = news_df.groupby(news_df['date'].dt.date).size().reset_index()
    news_agg.columns = ['date', 'news_count']
    
    # 按日期聚合社交媒体帖子数量
    social_agg = social_df.groupby(social_df['date'].dt.date).size().reset_index()
    social_agg.columns = ['date', 'social_count']
    
    # 左连接合并
    result = stock_df.merge(news_agg, on='date', how='left')
    result = result.merge(social_agg, on='date', how='left')
    
    # 填充缺失值
    result['news_count'] = result['news_count'].fillna(0)
    result['social_count'] = result['social_count'].fillna(0)
    
    return result

# 执行对齐
aligned_df = align_data(stock_data, news_data, social_data)
print(aligned_df.head())

经验之谈:数据对齐时,不要只聚合数量。我还会计算「情感得分均值」、「情感波动率」等衍生特征,这些对后续的情绪建模非常有帮助。

六、完整数据流水线

最后,我把整个数据准备流程封装成一个类。这样每次跑新数据时,直接调用就行。

class MarketDataPipeline:
    """市场数据流水线"""
    
    def __init__(self):
        self.stock_data = None
        self.news_data = None
        self.social_data = None
        
    def fetch_all(self, stock_code, start_date, end_date):
        """获取所有数据"""
        print("开始获取行情数据...")
        self.stock_data = self._fetch_stock(stock_code, start_date, end_date)
        
        print("开始获取新闻数据...")
        self.news_data = self._fetch_news(start_date, end_date)
        
        print("开始获取社交媒体数据...")
        self.social_data = self._fetch_social(start_date, end_date)
        
        print("数据对齐中...")
        result = align_data(self.stock_data, self.news_data, self.social_data)
        
        return result
    
    def _fetch_stock(self, code, start, end):
        # 使用 akshare 获取行情
        import akshare as ak
        return ak.stock_zh_a_hist(symbol=code, start_date=start, end_date=end)
    
    def _fetch_news(self, start, end):
        # 获取新闻标题
        # 实际项目中会调用爬虫
        pass
    
    def _fetch_social(self, start, end):
        # 获取社交媒体数据
        pass

# 使用示例
pipeline = MarketDataPipeline()
data = pipeline.fetch_all('000001', '20240101', '20241231')
print(f"最终数据形状: {data.shape}")

嗯,到这里,数据源准备的核心内容就讲完了。你可能会问:这些代码能直接跑吗?说实话,不能。因为每个数据源都需要你注册账号、获取 token 或 cookie。但框架和思路是通用的,你照着这个结构去填充具体实现就行。

数据准备是情绪分析中最枯燥、但最重要的一环。把这一步做扎实了,后面的模型构建就是水到渠成的事。


公众号:蓝海资料掘金营,微信deep3321