第1章:金融文本数据获取

做量化投资这些年,我越来越觉得——数据是策略的血液。而金融文本数据,说白了就是市场情绪的体温计。今天咱们就来聊聊,怎么把这支体温计稳稳地握在手里。

我个人习惯把金融文本数据分成三大类:财经新闻、公告公告、社交媒体情绪。这三类数据各有各的脾气,获取方式也完全不同。你想想看,Bloomberg的数据和微博上的散户吐槽,能是一回事吗?

1.1 财经新闻API:Bloomberg与Reuters

先说说最正统的数据源——财经新闻API。Bloomberg和Reuters这两家,基本就是金融数据界的"可口可乐"和"百事可乐"。

核心要点:Bloomberg API(即BLPAPI)提供实时和历史新闻流,支持关键词过滤、时间范围筛选。Reuters则通过Thomson Reuters Eikon API提供类似服务。

我在项目中遇到过最头疼的事,就是Bloomberg的API认证。嗯,这里要注意——Bloomberg的API需要本地运行Bloomberg Terminal,说白了就是得开着那个黑乎乎的终端窗口。否则API调用会直接报错。

# 示例:使用blpapi获取新闻标题
import blpapi
from datetime import datetime, timedelta

# 初始化session
session = blpapi.Session()
session.start()

# 构建新闻请求
service = session.getService("//blp/news")
request = service.createRequest("retrieveNews")
request.set("query", "AAPL")  # 苹果公司
request.set("maxResults", 50)

# 设置时间范围
start_date = datetime.now() - timedelta(days=7)
request.set("startDateTime", start_date.strftime("%Y-%m-%dT%H:%M:%S"))

# 发送请求
session.sendRequest(request)

# 处理响应
while True:
    event = session.nextEvent()
    if event.eventType() == blpapi.Event.RESPONSE:
        for msg in event:
            print(f"标题: {msg.getElement('headline').getValue()}")
            print(f"时间: {msg.getElement('timestamp').getValue()}")
    if event.eventType() == blpapi.Event.TIMEOUT:
        break

避坑指南:我曾经因为忘记设置timeout参数,导致程序在凌晨3点卡死。Bloomberg的API响应时间不稳定,建议设置5-10秒的超时。

至于Reuters,我个人觉得它的API文档写得比Bloomberg友好。但有个坑——Reuters的新闻ID是动态变化的,不能直接缓存。我踩过这个坑,后来改用新闻的GUID(全局唯一标识符)来做去重。

1.2 A股公告爬虫:巨潮资讯网

做A股的朋友,巨潮资讯网(cninfo.com.cn)绝对是绕不开的。它是证监会指定的信息披露平台,所有上市公司的公告都会在这里发布。

说白了,巨潮资讯网就是A股的"公告总仓库"。但它的反爬机制,嗯,有点意思。

# 示例:爬取巨潮资讯网公告列表
import requests
import json
from datetime import datetime

def get_cninfo_announcements(stock_code, page_num=1):
    """
    获取巨潮资讯网公告
    stock_code: 股票代码,如 '000001'
    """
    url = "http://www.cninfo.com.cn/new/disclosure/stock"
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Referer': 'http://www.cninfo.com.cn/new/commonUrl?url=disclosure/list/stock'
    }
    
    params = {
        'stockCode': stock_code,
        'pageNum': page_num,
        'pageSize': 30,
        'tabName': 'fulltext',
        'plate': 'sz',  # sz=深市, sh=沪市
        'category': 'category_ndbg_szsh',  # 年报
        'trade': '',
        'column': 'szse_latest',
        'searchkey': '',
        'secid': '',
        'sortName': '',
        'sortType': '',
        'isHLtitle': 'true'
    }
    
    response = requests.post(url, headers=headers, data=params)
    
    if response.status_code == 200:
        data = response.json()
        announcements = data.get('announcements', [])
        return announcements
    else:
        print(f"请求失败: {response.status_code}")
        return []

# 使用示例
announcements = get_cninfo_announcements('000001')
for ann in announcements[:5]:
    print(f"标题: {ann['announcementTitle']}")
    print(f"日期: {ann['announcementDate']}")
    print("---")

个人经验:巨潮资讯网的反爬主要靠IP频率限制。我建议用代理IP池,每个IP每分钟不超过30次请求。另外,公告PDF文件可以直接下载,但要注意文件名编码问题——我遇到过中文文件名乱码的坑。

你想想看,A股公告的类型五花八门——年报、季报、临时公告、股东大会决议...每种公告的情感倾向都不一样。比如"业绩预增"和"股东减持",情绪能一样吗?

1.3 社交媒体数据:Twitter与微博情绪

社交媒体数据,说白了就是"散户的嘴"。Twitter上的美股讨论,微博上的A股吐槽,都是情绪的天然矿藏。

我记得有一次做美股策略,发现Twitter上关于特斯拉的讨论量突然暴增,结果第二天股价就涨了5%。社交媒体的情绪,往往领先于价格

# 示例:使用Tweepy获取Twitter数据
import tweepy
import pandas as pd
from datetime import datetime, timedelta

# 配置API密钥(实际使用时替换为真实密钥)
consumer_key = "YOUR_CONSUMER_KEY"
consumer_secret = "YOUR_CONSUMER_SECRET"
access_token = "YOUR_ACCESS_TOKEN"
access_token_secret = "YOUR_ACCESS_TOKEN_SECRET"

# 认证
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)
api = tweepy.API(auth, wait_on_rate_limit=True)

def get_tweets_by_keyword(keyword, count=100):
    """
    获取包含关键词的推文
    """
    tweets = []
    
    # 搜索最近7天的推文
    start_date = datetime.now() - timedelta(days=7)
    
    for tweet in tweepy.Cursor(
        api.search_tweets,
        q=keyword,
        lang="en",
        result_type="mixed",
        tweet_mode="extended"
    ).items(count):
        
        tweets.append({
            'id': tweet.id,
            'text': tweet.full_text,
            'created_at': tweet.created_at,
            'user': tweet.user.screen_name,
            'followers': tweet.user.followers_count,
            'retweets': tweet.retweet_count,
            'likes': tweet.favorite_count
        })
    
    return pd.DataFrame(tweets)

# 使用示例
df_tweets = get_tweets_by_keyword("AAPL stock", count=200)
print(f"获取到 {len(df_tweets)} 条推文")
print(df_tweets[['text', 'created_at', 'likes']].head())

避坑指南:我曾经因为Twitter API的速率限制,导致数据采集中断了整整一天。建议使用多个API Key轮换,或者购买高级API权限。另外,Twitter的搜索API只能返回最近7天的数据,做历史回测时要注意。

微博的情况类似,但有个特点——微博的评论比正文更有价值。正文往往是官方口吻,评论才是真实情绪。我一般会同时抓取正文和评论,然后分别做情感分析。

1.4 数据获取的架构设计

说了这么多具体方法,咱们来聊聊整体架构。你想想看,如果每天要处理几万条新闻、几十万条公告、上百万条推文,没有一个好的架构是不行的。

金融文本数据获取架构图 数据源层 Bloomberg/Reuters API 巨潮资讯网爬虫 Twitter/微博 API 数据采集层 定时调度器 请求队列 代理IP池 错误重试机制 数据存储层 原始数据(MongoDB) 结构化数据(PostgreSQL) 缓存(Redis)

这个架构图是我在实际项目中总结出来的。三层结构:数据源层、数据采集层、数据存储层。每一层都有各自的职责,层与层之间通过消息队列解耦。

个人建议:数据采集层一定要做好监控。我曾经因为一个API密钥过期,导致整整两天没有采集到数据,而监控系统居然没报警。后来我加了一个"数据新鲜度"指标——如果某个数据源超过1小时没有新数据,立刻发邮件通知。

好了,关于金融文本数据的获取,咱们就聊到这里。记住一句话:数据获取是情感分析的地基。地基打不牢,后面再漂亮的策略都是空中楼阁。


专注资料整理