获取股票行情数据的多种方法

2020年10月14日

我国的股市总受人诟病,99.99%研究国内股票市场的文章都有指责该市场不成熟、不完善的祖传情绪,但一想到我国其他金融工具那×样,学习方法论的时候还是采用股票市场数据比较好吧。可是每次讲下载行情数据,学生都一脸嫌弃:股票数据还不满大街都是嘛!雷神能耍锤子,不代表你猕猴桃也可以。你得上手试一次,才知道这件事儿能不能成。

获取所有股票代码

我们在不同场合对数据获取有不同的要求,一个个数值往Excel里贴那是肯定不行的。今天我们就来讲讲,获取股票行情数据的办法:

获取所有股票代码

1 Tushare库

2017年备课的时候,tushare库还不是我下载金融数据的首选。那时候pandas-datareader还能用,我只以附录的形式介绍了tushare,心里想着说不定它哪天就坚持不下去了。没想到,我正吃着火锅唱着歌,pdr挂了,2018年上半年都是靠tushare续命。既然长寿又优雅,tushare慢慢被大家接受。但对2015级的学生,我始终心里有些愧疚,觉得这一块教得少了。最近tushare库不安生,作者米哥可能想变现了,整出一个不好用的tushare pro,不知道纯净版tushare还能用多久——且用且珍惜吧!

这里介绍tushare库获取股票行情的三种基本方法:

1.1 get_k_data()

我们在课上主要用get_k_data()函数获取行情数据,这是0.5.6版(2016年11月)中出现的行情获取函数,作者很喜欢这个函数,还特地写了一篇文章来说明它的用法。而我个人也很推荐它,因为它返回值够用,速度快,占用带宽也少。我们解释一下它的参数:

get_k_data(code=None,  # 股票代码(字符串,不需要标明交易所)
           start=''# 起始时间,必须是YYYY-MM-DD格式(字符串)
           end=''# 终止时间
           ktype='D'# 数据频率,可接受周W、日D、月M和5、15、30、60分钟(都是用字符表示的)
           autype='qfq'# 前复权,可选hfq(后复权)和None(不复权)
           index=False# 股指
           retry_count=3, pause=0.001# 与爬取技术相关的参数

这个函数的优点很多:

我们以600000为例:

import tushare as ts
df = ts.get_k_data('600000')
df.head()

作为量化分析的基础,这样干净的数据让人眼前一亮;但我们仍然发现一个问题,课上我们讲过get_k_data的结果中数据索引不是日期。于是我们用以下代码将date列转化为索引:

import pandas as pd
df.set_index(pd.to_datetime(df.date),inplace=True)

每次获取数据都需要转换索引,这是我当时拼命在课堂上缅怀pdr的主要原因。

1.2 bar()

由于课堂上我们用聚宽来获取期货数据,所以我没有提tushare获取非股票数据的方法。其实从0.8.0版(2017年6月)开始,tushare就是可以获取期货行情的;1.0.2版(2017年10月)的一次重大调整中,各种金融工具开始共用一个新的bar()函数。很遗憾,备课的时候我没能跟进该库的新发展,这里有必要隆重介绍一下。

bar()函数的语法为:

bar(code, # 股票代码(字符串)
    conn=None# 接口:需要修改为ts.get_apis()
    start_date=None# 起始日期,接受YYYY-MM-DD和YYYYMMDD
    end_date=None# 终止时间
    freq='D'# 数据频率,支持年Y
    asset='E'# 金融工具类型代码,E是股票和交易所基金,INDEX是沪深指数,X是期货/期权/港股/中概美国/中证指数/国际指数
    adj=None# 复权类型
    factors=[], # 自定义换手率(tor)和量比(vr)指标
    retry_count=3# 与爬取技术相关的参数

我们先说bar()函数与get_k_data()的区别:

所以你看,福祸相依,喜忧参半:

# 股票
ts.bar('600000',conn=ts.get_apis())
# 期货
ts.bar('AU',conn=ts.get_apis(),asset='X')
# X是其他金融工具的统称,我们也可以明确一下黄金期货是上海商品期货交易所的,于是asset='OS',但没有必要啦
# 不难看出,不同金融工具的返回指标存在差异

与bar()函数类似,1.0.2版改动中还增加一个tick()函数,用于获取每笔交易。我们用得比较少,感兴趣的同学可以通过help(ts.tick)来查看。

1.3 tushare pro

8月10日,tushare给我推送了一个重磅消息。它们要放弃原先的数据获取方式了——突然间我好惶恐,我××刚学会呀!取而代之的是,一种被称为tushare pro的东西。两者的差别在于:

好处很明显,获取数据会更稳定,有就是有,没有就是没有;坏处是,指不定哪天TOKEN就收费了(或者用积分换流量)。我找到它的网站,先注册了一个账号。

注册获取TOKEN:

我大概瞅了一眼手册,语法全都变了,这是要朕去死啊!没关系,先看看能不能获取K线所需的数据,保障我下学期的课能开起来才是最重要的。

Step1:初始化常规操作

import tushare as ts # 引入前先升级
ts.set_token('bd********f2'# 设置TOKEN
pro = ts.pro_api() # 初始化接口

Step2:获取股票行情

# daily函数:
pro.daily(ts_code = None# 股票代码(必须有交易所标记)
          trade_date = None# 交易日期(与代码二选一)
          start_date = None# 起始日期
          end_date = None# 终止日期

# query函数:
pro.query('daily'# 日行情
          <其他参数相同>)

我个人感觉这一串代码很繁琐,如果不是为了收钱,真的没有必要这么做。你看初始化就要三行,daily函数那些参数的名字又臭又长。而且截至发稿,还没有期货数据,手册也没说怎么调整采样频率和复权。

2 pandas-datareader库2.1 获取美股行情

pandas-datareader一直都能获取美股数据。0.6.0版说Google源不稳定,这根本对国内没有影响,毕竟从来没有连上过。从手册上我们可以看到不少pandas-datareader的数据源,比如IEX、Robinhood、Morningstar等等,用法非常相似,只是改动了数据源的参数。以拼多多的股票为例(PDD):

import pandas_datareader as pdr # 注意是下划线
pdd = pdr.data.DataReader('PDD''iex''2018-01-01''2018-08-20'# 用IEX源

从结果上看,IEX源的数据返回最简洁,索引是时间(注意不是时间戳,而是字符串),一切都很完美。很遗憾,我不知道怎么才能下载A股行情,尤其是Morningstar源,明明网站是能查到A股行情的……

2.2 用fix_yahoo_finance库修正雅虎源

雅虎(Yahoo!)近些年的发展就像一位长者,它明明改变了世界,可人们关心的却永远都是它还在不在了。人心不古啊!pandas-datareader库从2018年2月开始就放弃雅虎源了,后来有人写了一个fix_yahoo_finance库,修正了这个问题。不知道这种组合能坚持多久——且用且珍惜吧!

安装pandas-datareader库和fix_yahoo_finance库都可以用pip3 install,非常方便。安装好之后,执行修正命令:

import pandas_datareader as pdr
import fix_yahoo_finance as fyf
fyf.pdr_override() # 修正命令

然后,就正常使用get_data_yahoo函数来获取行情数据:

df = pdr.data.get_data_yahoo('000001.SZ', start='2017-01-01', end='2018-12-12')

一切都仿佛回到了2017年我备课那会儿,我的PPT也不用改了,感恩!参数只有两个小点需要注意下:

3 量化平台

从量化平台里取数据出来有点本末倒置,但考虑到我们有1/4学期都在量化平台上折腾,不少同学已经很熟悉平台定制的函数,甚至自己都不安装python在本地而使用平台提供的notebook,这不失为一种灵活的方法。

我们以聚宽量化平台的黄金期货数据为例:

import jqdata # 载入平台专用函数库
import pandas as pd # 用到pandas中的to_excel方法
hj = attribute_history('AU9999.XSGE',count=300)
hj.to_excel(r'hjdata.xlsx')
# 这时notebook根目录里面就会出现一个名叫hjdata的Excel文件
# 将其下载下来,放在D盘根目录下
df = pd.read_excel(r'd:\hjdata.xlsx')

有几点需要说明:

当然,如果我们使用量化平台来回测,就真的没有必要下载数据在本机处理。平台回测可以动态复权、自动调整滑点,而很多常用的库都已经内嵌在平台中,甚至能用tushare,确实很方便。(缺点就是如果真有一个库没安装,比如tensorflow、CNTK、keras、pyecharts,就直接瞎了。)

已经内嵌的库:(加粗表示课上用过)

anyjson;arch;beautifulsoup4;cvxopt;gensim;graphviz;hmm;hmmlearn;jieba;Lasagne;matplotlib;mpl_toolkits;numpy;openpyxl;pandas;pillow;prettytable;pybrain;PyBrain;pycrypto;pymc;pywt;requests;scipy;seaborn;sklearn;snownlp;statsmodels;tables;talib;theano;tushare;xlrd;xlwt;zipline

4 API接口

我们还有一招:找一个提供行情数据的网站,直接下载,比如雅虎。但既然写出来当作一个方法,肯定不能这么全手动。我们发现,一些网站给出了股票行情的API接口。

4.1 雪球接口(推荐)

我个人最喜欢雪球网的数据,响应速度快,返回值种类多,整理起来也方便。

&period=&type=&begin=&end=

{"error_description":"遇到错误,请刷新页面或者重新登录帐号后再试","error_uri":"/stock/forchartk/stocklist.json","error_code":"400016"}

这也就导致爬虫不能简单使用requests.get(url)的形式,而要利用访问主页的cookies来访问目标链接。在这里给出核心的代码,以防同学们哪天用到:

# import requests; import json; import pandas as pd
# 简单说,Session是保持浏览方式的类,后续每一个get方法都沿用相同的请求头、cookies、连接方式
session = requests.Session()
# 伪造请求头才能登录首页
session.headers = {'User-Agent''Mozilla/5.0'}
# 登录首页,目的是记录连接方式(包括cookies)
session.get('https://xueqiu.com/')
# 这时再get包含行情信息的网页就能成功了
r = session.get('https://xueqiu.com/stock/forchartk/stocklist.json?symbol=SH600000&period=1day&type=before'
# 最后把数据保存为数据框
df = pd.DataFrame(json.loads(r.text)['chartlist'])

缺点是起止时间(用start、end和_设置)均使用时间戳对应的数字表示,用不惯。多说一句,我们不一定非要用Session类,也可以用浏览器找到名称为aliyungf_tc、域名为的cookies,人工保持连接。

4.2 网易接口

&start=&end=&fields=

举例:

4.3 和讯接口

和讯接口的特点是可以控制采样的数量和方向。

&start=&number=&type=

和讯还提供了更简单的接口:

全部日线行情:

__DA.html

全部分时行情:

__MA.html

4.4 腾讯接口

/.js

举例:

腾讯接口的好处是它直接返回js,如果我们用Echarts画图,可以直接导入。

4.5 新浪接口

关于新浪财经接口的介绍文章很多,但我很少用,不太清楚现在有没有历史数据接口了。

有趣的是,新浪提供了一个行情图接口,返回png格式的图片:

/n/.png

5 直接爬取行情数据

我自己是最不愿意写爬虫的,能用轮子就绝不自己写,能Ctrl+C就绝不打字;但考虑到上述方案随时都可能消失或收费,我们总得给自己留一条后路。简单说一下我的备用方案:

5.1 从雅虎财经上爬

雅虎财经的个股地址是

/history

我们以600000为例:

# 用requests库获取网页源代码
r = requests.get(r'https://finance.yahoo.com/quote/600000.SS/history')
# 用re库获取源代码中的行情数据部分
# 发现源代码中居然有用json保存的数据(笑出声)
pat = re.compile(r'"HistoricalPriceStore":\{"prices":(\[.*?\])')
json_data = pat.search(r.text).group(1)
# 将json转换为数据框
df = pd.read_json(json_data)
# 将date放入索引
df.set_index(df.date,inplace=True)

如果我们不以json(而是以字典)的形式转化数据框,就会发现雅虎保存日期是用时间戳对应的数字,我这个遇到数据类型就晕菜的人只能写一长段代码转来转去,所幸最后转出来了。

# 与上一段代码的作用相同
# lst = pat.search(r.text).group(1)
df = pd.DataFrame(columns=['date','open','high','low','close','adjclose','volume','amount','type','data'])
for daily in eval(lst):
    df = pd.concat([df,pd.DataFrame(pd.Series(daily)).T],ignore_index=True)
def change(num):
    return pd.Timestamp(time.strftime("%Y-%m-%d",time.localtime(int(num))))
df.set_index(pd.to_datetime(list(map(change,list(df.date)))),inplace=True)

两者没有差别,就是不知道为啥read_json出来的date有零有整的,此处有图(又不是不能用.jpg)。如果只要爬近一年的数据,雅虎财经真是又快又好,配合其他地址参数还能做一些非常规动作。特别说明:amount、type和data都是除权当日出现的新标签,比如示例中有一天的type为DIVIDEND。

5.2 爱搞搞

毕竟A股数据遍地都是,我们没必要盯着一家,比如我随手搜了一个叫爱搞搞的网站:

源代码是乱了一些,但还是很好爬的:

# 用requests库获取网页源代码
r = requests.get(r'http://www.aigaogao.com/tools/history.html?s=600000')
# 把日期和高开低收行情挑出来
pat = re.compile(r'\1<\/td>([\d\.]+?)([\d\.]+?)([\d\.]+?)([\d\.]+?)')
res = pat.findall(r.text)
# 转成数据框
df = pd.DataFrame(res,columns=['Date','Open','High','Low','Close'])
# 把索引设置为时间戳
df.set_index(pd.to_datetime(a.Date),inplace=True)
# 丢弃Date列
df.drop('Date',axis=1,inplace=True)

当然了,再说一下原则,能站着获取数据,就坚决不爬。

第4和第5种方法没有本质不同,都是把网页上的行情信息获取下来整理备用,只不过API接口数据更整齐获取所有股票代码,网站往往会将其直接导入js代码区域,因此有的API数据干脆就是var赋值语句;而从普通网站上爬下来的源代码往往较乱,特别是一些不怎么讲究的网站,可能需要更多地使用正则表达式,而不是对标签的解析(xpath或bs4)。

6 小结

好了,到此为止我们讲完了获取股票行情数据的5种方法。当然,我们还能想到很多其他的办法,比如去数据库下载、上淘宝买、找程老师要……足够我们在各种场合下愉快地玩耍了。

总结一下:tushare和pandas-datareader库使用最方便,一次调试反复调用,记住个别函数就够了;量化平台也还行,特别是我们决定在平台自己的notebook上写代码的时候,这可能是最好的办法;API接口和网络爬虫都有点费事,但非常灵活,可以得到一些其他途径得不到的数据,实时监测更是不可或缺。

获取所有股票代码