前言在之前的文章(如下)中,分享了爬取天天基金、中债指数的代码。爬取的数据很多,如何方便地存储、查询,也是一个麻烦的问题。本文分享一下我实现的基于 lsm-db 的本地文件数据存储、查询模块。
分享天天基金数据爬取的Python脚本
【Python分享】爬取中债指数的信息和历史行情
基础使用文件存储数据
以下面的数据为例,要把 index 存储下来,一个非常简单的方法,就是用 JSON 序列化,存储到文件里
index = {
'source': 'chinabond',
'code': '中债-1-30年利率债指数-总值-财富',
'indexid': '8a8b2ca0561f2580015620aed8481153',
'name': '中债-1-30年利率债指数-总值-财富',
'history': [
['2007-01-04', 100.0308],
['2007-01-05', 100.1178],
...
],
}
代码如下:
import json
s = json.dumps(index)
open('index.json', 'w').write(s)
s = open('index.json').read()
index = json.loads(s)
对于单条数据的读写来说,这样确实简单方便。但是如果是很多条数据,就不那么方便了。如果把所有数据存储到一个 JSON 文件里,那么即使只想读取单条数据,也必须读取全部内容,更新数据的时候也是如此,在数据量较大时就极为低效了。如果把每个数据存储到单独的 JSON 文件里,那么当数据的数量很多时,就会产生大量小文件,读写效率也会下降。
使用数据库存储数据
数据库是计算机领域对于数据存储的通用解决方案。常见的,如关系型数据库 MySQL、SQLite,文档型数据库 MongoDB。
对于轻量数据存取需求,KV数据库 lsm-db 是一个比较好的选择。lsm-db 是一个内嵌的 KV 数据库,数据存储在单个文件中,支持高效地读写、扫描操作。还以上面的 index 数据为例,用 index 的 code 作为 key,JSON 序列化的字符串作为 value,就实现了将数据的存储在KV数据库中,避免了上面原始的文件存储数据的弊端。
更复杂一点,可以把数据的一部分字段,单独存储到特殊的key里,作为索引,用于加速查询。这里就不展开说明了。
存储查询模块使用下面开始正式介绍模块的使用
概述
lib_dbs.py
这个文件实现了一个基于 KV 数据库的数据存储类 Table,类似 MySQL 里的表。
lib_filter.py
这个文件实现了一个查询类,可以支持一些匹配条件,比如等于、不等于、大于、小于、存在、不存在等,也支持多个条件的组合。
基本用法
from lib_dbs import Table
索引字段INDEXES = [
'code',
'name',
'update_time',
'ror',
]
Index = Table(
'data/index.ldb',
'Index',
'code',
INDEXES,
['history'],
)
上面的代码里,Index 就是具体的数据模型对象了,它的初始化参数是
db_uri 数据库文件路径,每个数据模型对象都要有单独的数据库
name 数据模型对象的名称
pk 数据模型对象的主键,用于唯一标识一条数据,数据的这个字段的值必须是字符串,且不能为空
indexes 需要索引的字段,list类型,针对这些字段的匹配会比较快
heavy_keys list类型,这些字段会单独存储,可以把历史行情数据等很大的字段放里面
Table 实例的基本用法如下:
Table.ensure_index()
刷新索引数据,新增的数据表、修改了索引字段后,必须执行这个函数
Table.save(item)
存储一条数据,item 应是 dict 类型,主键必须存在且不为空值
Table.bulk_save(items)
存储多条数据
Table.delete(pk)
根据主键的值 pk 删除一条数据。对应数据不存在时也不会报错
Table.get_by_pk(pk, shallow=False)
根据主键的值 pk 查询一条数据。对应数据不存在时,返回None。shallow参数为True时,代表浅查询,表定义里 heavy_keys 里的字段将不会读取,速度会更快。
Table.list(shallow=False)
读取所有数据。shallow参数同上。
示例:
from lib_index_db import Index
Index.ensure_index()
indexids = [i['id'] for i in get_chinabond_index_list()]
names = set()
for indexid in indexids:
_indexes = get_chinabond_index(indexid)
for index in _indexes:
names.add(index['name'])
print(index['name'])
Index.save(index)
indexes = Index.list(shallow=True)
index = Index.get(name='中债-信用债总指数-总值-财富')
from lib_fund_db import Fund
from lib_fund import fund_detail
Fund.ensure_index()
codes = ['510050', '510310', '510500']
for code in codes:
fund = fund_detail(code)
if fund:
Fund.save(fund)
funds = Fund.list(shallow=True)
fund = Fund.get(code='510310')
数据模型对象的高级用法
Table.filter(q=None, shallow=False, **kwargs)
返回查询对象QuerySet,查询结果对象QuerySet的详细用法见下文。shallow参数为True时,代表浅查询,表定义里 heavy_keys 里的字段将不会读取,速度会更快。q 是查询对象Q,查询对象Q的详细用法见下文。当 q 为 None 时,kwargs 如果非空,将会用于快捷构造查询对象。
Table.get(q=None, shallow=False, **kwargs)
参数与 .filter() 函数相同,但是返回的是查询对象的第一条数据,如果不存在匹配的数据,返回 None
查询结果集对象QuerySet
迭代
QuerySet 对象可以作为迭代器使用
QuerySet.filter(q=None, **kwargs)
查询结果集对象可以再应用另一个查询,以便级联查询
QuerySet.list()
返回 list 格式的查询结果
QuerySet.list_field(field)
返回 list 格式的查询结果,但是只查询 field 字段
QuerySet.list_fields(*fields)
返回 list 格式的查询结果,但是只查询fields指定的多个字段
QuerySet.count()
返回查询结果集对象的数据个数
查询对象 Q
Q(expr)
从字符串 expr 初始化,expr 是表达式形式,例如 "ror.2020 > 10",左边是字段名,中间是运算符,右边是比较值。"ror.2020 > 10"的意思就是 item['ror']['2020'] 大于 10。注意:除非特别说明,否则字段不存在时,仍然认为是满足匹配条件的。
运算符列表
=== 相等,如果对应字段不存在,视为不匹配。
== 相等
!= 不相等
<=, =, > 字面意思
~ 字段是字符串类型,表示包含字串。例如 "name ~ '债券' 的意思是 name 字段包含 '债券'
!~ 不包含
$e 字段存在
$ne 字段不存在
Q.from_kwargs(kwargs)
kwargs 是 dict 格式,等价于 key==value 的查询条件
示例
Table.filter(code='510310) 等价于
Table.filter(Q.from_kwargs({'code': '510310'})) 等价于
Table.filter(Q('code == "510310"'))
组合 Q 对象
多个 Q 对象可以组合成复杂查询
~ q 不满足q条件,逻辑非
q1 & q2 同时满足 q1、q2,逻辑与
q1 | q2 满足 q1 或者 q2,逻辑或
示例
Q('ror.2020 > 10') | Q('mdd.2020 < 2')
模块下载与配置代码在 https://github.com/puxxustc/invlib 这里。如果不能访问,也可以从附件下载。附件 invlib.pdf 是个 zip 压缩包,因为论坛无法上传 zip 格式,所以改名成 pdf,下载后再自己改成 zip 后缀,就可以解压了。
环境依赖
Python 3.8
需要安装这些 pip 包 beautifulsoup4, lxml, matplotlib, numpy, pandas, requests, wcwidth, ujson, lsm-db, msgpack, more-itertools
赏
1金币8金币18金币58金币88金币188金币其它金额
余额不足,立即充值
我的金币余额:个