原来我也自己通过查资料,来学习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也是同样。
1 def func_top(url): 2 data_dict= {} 3 4 #在页面上获取到子url 5 sub_urls = xxxx 6 7 data_list = [] 8 for it in sub_urls: 9 data_list.append(func_sub(it)) 10 11 data_dict[‘data‘] = data_list 12 13 return data_dict 14 15 def func_sub(url): 16 data_dict= {} 17 18 #在页面上获取到子url 19 bottom_urls = xxxx 20 21 data_list = [] 22 for it in bottom_urls: 23 data_list.append(func_bottom(it)) 24 25 data_dict[‘data‘] = data_list 26 27 return data_dict 28 29 def func_bottom(url): 30 #获取数据 31 data = xxxx 32 return data
所以实现方案也就有了:
定义一个装饰器,如果之前取到数据,就直接取cache(file_path)的数据;如果之前没有取到,那么就从网站拉取,并且存入cache中.
1 import os 2 import hashlib 3 4 def deco_args_recent_cache(category=‘dumps‘): 5 ‘‘‘ 6 装饰器,返回最新cache的数据 7 ‘‘‘ 8 def deco_recent_cache(func): 9 def func_wrapper(*args, **kargs): 10 sig = _mk_cache_sig(*args, **kargs) 11 data = _get_recent_cache(category, func.__name__, sig) 12 if data is not None: 13 return data 14 15 data = func(*args, **kargs) 16 if data is not None: 17 _set_recent_cache(category, func.__name__, sig, data) 18 return data 19 20 return func_wrapper 21 22 return deco_recent_cache 23 24 def _mk_cache_sig(*args, **kargs): 25 ‘‘‘ 26 通过传入参数,生成唯一标识 27 ‘‘‘ 28 src_data = repr(args) + repr(kargs) 29 m = hashlib.md5(src_data) 30 sig = m.hexdigest() 31 return sig 32 33 def _get_recent_cache(category, func_name, sig): 34 full_file_path = ‘%s/%s/%s‘ % (category, func_name, sig) 35 if os.path.isfile(full_file_path): 36 return eval(file(full_file_path,‘r‘).read()) 37 else: 38 return None 39 40 def _set_recent_cache(category, func_name, sig, data): 41 full_dir_path = ‘%s/%s‘ % (category, func_name) 42 if not os.path.isdir(full_dir_path): 43 os.makedirs(full_dir_path) 44 45 full_file_path = ‘%s/%s/%s‘ % (category, func_name, sig) 46 f = file(full_file_path, ‘w+‘) 47 f.write(repr(data)) 48 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