最后更新于 .

好吧,我知道是大半夜……,但我还是觉得赶紧花上半个小时,把这最新的想法分享出来是值得的~直接进入正题~ 我们来模拟一个场景,需要你去抓去一个页面,然后这个页面有好多url也要分别去抓取,而进入这些子url后,还有数据要抓取。简单点,我们就按照三层来看,那我们的代码就是如下:

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

func_top是上层页面的处理函数,func_sub是子页面的处理函数,func_bottom是最深层页面的处理函数,func_top会在取到子页面url后遍历调用func_sub,func_sub也是同样。 如果正常情况下,这样确实已经满足需求了,但是偏偏这个你要抓取的网站可能极不稳定,经常链接不上,导致数据拿不到。 于是这个时候你有两个选择:

  • 1.遇到错误就停止,之后重新从断掉的位置开始重新跑
  • 2.遇到错误继续,但是要在之后重新跑一遍,这个时候已经有的数据不希望再去网站拉一次,而只去拉没有取到的数据

对第一种方案基本无法实现,因为如果别人网站的url调整顺序,那么你记录的位置就无效了。那么只有第二种方案,说白了,就是要把已经拿到的数据cache下来,等需要的时候,直接从cache里面取。 OK,目标已经有了,怎么实现呢? 如果是在C++中的,这是个很麻烦的事情,而且写出来的代码必定丑陋无比,然而庆幸的是,我们用的是python,而python对函数有装饰器。 所以实现方案也就有了: 定义一个装饰器,如果之前取到数据,就直接取cache的数据;如果之前没有取到,那么就从网站拉取,并且存入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这个装饰器即可~~ 搞定!这样做最大的好处在于,因为top,sub,bottom,每一层都会dump数据,所以比如某个sub层数据dump之后,是根本不会走到他所对应的bottom层的,减少了大量的开销! OK,就这样~ 人生苦短,我用python!


感谢依云的提醒,python3居然原生支持了这种功能!链接如下: http://docs.python.org/py3k/whatsnew/3.2.html#functools

Pingbacks

  1. 装饰器的几个用途【Python】 on #

    [...] 这篇文章也是说这个问题(http://www.vimer.cn/?p=2124),这个例子更为精炼(http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize) [...]

Pingbacks已打开。

Trackbacks

引用地址

评论

  1. 依云

    依云 on #

    嗯,我就喜欢这样的装饰品,所以毫不犹豫地装了 Python 3.2——
    http://docs.python.org/py3k/whatsnew/3.2.html#functools

    Reply

    1. 依云

      依云 on #

      s/品/器

      Reply

    2. Dante

      Dante on #

      python居然原生支持了了,太强了!
      啊,开始纠结是不是要往python3上迁移了。。。

      Reply

      1. 依云

        依云 on #

        迁移吧,Vim 的 Python3 支持的 bug 我都搞定了,一直都没人测试呢。。。

        Reply

        1. Dante

          Dante on #

          唉,django一直不肯支持python3,也是我一直不敢迁移的原因。。

          Reply

  2. ==

    == on #

    请问,win 上的 gvim 可以设置 程序半透明 或者 背景图片 吗?谢谢。网上的都是 linux 下在终端中用 vim 并设置终端透明

    Reply

  3. saalihmao

    saalihmao on #

    对python不是很熟,不过读读代码看上去就是装饰器模式嘛~ 这个所有带有一点函数式味道的语言都可以实现的,c++也是可以的,函数对象+装饰器模式就可以~ 不丑陋的 : )

    Reply

  4. saalihmao

    saalihmao on #

    大概是这样
    class base
    {
    public:
    int operator()(char* some_para) = 0;
    };

    class deco:public base
    {
    base* pBase;
    public:
    explicit deco(const base* p){...;}

    int operator()(char* some_para)
    {
    //do something
    ...
    (*pBase)(some_para);
    ....
    //still do something
    }
    }

    Reply

  5. saalihmao

    saalihmao on #

    呃 deco类的构造函数参数类型应该把const去掉~ : ) 失误失误

    Reply

    1. Dante

      Dante on #

      不过总感觉c++用某些设计模式会比较别扭,包括语法,效率,之类的,之前写bayonet项目时,c++用到状态模式,到处是指针,挺郁闷。。

      还有,博客支持pre标签哦,代码显示会友好一些,留言页面有说明~

      Reply

      1. saalihmao

        saalihmao on #

        c++的面向对象方面确实是这样~~ 一旦用到继承之类的就满天指针,不方便,而且没有GC内存分配的效率也很成问题~~

        不过也不一定非要用对象嘛~ 类型也是可以的,就像这个问题这样可能更好些
        <pre lang="cpp" line="14">
        //方便起见, 约定这些函数/函数类型参数都是char*, 返回类型都是int
        template
        class deco
        {
        public:
        int operator()(char* some_para)
        {
        //do sth

        func(some_para);

        //still do sth
        }
        };
        </pre>

        Reply

        1. saalihmao

          saalihmao on #

          呵呵pre标签不支持template ...?

          Reply

          1. Dante

            Dante on #

            呃,你是说尖括号转义了吗?

            Reply

            1. saalihmao

              saalihmao on #

              嗯= = 尖括号被吃掉了~ 应该怎么写?~

              Reply

              1. Dante

                Dante on #

                <pre lang="cpp" line="1">
                template <T>
                </pre>

                我试一下

                Reply

                1. Dante

                  Dante on #

                  好像把lang写上就可以了,我写的是cpp类型。

                  Reply

                  1. saalihmao

                    saalihmao on #

                    测试下...
                    <pre lang="cpp">
                    template
                    class deco
                    {
                    public:
                    int operator()(char* some_para)
                    {
                    //do sth

                    func(some_para);

                    //still do sth
                    }
                    };
                    </pre>

                    Reply

                    1. saalihmao

                      saalihmao on #

                      还是不行... 换个浏览器

                      <pre lang="cpp">
                      template
                      template
                      template
                      </pre>

                      Reply

  6. 虫子的海飞丝

    虫子的海飞丝 on #

    def _get_recent_cache(category, func_name, sig):这个装饰器有缺陷啊,func_name这个参数还不如去掉。

    Reply

    1. Dante

      Dante on #

      对,确实冗余了,以前还不熟悉

      Reply

发表评论