首先获取股票列表,这类数据没有有效的时间戳让我们判断是否是新数据,且所有历史数据都可能发生变化,比如名称中的ST等。对应的_delta方法要单独考虑。
在父类AbstractDataRetriever的基础上(见https://blog.csdn.net/kengxie/article/details/118086422),去获取tushare的数据就很简单了。实际需要实现的就是_full和_delta这两个抽象方法。它们的返回值都要求是dataframe,然后框架会把dataframe按append的方式写入数据库。
class StockBasic(AbstractDataRetriever):
def __init__(self):
super().__init__('stock_basic')
def _full(self, **kwargs):
pass
def _delta(self, **kwargs):
pass
参考官方说明https://waditu.com/document/2?doc_id=25,公开的接口不能一次取得所有数据,如果要把L上市 D退市 P暂停上市三种状态的股票信息都读取回来的话,需要请求三次,每次获得一种状态的数据。没关系,我们可以在本地利用dataframe把全部数据合并到一起。
那么_full的实现就是这样:
def _full(self, **kwargs):
df_list = pro.stock_basic(list_status='L', fields=StockBasic._fields)
df_delist = pro.stock_basic(list_status='D', fields=StockBasic._fields)
df_pending = pro.stock_basic(list_status='P', fields=StockBasic._fields)
return pd.concat([df_list, df_delist, df_pending], ignore_index=True)
执行一遍再登陆数据库看看,数据正确取得了:
_delta的实现就要麻烦一些,关键是数据本身没有明显的标志可以作为是否是新数据的判断依据。考虑到数据量很小,总共也就4000+,干脆也全部取回来好了。
def _delta(self, **kwargs):
return self._full(**kwargs)
但是写入数据库的时候就不能用if_exists = 'append'的方式了,这里需要覆盖原来的所有数据。
dataframe自带的replace方式不够友好,它默认的实现方式是删除整张表,然后重建表再写入数据。这里我希望保留表结构,因为我手动创建了主键,添加了索引,还修改了字段类型,这些我希望保留。
把base class修改一下,自定义一个_replace方法,采用保留表结构,先删除整表数据,再插入数据的方式来完成replace操作。特别的,要把delete和insert放到一个事务中:
def _save(self, df):
if self.if_exists == 'replace':
self._replace(df)
else:
df.to_sql(self.table_name, engine_ts, index=False, if_exists=self.if_exists, chunksize=5000)
def _replace(self, df):
db = SQLDatabase(engine_ts)
with db.run_transaction() as conn:
if self._initialized():
stmt_truncate = text(f"delete from {self.table_name}")
conn.execute(stmt_truncate)
df.to_sql(self.table_name, conn, index=False, if_exists='append', chunksize=5000)
在子类的构造方法中指明采用if_exists = 'replace':
def __init__(self):
super().__init__('stock_basic', if_exists='replace')
测试一下,没有问题,搞定!
完整的StockBasic类:
class StockBasic(AbstractDataRetriever):
_fields = 'ts_code,symbol,name,area,industry,market,exchange,list_status,list_date,delist_date,is_hs'
def __init__(self):
super().__init__('stock_basic', if_exists='replace')
def _full(self, **kwargs):
df_list = pro.stock_basic(list_status='L', fields=StockBasic._fields)
df_delist = pro.stock_basic(list_status='D', fields=StockBasic._fields)
df_pending = pro.stock_basic(list_status='P', fields=StockBasic._fields)
return pd.concat([df_list, df_delist, df_pending], ignore_index=True)
def _delta(self, **kwargs):
return self._full(**kwargs)
全部代码上传到https://github.com/xiekeng/tushare-client,感兴趣的可以自取。