2、金融文本数据源:获取财经新闻、社交媒体数据的API与爬虫方法
做量化投资的朋友都知道,情绪因子这东西,说白了就是「别人贪婪我恐惧,别人恐惧我贪婪」的数字化版本。但问题来了——你上哪儿去搞这些「别人」的言论数据?
我刚开始做情绪因子研究时,第一反应就是去爬财经新闻。结果发现,这事儿远没想象中简单。今天我就把这几年的踩坑经验,一次性倒给你们。
2.1 财经新闻数据源:从哪里抓?怎么抓?
财经新闻是情绪因子的「基本盘」。我个人习惯把新闻源分成三类:
- 综合门户:新浪财经、东方财富、雪球——覆盖面广,但噪音大
- 专业媒体:华尔街见闻、财新、Reuters——质量高,但可能有付费墙
- 官方渠道:上交所/深交所公告、证监会发布——最权威,但格式死板
嗯,这里要注意:不同源头的新闻,情绪权重是不一样的。我在项目中遇到过,某次把微博大V的吐槽和交易所公告混在一起做情绪打分,结果模型直接崩了——因为公告里全是「中性」措辞,而微博全是「极端」情绪,两者方差太大。
2.1.1 用API获取新闻(推荐方式)
如果有条件,优先用API。稳定、合规、省心。常用的几个:
| 数据源 | API名称 | 免费额度 | 特点 |
|---|---|---|---|
| 新浪财经 | 新浪财经API(非官方) | 无限制(但限频) | 中文新闻最全,但接口不稳定 |
| 东方财富 | 东方财富Choice API | 有限免费 | 数据规范,适合量化 |
| Bloomberg | Bloomberg API | 付费 | 全球最权威,但贵 |
| NewsAPI | NewsAPI.org | 500次/天 | 英文新闻为主,中文覆盖一般 |
举个例子,用Python调新浪财经的非官方API,获取某只股票的新闻标题:
import requests
import json
def get_sina_news(stock_code, page=1):
url = f"https://vip.stock.finance.sina.com.cn/corp/go.php/vCB_AllNewsStock/symbol/{stock_code}.phtml?page={page}"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
resp = requests.get(url, headers=headers)
# 这里需要解析HTML,实际项目中我一般用BeautifulSoup
return resp.text
# 调用示例
news_html = get_sina_news('sh600519') # 贵州茅台
print(news_html[:500])
我曾经因为没加User-Agent,被新浪封了IP整整三天。后来学乖了,每次请求都随机换一个User-Agent,再配合代理池。你想想看,人家服务器也不是傻子,你一秒发100个请求,不封你封谁?
2.1.2 爬虫方法(备用方案)
API拿不到的数据,就得靠爬虫。我建议用Scrapy框架,它比requests+BeautifulSoup的组合更抗造。为什么?因为Scrapy自带异步、去重、中间件,遇到反爬还能自动重试。
核心思路就三步:
- 找到目标页面:比如东方财富的个股新闻列表页
- 解析HTML:用XPath或CSS选择器提取标题、时间、正文
- 存储数据:存到CSV或数据库里,方便后续处理
给你看个简化版的爬虫代码,抓取东方财富的新闻标题:
import scrapy
class EastMoneySpider(scrapy.Spider):
name = 'eastmoney'
start_urls = ['http://finance.eastmoney.com/a/czqyw.html']
def parse(self, response):
# 提取新闻列表
for news in response.css('div.list-item'):
title = news.css('a::text').get()
link = news.css('a::attr(href)').get()
time = news.css('span.time::text').get()
yield {
'title': title.strip(),
'link': link,
'time': time
}
爬新闻的时候,别只爬标题。正文里的「关键词密度」和「情感词分布」才是情绪因子的核心。我一般会把正文前200字单独存一列,因为新闻的「导语」通常包含了最强烈的情绪信号。
2.2 社交媒体数据:Twitter/微博的情绪金矿
财经新闻是「官方情绪」,社交媒体才是「民间情绪」。你想想看,一个散户在微博上骂「茅台今天又跌了,垃圾!」——这种情绪,新闻里是看不到的。
2.2.1 Twitter数据获取
Twitter有官方API,但2023年改版后,免费额度砍得很厉害。我个人建议用以下方案:
- 学术API:如果你有学术邮箱,可以申请Twitter Academic API,免费且额度高
- 第三方平台:比如SocialData、Brandwatch,付费但省心
- 爬虫(不推荐):Twitter反爬极严,我试过用Selenium模拟登录,结果账号被封了5个
用Tweepy库调Twitter API的示例:
import tweepy
# 配置API密钥(实际项目中别硬编码)
bearer_token = "你的Bearer Token"
client = tweepy.Client(bearer_token=bearer_token)
# 搜索最近7天关于"AAPL"的推文
query = "AAPL lang:en -is:retweet"
tweets = client.search_recent_tweets(
query=query,
max_results=10,
tweet_fields=['created_at', 'public_metrics']
)
for tweet in tweets.data:
print(f"{tweet.created_at}: {tweet.text[:100]}")
Twitter数据的关键不是「量」,而是「质」。我建议只抓取粉丝数>1000的用户的推文,因为他们的情绪更有「传染性」。普通小号的噪音太大,反而会稀释情绪信号。
2.2.2 微博数据获取
微博的API比Twitter更坑。2018年之后,微博大幅收紧了开放接口。现在想拿微博数据,基本只有两条路:
- 微博开放平台:申请开发者权限,但审核极严,个人基本过不了
- 爬虫+模拟登录:用Selenium或Playwright模拟手机端登录,然后抓取
我目前在用的方案是Playwright模拟微博移动端(m.weibo.cn),因为移动端的反爬相对弱一些。给你看个核心片段:
from playwright.sync_api import sync_playwright
def fetch_weibo(keyword, max_pages=3):
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
# 先登录(需要手动扫码一次,后续用cookie)
page.goto('https://m.weibo.cn/search?type=60&q=' + keyword)
page.wait_for_timeout(3000)
for i in range(max_pages):
# 提取推文内容
cards = page.query_selector_all('div.card')
for card in cards:
text = card.query_selector('p.txt').inner_text()
print(text)
# 点击加载更多
page.click('a.m-page-next')
page.wait_for_timeout(2000)
browser.close()
爬取社交媒体数据,尤其是需要登录后才能看到的内容,存在法律风险。我曾经有个朋友,因为爬了某平台的用户私信数据,被起诉了。所以我的原则是:只爬公开数据,不碰用户隐私。做量化研究,合规永远是第一位的。
2.3 数据清洗与存储:别让脏数据毁了你的模型
数据拿到手,只是第一步。真正磨人的是清洗。我总结了一个「三筛法」:
- 第一筛:去重——同一篇新闻被多个网站转载,只保留最早的那篇
- 第二筛:去噪——去掉广告、营销软文、纯数据表格
- 第三筛:对齐——把新闻时间统一成UTC+8,把股票代码统一成6位数字
存储方面,我推荐用MongoDB。为什么?因为新闻和推文都是非结构化数据,字段不固定。MongoDB的文档型存储,比MySQL灵活得多。当然,如果你只是做小规模实验,CSV也够用。
# 清洗示例:过滤掉长度小于50字的新闻
def clean_news(news_list):
cleaned = []
for news in news_list:
if len(news['content']) < 50:
continue # 太短,可能是标题党
if '广告' in news['title'] or '推广' in news['title']:
continue
# 统一时间格式
news['time'] = pd.to_datetime(news['time']).tz_localize('Asia/Shanghai')
cleaned.append(news)
return cleaned
2.4 本章知识体系总览
说了这么多,我画了张图帮你理清思路。这张图展示了从数据源到情绪因子的完整链路:
这张图你看懂了吗?从数据源到最终存储,每一步都有坑。我当年在第一层「数据源」就卡了两个月——因为没搞清楚不同新闻源的更新频率差异,导致时间序列对不齐。后来我学乖了,每个数据源都单独建一个时间戳字段,再统一做对齐。
数据源的选择,决定了你情绪因子的「天花板」。如果你只爬新浪财经,那你的情绪只能反映「中国散户」;如果你加上Twitter,就能看到「全球投资者」的情绪。我个人建议,至少覆盖2个新闻源+1个社交媒体源,这样因子才够稳健。
公众号:蓝海资料掘金营,微信deep3321