美文网首页Scrapy框架的点点滴滴
Scrapy框架之Scrapy-Redis功能扩充

Scrapy框架之Scrapy-Redis功能扩充

作者: HideOnStream | 来源:发表于2019-02-25 18:03 被阅读0次

    关于Scrapy-Redis源码的小小研究, 并在此基础上修改其使用的serializer和fingerprint

    前提

    熟练使用Scrapy框架, 已经安装了Redis服务器, Scrapy框架, 并且已经将Scrapy-Redis插件源码整合到Scrapy项目中

    如果不熟悉Scrapy-Redis的使用, 参考前面一篇文章, Scrapy框架之Scrapy-Redis的两种使用方式

    一. 修改serializer

    使用Scrapy-Redis后, 往Scrapy-Redis调度器中推送的种子, 会被Scrapy-Redis使用pickle序列化工具将其Request对象以二进制形式存储到Redis中, 例如:

    \x80\x04\x95E\x01\x00\x00\x00\x00\x00\x00}\x94(\x8C\x03url\x94\x8C\x1
    3http://www.sina.com\x94\x8C\x08callback\x94\x8C\x05parse\x94\x8C\x0
    7errback\x94N\x8C\x06method\x94\x8C\x03GET\x94\x8C\x07headers\x9
    4}\x94C\x0AUser-Agent\x94]\x94CiMozilla/5.0 (X11; Linux x86_64) 
    AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 
    Safari/537.36\x94as\x8C\x04body\x94C\x00\x94\x8C\x07cookies\x94}\x9
    4\x8C\x04meta\x94}\x94\x8C\x04item\x94}\x94\x8C\x02xx\x94\x8C\x02o
    o\x94ss\x8C\x09_encoding\x94\x8C\x05utf8\x94\x8C\x08
    priority\x94K\x00\x8C\x0Bdont_filter\x94\x88\x8C\x05flags\x94]\x94u.
    

    这种格式是极难看的, 不方便我们进行调试

    我们现在通过修改这个serializer, 使它采用json序列化工具来保存Request.
    查看一下Scrapy-Redis源码, 先看一下queue.py文件

            if serializer is None:
                # Backward compatibility.
                # TODO: deprecate pickle.
                serializer = picklecompat
            if not hasattr(serializer, 'loads'):
                raise TypeError("serializer does not implement 'loads' function: %r"
                                % serializer)
            if not hasattr(serializer, 'dumps'):
                raise TypeError("serializer '%s' does not implement 'dumps' function: %r"
                                % serializer)
    

    可以看到如果没有设置serializer则使用默认的serializer(即picklecompat),所以这里我们再看一下picklecompat.py文件:

    """A pickle wrapper module with protocol=-1 by default."""
    
    try:
        import cPickle as pickle  # PY2
    except ImportError:
        import pickle
    
    
    def loads(s):
        return pickle.loads(s)
    
    
    def dumps(obj):
        return pickle.dumps(obj, protocol=-1)
    
    

    它提供了两个方法, loads和dumps, 毫无疑问,一个是序列化,一个是反序列化, 也就是说如果我们希望使用json来代替pickle, 只需要遵循这个协议,提供loads和dumps方式即可.

    到这里, 我们在Scrapy-Redis目录下新建一个文件为jsoncompat.py文件, 然后编写以下内容:

    import json
    from scrapy import Item
    from tutorial.scrapy_redis.utils import convert_str
    
    def loads(s):
        if type(s) is bytes:
            s = s.decode()
        t = json.loads(s)
        if 'body' in t.keys():
            t['body'] = bytes(t['body'], encoding='utf-8')
        return t
    
    def dumps(obj):
        meta = obj['meta']
        for k in meta.keys():
            if issubclass(type(meta[k]), Item):
                meta[k] = dict(meta[k])
        if 'body' in obj.keys():
            obj['body'] = obj['body'].decode()
        if 'headers' in obj.keys():
            obj['headers'] = convert_str(obj['headers'])
        return json.dumps(obj)
    

    其中scrapy_redis.utils中的convert_str方法为:

    def convert_str(data):
        if isinstance(data, bytes):
            return data.decode()
        if isinstance(data, dict):
            return dict(map(convert_str, data.items()))
        if isinstance(data, tuple):
            return map(convert_str, data)
        if isinstance(data, list):
            return data[0].decode()
        return data
    

    然后修改之前的那个queue.py文件,

            if serializer is None:
                # Backward compatibility.
                # TODO: deprecate pickle.
                serializer = jsoncompat
            if not hasattr(serializer, 'loads'):
                raise TypeError("serializer does not implement 'loads' function: %r"
                                % serializer)
            if not hasattr(serializer, 'dumps'):
                raise TypeError("serializer '%s' does not implement 'dumps' function: %r"
                                % serializer)
    

    一切准备就绪, 然后往调度器中重新push一条任务, 可以看到, 最终效果:

    {
        "url":"[http://www.weixin.com](http://www.weixin.com/)",
        "callback":"parse",
        "errback":null,
        "method":"GET",
        "headers":{
            "User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36"
        },
        "body":"",
        "cookies":{
    
        },
        "meta":{
            "item":{
                "xx":"oo"
            }
        },
        "_encoding":"utf-8",
        "priority":0,
        "dont_filter":true,
        "flags":[
        ]
    }
    

    另外, 上述修改serializer的方式还可以通过修改settings文件来做到, 具体方法是编写完jsoncompat文件后, 不改变queue文件而是在settings增加这行配置:

    SCHEDULER_SERIALIZER = jsoncompat
    

    之所以这样做也可以生效, 是因为在Scrapy-Redis的scheduler文件中有留有说明:

           optional = {
                # TODO: Use custom prefixes for this settings to note that are
                # specific to scrapy-redis.
                'queue_key': 'SCHEDULER_QUEUE_KEY',
                'queue_cls': 'SCHEDULER_QUEUE_CLASS',
                'dupefilter_key': 'SCHEDULER_DUPEFILTER_KEY',
                # We use the default setting name to keep compatibility.
                'dupefilter_cls': 'DUPEFILTER_CLASS',
                'serializer': 'SCHEDULER_SERIALIZER',
            }
    

    这五个参数可以通过在settings中设置指定的参数来指定(比如想修改serializer, 则可以在settings中设置参数SCHEDULER_SERIALIZER来指定). 如果settings文件没有指定,则使用默认值;

    二. 修改fingerprint

    Scrapy-Redis在dupefilter实现了根据指纹去重(实际上简单的调用了一下Scrapy的指纹去重), 代码如下:

        def request_fingerprint(self, request):
            """Returns a fingerprint for a given request.
    
            Parameters
            ----------
            request : scrapy.http.Request
    
            Returns
            -------
            str
    
            """
            return request_fingerprint(request)
    

    我们可以看到,去重指纹是sha1(method + url + body + header); 所以,实际能够去掉重复的比例并不大。如果我们需要自己提取去重的finger,需要自己实现Filter,并配置上它。

    下面这个Filter只根据url的md5码去重:

        def request_fingerprint(self, request):
            """Returns a fingerprint for a given request.
    
            Parameters
            ----------
            request : scrapy.http.Request
    
            Returns
            -------
            str
    
            """
            return md5(request.url)
    

    住: 另外一种方式类似于上边修改serializer, 直接新建一个文件, 包含新的指纹去重算法, 然后在settings中指定即可.

    相关文章

      网友评论

        本文标题:Scrapy框架之Scrapy-Redis功能扩充

        本文链接:https://www.haomeiwen.com/subject/zsjsyqtx.html