国内目前5家期货交易所都在官网公开了各自期货品种的历史数据。从技术上,它们各自的实现方式都不相同。在抓取数据的时候,会用到解析json,解析xml,解析html和解析tsv这几种不同的方式。爬取这几家的数据对于学习网络爬虫特别是数据解析方法是一个很好的练习题目。
接下来把5家交易所的日统计数据都抓取一遍,这里先从上海期货交易所日统计数据开始。
在浏览器按F12能看到,上海期货交易所的数据获取接口是:
GET http://www.shfe.com.cn/data/dailydata/kx/kx20210826.dat
返回的数据格式是json。
具体的实现还是沿用这个系列一直使用的框架,按全量获取和增量获取分别实现,用线程池提高访问速度。本系列之前的文章都有说明,这里就不重复了。
json能取代其它数据格式,当前这么流行是有原因的,它不仅一定程度上满足了可读性,且对程序解析非常友好,基本就是一句话就转成了map数据结构。当然python中是dict。
json_value = json.loads(r.content.decode())
剩下唯一值得一提的问题是,具体测试的时候,发现新旧数据不完全兼容,比较新的数据增加了一个turnover字段(表示成交额),而在老数据中没有这个字段。
为了程序不出错,且能保存到同一张数据库表中,这里用最简单的方式处理一下,如果发现服务器没有返回turnover字段,就补一个None把位置填充上。
if 'TURNOVER' in data:
turnover = data['TURNOVER']
else:
turnover = None
完整的代码如下:
class ShfeDaily(AbstractDataRetriever):
def __init__(self):
super().__init__('futures_shfe_daily')
def _full(self, **kwargs):
self._get_data_list('20110101', today())
def _delta(self, **kwargs):
df_origin = self.query(fields='max(report_date)')
if df_origin.empty or df_origin.iat[0, 0] is None:
self._get_data_list('20110101', today())
else:
self._get_data_list(df_origin.iat[0, 0], today())
def _get_data_list(self, start_date, end_date, max_worker=multiprocessing.cpu_count() * 2):
df_cal_date = StockCalendar().query(
fields='cal_date',
where=f'`exchange`=\'shfe\' and is_open=\'1\' and cal_date >\'{start_date}\' and cal_date <= \'{end_date}\'',
order_by='cal_date')
with ThreadPoolExecutor(max_worker) as executor:
future_to_date = \
{executor.submit(self._get_daily_data, trade_date=row['cal_date']): row
for index, row in df_cal_date.iterrows()}
for future in as_completed(future_to_date):
row = future_to_date[future]
try:
data = future.result()
except Exception as ex:
self.logger.error(f"failed to retrieve {row['cal_date']}")
self.logger.exception(ex)
def _get_daily_data(self, trade_date):
shfe_url = f'http://www.shfe.com.cn/data/dailydata/kx/kx{trade_date}.dat'
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0',
'Accept': '*/*',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2'
}
r = requests.get(shfe_url, headers=headers)
json_value = json.loads(r.content.decode())
df = pd.DataFrame(
columns=['productid', 'name', 'deliverymonth', 'open', 'high', 'low', 'close', 'settlement', 'zd1_chg',
'zd2_chg', 'volume', 'turnover', 'openinterest', 'openinterestchg', 'report_date'])
for data in json_value['o_curinstrument']:
if data['OPENPRICE'] != '':
if 'TURNOVER' in data:
turnover = data['TURNOVER']
else:
turnover = None
df = df.append(
{'productid':data['PRODUCTID'].strip(), 'name':data['PRODUCTNAME'].strip(),'deliverymonth':data['DELIVERYMONTH'],
'open':data['OPENPRICE'], 'high':data['HIGHESTPRICE'], 'low':data['LOWESTPRICE'],
'close':data['CLOSEPRICE'],'settlement':data['SETTLEMENTPRICE'],'zd1_chg':data['ZD1_CHG'],
'zd2_chg':data['ZD2_CHG'],'volume':data['VOLUME'], 'turnover':turnover,
'openinterest':data['OPENINTEREST'],'openinterestchg':data['OPENINTERESTCHG'],
'report_date':trade_date},
ignore_index=True)
self._save(df)
if __name__ == '__main__':
ShfeDaily().retrieve()
上海国际能源交易中心和上海期货交易所比较,除了网址域名不同,其它从数据格式到命名风格都如出一辙。这两家应该用的完全相同的后台系统。所以爬取数据的处理方式也完全相同,就不重复了。