在期权回测中我用到了三种数据:期权日数据、ETF日数据和期权合约数据。其中前两种数据的获取方法已经在本系列的第1篇文章中做了说明,这里补充一下怎么获取期权合约数据。
期权合约数据是从上交所和深交所下载的。虽然深交所的合约可以手工下载excel文件再转化为CSV之后通过python程序读取。然而,如果需要长期持续地获取数据,最好还是通过爬虫自动爬取。
本文演示怎么通过scrapy爬虫获取上交所/深交所的期权合约信息。假定读者对scrapy有一定的了解,知道scrapy的基础用法。对scrapy不了解的读者可以先看scrapy官方教程中文版。
1. 上交所期权合约
上交所的数据在上交所当日合约可以查到。
1.1 上交所网页的反爬措施
分析上交所的URL,可以看到有两个地方需要特殊处理:
- 原网页通过一个回调函数处理返回数据,所以请求里要给一个回调函数名称。HTTP请求的参数形式如下:jsonCallBack=jsonpCallbacknnnnn。最后的“nnnnn”是一个随机数。可以通过python的randint()函数生成。
- 请求里有一个时间戳参数,形式如下:_=1596089949708。等号后面的数字是当前时间的毫秒数,可以通过time.time()*1000来获得。
1.2 创建爬虫
打开一个命令行窗口,进入要创建爬虫项目的目录,然后输入下面的命令:
scrapy startproject optdata
其中optdata是要创建的项目名称。scrapy创建的内容如下:
optdata/
scrapy.cfg
optdata/
__init__.py
items.py
pipelines.py
settings.py
spiders/
__init__.py
...
1.3 实现爬虫
进入刚才创建的“optdata\optdata\spiders”目录,用自己喜欢的编辑器新建一个名为“OptCodeSpider.py”的文件。我习惯用pycharm。
import scrapy
import json
import time
import re
import pandas as pd
from datetime import date
from random import randint, random
# 爬取上交所期权合约
class SSEOptCodeSpider(scrapy.Spider):
name = "sseoptcode" # 上交所期权合约爬虫名称
# 定义爬虫使用的header信息
headers = {
'Referer': 'http://www.sse.com.cn/assortment/options/disclo/preinfo/',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36'
}
# 爬取下来的数据保存在这个文件里
data_file = 'D:\Srcs\python\stockenv\data\options\opts_sse.csv'
# 爬取的期权合约信息
opts = []
# 获取起始URL的内容
def start_requests(self):
# 构造上交所期权数据的URL
url = self.get_opt_code_url()
# 获取页面内容。上交所当日合约可以全部放在一个页面里,不需要处理翻页问题。
yield scrapy.Request(url=url, headers=headers, callback=self.parse)
# 解析页面内容
def parse(self, response):
# 首先把返回数据转换成json数据
json_str = response.text[18:-1]
data = json.loads(json_str)
# 循环处理每一条合约
for opt in data['result']:
# 提取期权代码和期权名称
code = opt['SECURITY_ID']
name = opt['CONTRACT_SYMBOL']
# 从“50ETF(510050)”这样的字符串中提取基金代码,即括号里的部分。
s = opt['SECURITYNAMEBYID']
m = re.search(r"\(\d+\)", s) # 通过正则表达式匹配ETF代码
if m is None:
scrapy.log.msg('{}: 没有找到对应的ETF基金: {}'.format(code, s),
level=scrapy.log.WARNING)
continue
asset = m.group(0)[1:-1]
# 提取期权类型,行权价,合约单位
opt_type = opt['CALL_OR_PUT']
strike = opt['EXERCISE_PRICE']
unit = opt['CONTRACT_UNIT']
# 提取到期日
maturity = opt['EXPIRE_DATE']
maturity = '{}-{}-{}'.format(maturity[0:4], maturity[4:6], maturity[6:])
# 提取行权交收日
dd = opt['DELIVERY_DATE']
delivery_date = '{}-{}-{}'.format(dd[0:4], dd[4:6], dd[6:])
# 添加当前期权合约到期权合约列表
self.opts.append([code, name, asset, opt_type, strike, unit, maturity, delivery_date])
# 保存结果到数据文件
df = pd.DataFrame(self.opts, columns=['合约编码', '合约简称', '标的名称', '类型', '行权价(元)', '合约单位(份)', '期权行权日', '期权交收日'])
df.to_csv(self.data_file, index=False)
return
# 生成查询期权合约的URL
def get_opt_code_url(self):
# 生成时间戳
timestamp = int(time.time() * 1000)
# 构造URL
url = 'http://query.sse.com.cn/commonQuery.do?jsonCallBack=jsonpCallback{0}&isPagination=true&expireDate=&securityId=&sqlId=SSE_ZQPZ_YSP_GGQQZSXT_XXPL_DRHY_SEARCH_L&pageHelp.pageSize=10000&pageHelp.pageNo=1&pageHelp.beginPage=1&pageHelp.cacheSize=1&pageHelp.endPage=5&_={1}'.format(
randint(1000, 9999), timestamp)
return url
1.5 运行爬虫
完成爬虫编写后,回到命令行界面,进入第1层optdata目录,执行爬虫:
scrapy crawl sseoptcode
然后读取并检查“opts_sse.csv”文件:
df = pd.read_csv('D:\Srcs\python\stockenv\data\options\opts_sse.csv')
df[:5]
可以看到如下结果:
综上,上交所期权合约的爬虫就实现完成了。读者可以自行对此进行完善,比如添加更严谨的错误处理逻辑。
2. 深交所期权合约
深交所的期权合约在深交所产品目录/期权可以看到。
深交所的爬虫与上交所类似,区别在于:
- URL不同
- 合约数据项名称不同
- 个别数据项的格式略有区别
这些差异都很容易处理,所以这里直接给出代码。这个类和上交所的爬虫类一样,都放在“OptCodeSpider.py”文件里。
# 深交所期权合约爬虫
class SZSEOptCodeSpider(scrapy.Spider):
name = "szseoptcode" # 深交所期权合约爬虫名称
headers = {
'Referer': 'http://www.szse.cn/market/product/option/index.html',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36'
}
data_file = 'D:\Srcs\python\stockenv\data\options\opts_szse.csv'
opts = []
# 发送初始URL,开始爬取数据
def start_requests(self):
url = self.get_opt_code_url()
yield scrapy.Request(url=url, headers=self.headers, callback=self.parse)
# 解析爬取的数据
def parse(self, response):
# 转换为json
json_str = response.text
data = json.loads(json_str)
# 循环处理每一个合约
for opt in data[0]['data']:
code = opt['hydm']
name = opt['hymc']
asset = opt['bdmc']
if asset is not None and asset == '300ETF':
asset = '159919'
else:
scrapy.log.msg('{}: 没有找到对应的ETF基金: {}'.format(code, s),
level=scrapy.log.WARNING)
continue
opt_type = opt['hylx']
strike = opt['xqj']
unit = opt['hydw']
maturity = opt['xqrq']
delivery_date = opt['jsrq']
self.opts.append([code, name, asset, opt_type, strike, unit, maturity, delivery_date])
# 检查是否所有的页面都已经处理完成。
pn = data[0]['metadata']['pageno'] # 当前页数
pc = data[0]['metadata']['pagecount'] # 总页数
if pn < pc:
# 如果还有更多的页面,继续获取下一个页面
url = self.get_opt_code_url(pn + 1)
yield scrapy.Request(url=url, headers=self.headers, callback=self.parse)
else:
# 全部数据爬取完成,保存数据
df = pd.DataFrame(self.opts,
columns=['合约编码', '合约简称', '标的名称', '类型', '行权价(元)', '合约单位(份)', '期权行权日', '期权交收日'])
df.to_csv(self.data_file, index=False)
return
# 生成查询期权合约的URL
def get_opt_code_url(self, page_no=1):
url = 'http://www.szse.cn/api/report/ShowReport/data?SHOWTYPE=JSON&CATALOGID=ysplbrb&TABKEY=tab1&PAGENO={}&random={:.16f}'.format(
page_no, random())
return url
执行爬虫:
scrapy crawl szseoptcode
3. 小结
通过使用scrapy可以自动爬取上交所和深交所的期权合约数据,上交所和深交所也采取了一些简单的反爬措施,不过很容易解决。而且这个解决方案也可以用来爬取上交所/深交所的其它数据。