原来我也自己通过查资料,来学习python的装饰器,但是效果不好。因为没有接触过需要用到装饰器的场景,所以
一起的资料都只停留在纸面上,但是今天偶然看到了vimer的这篇文章:http://www.vimer.cn/2011/04/python%E8%A3%85%E9%A5%B0%E5%99%A8%E7%9A%84%E4%B8%80%E4%B8%AA%E5%A6%99%E7%94%A8.html
我们就根据这篇文章的思路来,在场景中理解python装饰器
其中的一个场景是:爬取数据的时候,目标网站,不稳定。爬虫在爬取数据的时候可能异常。
解决方案:
思路:把爬中的每个方法爬取的数据,都存在硬盘中,存储规则如下:
file_path:/method_name/
file_name:MD5_number.txt
解释:MD5_number是通过方法名和传入的参数计算出来的唯一标识符(搜索一下MD5)这样做的好 处,不需要把数据爬下来之后才能判断是否爬过。
最后:如果方法爬取目标url的数据时异常,暂不执行该方法。最后多跑几次这个爬虫脚本就可以了!
该爬虫调用三个方法,设计一个通用的方法:根据每个方法的方法名和传入的参数通过MD5计算出唯一的标识符(MD5_numbe)。然后在file_path目录中查找文件名为MD5_numbe的文件,如果file_path中的没有这个文件,则按照存储规则存储数据。如果找到了,不存储到硬盘中,直接返回data,用于下一个方法。
————————————
场景结束
++++++++++++
在上面描述的这个场景中,修饰器用在什么地方?
对了!就是那个通用方法,就需要用修饰器来实现,为什么?一步步的来
python中一切东西都是对象。方法就可以做当作对象来传递!
1.什么是方法?
传入参数,然后对传入的参数进行一系列操作。
2.什么是高级方法
方法接受的参数是方法,仅此而已。
3.什么是闭包
当方法1里面有一个方法2,方法2调用的是方法1接收到的参数,最后结果返回方法2。提供这种实现的技术叫做闭包。
说到底修饰器就是一个方法,而传入的参数是方法,并在修饰器中对传入的方法进行一系列的操作(这里用到闭包)。从而实现了,在不修改原方法基础上,增加新的操作。比如上一个场景中的,检验有没有重复爬取数据。
爬中脚本中的代码片段
func_top是上层页面的处理函数,func_sub是子页面的处理函数,func_bottom是最深层页面的处理函数,func_top会在取到子页面url后遍历调用func_sub,func_sub也是同样。
def func_top(url):
data_dict= {} #在页面上获取到子url
sub_urls = xxxx data_list = []
for it in sub_urls:
data_list.append(func_sub(it)) data_dict['data'] = data_list return data_dict def func_sub(url):
data_dict= {} #在页面上获取到子url
bottom_urls = xxxx data_list = []
for it in bottom_urls:
data_list.append(func_bottom(it)) data_dict['data'] = data_list return data_dict def func_bottom(url):
#获取数据
data = xxxx
return data
所以实现方案也就有了:
定义一个装饰器,如果之前取到数据,就直接取cache(file_path)的数据;如果之前没有取到,那么就从网站拉取,并且存入cache中.
import os
import hashlib def deco_args_recent_cache(category='dumps'):
'''
装饰器,返回最新cache的数据
'''
def deco_recent_cache(func):
def func_wrapper(*args, **kargs):
sig = _mk_cache_sig(*args, **kargs)
data = _get_recent_cache(category, func.__name__, sig)
if data is not None:
return data data = func(*args, **kargs)
if data is not None:
_set_recent_cache(category, func.__name__, sig, data)
return data return func_wrapper return deco_recent_cache def _mk_cache_sig(*args, **kargs):
'''
通过传入参数,生成唯一标识
'''
src_data = repr(args) + repr(kargs)
m = hashlib.md5(src_data)
sig = m.hexdigest()
return sig def _get_recent_cache(category, func_name, sig):
full_file_path = '%s/%s/%s' % (category, func_name, sig)
if os.path.isfile(full_file_path):
return eval(file(full_file_path,'r').read())
else:
return None def _set_recent_cache(category, func_name, sig, data):
full_dir_path = '%s/%s' % (category, func_name)
if not os.path.isdir(full_dir_path):
os.makedirs(full_dir_path) full_file_path = '%s/%s/%s' % (category, func_name, sig)
f = file(full_file_path, 'w+')
f.write(repr(data))
f.close()
最后,我们只需要在每个func_top,func_sub,func_bottom都加上deco_args_recent_cache这个装饰器即可~~
参考资料:
浅显理解闭包:https://serholiu.com/python-closures
修饰器:http://jnotes.googlecode.com/svn/trunk/Notes/NotesOnPythonLearning/Python_decorator.html