第二章:金融文本数据获取实战
做量化交易的朋友都知道,数据是策略的命根子。但金融数据这东西,结构化数据好拿,非结构化的文本数据就有点麻烦了。今天我就带大家走一遍,怎么把A股的新闻数据、财经网站的公告信息,实实在在地抓下来。
嗯,先别急着写代码。咱们得先搞清楚,到底要拿什么数据。
2.1 数据源的选择与对比
我个人习惯把数据源分成三类:专业金融接口、财经网站、以及公开API。这三类各有各的脾气。
| 数据源类型 | 代表工具 | 优点 | 缺点 |
|---|---|---|---|
| 专业金融接口 | Tushare, AkShare | 数据规范、字段完整 | 部分需要积分/权限 |
| 财经网站 | 东方财富、新浪财经 | 实时性强、内容丰富 | 反爬严格、结构不固定 |
| 公开API | 新浪财经API、腾讯财经API | 免费、调用简单 | 接口不稳定、文档不全 |
你想想看,如果只是做回测验证,Tushare和AkShare就够用了。但要是做实时舆情监控,那还得靠爬虫。
核心观点:不要迷信单一数据源。我在项目中吃过亏,某次策略回测表现很好,实盘却崩了。后来发现是数据源在某个时间段有缺失。所以,至少准备两个数据源做交叉验证。
2.2 使用Tushare获取新闻数据
Tushare这个库,做量化的应该都不陌生。它提供了比较干净的金融数据接口。获取新闻数据,主要用 news 这个接口。
import tushare as ts
# 初始化,需要你的token
pro = ts.pro_api('你的token')
# 获取最近100条新闻
df = pro.news(src='sina', start_date='2024-01-01', end_date='2024-01-31')
print(df.head())
这里有个坑,我一开始也踩过。Tushare的新闻数据,默认只返回最近几条。如果你要历史数据,必须指定 start_date 和 end_date。而且,src 参数可以指定来源,比如 sina、eastmoney 等。
小技巧:我建议把获取到的数据直接存到本地CSV或者数据库里。别每次都重新拉,Tushare有调用次数限制的。我曾经一天拉了5000次,直接被封了24小时。
2.3 使用AkShare获取财经新闻
AkShare是另一个好用的库,它更像一个数据聚合器。获取新闻的方式更直接。
import akshare as ak
# 获取东方财富的新闻
df = ak.news_eastmoney(symbol="SH600519", date="2024-01-31")
print(df.columns)
print(df.head())
AkShare的好处是,它把很多网站的接口封装好了。你不需要自己去分析网页结构。但坏处是,如果网站改版,AkShare的接口可能会失效。我遇到过两次,都是等作者更新版本才恢复。
注意:AkShare的接口名经常变。比如 news_eastmoney 可能某天就改成 eastmoney_news 了。建议每次使用前,先看看官方文档。
2.4 使用Requests爬取东方财富
有时候,现成的接口满足不了需求。比如我想抓取某只股票的所有公告,那就得自己写爬虫了。
东方财富的公告页面,其实是一个动态加载的页面。直接请求HTML是拿不到数据的。得分析它的XHR请求。
import requests
import json
url = "https://np-anotice-stock.eastmoney.com/api/security/ann"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Referer": "https://data.eastmoney.com/",
}
params = {
"sr": -1,
"page_size": 50,
"page_index": 1,
"ann_type": "A",
"stock_list": "600519",
"f_node": 0,
"s_node": 0,
}
resp = requests.get(url, headers=headers, params=params)
data = resp.json()
print(data['data']['list'][0]['title'])
为什么会这样?因为东方财富的前端是Vue写的,数据都是通过API异步加载的。直接爬HTML只能拿到一个空壳子。
我记得有一次,我为了抓一个特定格式的公告,花了整整一下午分析接口参数。最后发现是 ann_type 这个参数没传对。嗯,这种坑踩多了,就长记性了。
2.5 使用Scrapy爬取新浪财经
如果数据量比较大,比如要爬取几千只股票的历史新闻,那Requests就有点吃力了。这时候我推荐用Scrapy。
Scrapy是一个异步爬虫框架,速度比Requests快很多。而且它自带数据管道,可以很方便地把数据存到数据库。
import scrapy
class SinaNewsSpider(scrapy.Spider):
name = 'sina_news'
start_urls = ['https://finance.sina.com.cn/stock/']
def parse(self, response):
# 提取新闻列表
for news in response.css('.news-item'):
yield {
'title': news.css('a::text').get(),
'url': news.css('a::attr(href)').get(),
'time': news.css('.time::text').get(),
}
# 翻页
next_page = response.css('.next::attr(href)').get()
if next_page:
yield scrapy.Request(next_page, callback=self.parse)
Scrapy的学习曲线稍微陡峭一点,但一旦上手,效率提升很明显。我现在的生产环境里,大部分爬虫都是用Scrapy写的。
避坑指南:我曾经写过一个爬虫,没加下载延迟,结果把新浪财经的服务器搞崩了。后来对方直接封了我的IP。所以,爬虫一定要加 DOWNLOAD_DELAY 设置,建议至少1秒。
2.6 API调用与数据清洗基础
数据拿到手了,但还不能直接用。金融文本数据通常很脏,需要清洗。
常见的清洗步骤包括:
- 去重:同一篇新闻可能被多个源转载
- 去噪:去掉广告、无关字符、HTML标签
- 标准化:统一日期格式、编码格式
- 字段提取:从文本中提取股票代码、公司名称等关键信息
import re
import pandas as pd
def clean_news_text(text):
# 去掉HTML标签
text = re.sub(r'<.*?>', '', text)
# 去掉特殊字符
text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9,。!?、]', '', text)
# 去掉多余空格
text = re.sub(r'\s+', '', text)
return text
# 示例
df = pd.read_csv('news_data.csv')
df['clean_content'] = df['content'].apply(clean_news_text)
print(df[['title', 'clean_content']].head())
数据清洗这一步,看似简单,其实最花时间。我有个项目,80%的时间都花在清洗数据上。但这一步做不好,后面的NLP模型再牛也没用。
我的习惯:清洗完的数据,我会做一个简单的质量检查。比如统计一下空值比例、文本长度分布。如果发现异常,就回溯到爬虫阶段看看是不是哪里出了问题。
2.7 本章知识体系
下面这张图,是我自己总结的数据获取流程。你可以照着这个思路来搭建自己的数据管道。
这张图把整个流程串起来了。从数据源到清洗,再到存储和分析,每一步都有讲究。我个人建议,刚开始做的时候,先用Tushare或AkShare跑通流程,再考虑自己写爬虫。
好了,数据获取这块就聊到这儿。数据拿到手,清洗干净,下一步就是做NLP分析了。不过那是后面章节的事,咱们先把基础打牢。
公众号:蓝海资料掘金营,微信deep3321