一.数据采集接口
1.tushare
2.sina
3.通联
二.数据采集模块:
为了保证采集到上层的数据可信,完整;定义两个类:
数据包:数据包为一次网络采集的数据片段,数据包封装各种接口,获取数据。数据包能够提供一个唯一的标识,以提供数据集识别;且数据包能够检测自身判断自身是否完成。还可以提供end标志,接收到该数据包表示整个数据集的完成。
数据集:为一系列的多个数据包,数据包在时间生成上可能不连续,可以持续很长时间,因此,数据集必须能够识别数据包片段,避免数据包的重复,而且数据集能够持久化(关键信息放入文件或者数据库中),可供以后恢复。数据集能够判断整个集合是否传输完成。数据集向上提供数据的生成器,或者保存到数据库。
class myque(deque):
def put(self,element):
self.append(element)
def get(self):
return self.pop()
class WebDataFrameBasic():
'''
数据包,一个web数据的片段
'''
def __init__(self,query_info={}):
'''
'''
self.query_info=query_info
def start(self,queue=None):
'''
开始获取数据,数据存放到queue中
queue中item的格式:{state:,label:,data:}
label:unique
state:end.finish,fail
:return:
'''
def unique(self):
'''
唯一标志该数据片段的关键字:数据集通过查询该片段保证是否已经获得,是重复片段
'''
def _is_finished(self):
'''
该片段是否成功传输
'''
def _is_end(self):
'''
是否是在后一个数据片段
'''
class WebDataSeqBasic():
'''
数据集,或者数据流
'''
STATE_TYPE={
"START":0,
"RUNNING":1,
"FINISH":2,
"END":3
}
def __init__(self,thread_num=1,info={},id=None):
'''
如果id为空,则自动创建一个数据集的id,并保存为一个记录
如果不为空,则在数据库或者文件中查询看是否有相应的记录,如果有则获取信息,创建实例
:param name:数据集的名称
:param id:唯一标志一个数据集
:return:
'''
if thread_num>1:#开启多线程模式
self.thread_mode=True
self.pool=ThreadPoolExecutor(thread_num)
self.queue=Queue()
else:#单线程模式
self.thread_mode=False
self.queue=myque()
if id:
self._load(id)
else:
max_id=WebDataSeqBasic._load_max_id()
self.id=max_id+1
self._save(self.id)
self.frame_state={} #该字典中填写数据片段的 STATE_TYPE
self.frame_try={}#该数据段的尝试次数
self.end_flags=False #当收到的数据片段标记_is_end为True时候会为真
self.info=info
def _load(self,id):
'''
从外部载入数据集
:param id:
:return:
'''
def _save(self,id):
'''
将该数据集保存到外部
:param id:
:return:
'''
@classmethod
def _load_max_id(cls):
return 0
def is_finished(self):
'''
是否完成
:return;
'''
finish=[WebDataSeqBasic.STATE_TYPE["FINISH"],WebDataSeqBasic.STATE_TYPE["END"]]
if self.end_flags and all([item in finish for item in self.frame_state.values()]):
return True
else:
return False
def task_gen(self):
'''
获取下一个任务的WebDataFrame实例
:return:
'''
raise NotImplementedError
def transport(self):
'''
传输
:return:
'''
for webdata in self.task_gen():
label=webdata.unique()
self.frame_state[label]=WebDataSeqBasic.STATE_TYPE["RUNNING"]
if label not in self.frame_try:
self.frame_try[label]=1
else:
self.frame_try[label]+=1
if self.thread_mode:
self.pool.submit(webdata.start,self.queue)
else:
webdata.start(self.queue)
while 1:
self.collect()
if self.is_finished():
break
def collect(self):
'''
从queue中获取信息,并更新状态
:return:
'''
item=self.queue.pop()
if item["state"]=="end":#end状态表示这是最后一帧,且完成了该帧
self.end_flags=True
if item["state"]in ["finish","end"]:
self.frame_state[item["label"]]=WebDataSeqBasic.STATE_TYPE["FINISH"]
self.handle_data(item["data"])
else:#
self.frame_state[item["label"]]=WebDataSeqBasic.STATE_TYPE["START"]
def handle_data(self,data):
raise NotImplementedError
程序分为收集数据模块,格式转换模块(dataframe转为nametuple,json与字典互转等),etl数据存储模块,db(sqlachemy,django),db查询模块,计算模块(主要采集dataframe数据作为输入),以及REST的接口。
dataframe转为nametuple,方便将记录存储
from collections import namedtuple
from numpy import float64
def numpy2type(data):
'''
将numpy格式的data转为普通的python格式
:return:
'''
import numpy
if "numpy" in str(type(data)):
if issubclass(type(data),float):
return float(data)
elif issubclass(type(data),int):
return int(data)
else:
return data
def df2tuple_gen(df,table_name,map={}):
'''
dataframe转为nametuple的生成器
map为记录的列名,df的列名的对应关系
'''
record=namedtuple(table_name,map.keys())
assert set(map.values())-{'index'} < set(df.columns)
rows,columns=df.shape
for row in range(rows):
info={}
for key in map:
if map[key]=="index":
info[key]=numpy2type(df.index[row])
continue
info[key]=numpy2type(df.iloc[row][map[key]])
yield record(**info)
针对tushare创建一个特别数据处理集
class TushareBase(WebDataSeqBasic):
'''
仅仅只有单个请求的接口
通过tushare接口来操作数据
'''
def __init__(self,data_cls,thread_num=1,info={},id=None):
super(TushareBase,self).__init__(thread_num,info,id)
self.data_cls=data_cls
def task_gen(self):
yield self.data_cls(self.info)
def handle_data(self,data):
'''
调用数据库接口,存储数据
:param data:
:return:
'''
op=BaseOperate()
for record in data:
op.createfrtuple(record)
例子,获取股票数据,并保存
class StockData(WebDataFrameBasic):
def hanlde_data(self,data_gen):
'''
对网络数据进行特别清洗
:param data_gen:数据生成器
:return:
'''
for record in data_gen:
date=str(record.timeToMarket)
yield record._replace(timeToMarket=date)
def start(self,queue=None):
df=ts.get_stock_basics()
from etl.df_trans import df2tuple_gen
data=self.hanlde_data(df2tuple_gen(df,"Stock",
{'code':'index','name':'name','industry':'industry',
'area':'area','outstanding':'outstanding','totals':'totals',
'timeToMarket':'timeToMarket'}))
queue.put({"state":"end","data":data,"label":self.unique()})
def unique(self):
return "get_stock_basics"
二.使用数据库:postgresql
ubuntu本身集成了postgresql,所以只需要安装客户端
$apt-get install postgresql
创建postgresql的实例:
-d:实例存储目录
-e:采取编码
$pg_createcluster 9.3 xxx -u postgres -d /val/postgresql/xxx -e utf8 ###xxx为实例名字
出现错误:The locale requested by the environment is invalid.
修改环境变量,重启,(实验过放在/etc/profile,或者.profile 里面,无效果)
$echo "LC_ALL=en_US.UTF-8" >> /etc/environment
我创建的实例名称不是postgres, 使用psql登录会出现 Peer authentication failed for user "postgres"
修改/etc/postgresql/版本号/实例名/pg_hba.conf
$vi /etc/postgresql/9.3/xxx/pg_hba.conf
其中peer为使用系统的用户名,密码登录方式
md5是使用用户名,密码的登录方式
trust,信任登录方式,无密码
postgresql常用命令:查看详细情形,http://www.postgres.cn/docs/9.3/
postgres=# \l ###显示数据库 postgres=# \q ###退出 postgres=# \c 数据库名 ###转到另一个数据库 postgres=# \dt ###查看表 postgres=# \d ###查看表 postgres=# \di ###查看表索引
postgres=# \du ###查看用户
远程登录postgresql服务器
a.使用vim 修改pg_hba.conf文件,添加一行:
host all all 0.0.0.0/24 md5
其中0.0.0.0/24可以更为内网段
b.再修改postgresql.conf,将注释listen_addresses = '*' 除掉
三.数据库操作
1.sqlachemy:如果需要数据支持更好性能,可以考虑添加sqlachemy的ORM层接口
2.优化数据库:
a.大量插入一次提交:使用装饰器,对于func函数的操作进行记录,只有达到一定次数后才会进行flush
def flush_wrapper(func=None):
'''
func函数被调用length次数时候,才会进行flush
:param func:
:return:
'''
def wrap(self,*arg,**kwargs):
try:
func(self,*arg,**kwargs)
if self.count==self.length:
self.session.flush()
self.count=0
else:
self.count+=1
except Exception as e:
self.session.close()
LOG.error(str(e))
raise e
return wrap
b.cache的使用:由于数据重复插入可能性存在,所以需要每次插入都需要查询数据库,为了节省时间,可以一次性将原来数据读入到内存当中。
实现思想:
建立cache_keys:仅仅记录数据库记录的关键字字段(该字段必须能够唯一定位一条记录),caches_keys包括所有记录的关键字
建立cache:cache里面加载了全部或者部分数据库中的相关对象的记录。
部分cache的策略:
1.如果外部数据的顺序与数据库中的某种顺序一致的话,可以进行预读取部分数据的策略,比如读取100条,当没有命中时候,则按照顺序将选择框,选择当前记录以及往下的顺序100条。
更新策略:n/m(新记录,所有记录)
如果不使用cache:则进行了m次的查询,以及后面的n次插入,以及多次更新。
不重复创建:该函数读入外部数据,如果外部数据在cache_keys中找到相应值,就不予更新,继续读取下条记录了;如果没有找到,就创建记录;花费时间:进行了一次所有记录读取+进行了n次insert动作
不重复创建或者更新:该函数读入外部数据,如果外部数据在cache_keys中找到相应值,则使用当前数据更新数据库。如果没有找到则创建记录;花费时间:进行了m-n次的更新,n次插入;
def init_cache(self,key=None,maxlen=None):
'''
初始化数据库缓存
key必须是model的unique非空字段
:return:
'''
self.key=key
self.cache_keys={}#存放缓存的记录的唯一非空关键字,凭借该key可以找到并唯一找到一条记录,必须对象的全部keys
if maxlen:
self.cache=deque(maxlen=maxlen)#长度为maxlen的队列
else:
self.cache=deque()#长度无限的队列
for index ,record in enumerate(self.query.all()):
self.cache_keys[getattr(record,'code')]=weakref.ref(record)
if not maxlen or index <maxlen:
self.cache.append(record)
def is_hit(self,record):
'''
是否在keys中查到值
:return:
'''
key_value=getattr(record,self.key)
if key_value in self.cache_keys:
return self.cache_keys[key_value]
else:#当没有该记录则将该记录添加到缓存当中,并返回False
self.cache_keys[key_value]=weakref.ref(record)
self.cache.append(record)
return False
四.web框架
1.django:进行快速开发