美文网首页
Scrayp-集成scrapy_redis和bloomfilte

Scrayp-集成scrapy_redis和bloomfilte

作者: 中乘风 | 来源:发表于2018-07-13 15:25 被阅读0次

    前言

    (备注一下,我的开发环境不是Linux就是MacOSX,Windows很多写法不是这样的)

    在爬取数据的过程中,有时候需要用到定时、增量爬取。定时这里暂且不说,先说增量爬取。

    • 我想要的增量爬取目前只是简单的,根据url请求来判断是否爬过,如果爬过则不再爬。
    • 复杂一些的增量则是重复爬取,根据指定的几个字段判断是否值有变化,值有变化也算作增量,应当爬取且只更新变化部分(比如天猫商品数据,商品的价格有变化则更新价格,但是url是重复的,也应当爬取)

    网上增量爬取的文章很多,包括看过慕课网Scrapy课的笔记,但是它还是不完善,我将在这个基础上进行实际集成。


    布隆简介

    Bloom Filter是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。Bloom Filter的这种高效是有一定代价的:在判断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误认为属于这个集合(false positive)。因此,Bloom Filter不适合那些“零错误”的应用场合。而在能容忍低错误率的应用场合下,Bloom Filter通过极少的错误换取了存储空间的极大节省。

    输入图片说明

    具体的bloomfilter概念和原理应该查看这篇文章:传送,还有《海量数据处理算法》以及《大规模数据处理利器》

    布隆优点

    相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数。另外, Hash 函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。

    布隆过滤器可以表示全集,其它任何数据结构都不能;

    k 和 m 相同,使用同一组 Hash 函数的两个布隆过滤器的交并差运算可以使用位操作进行。

    布隆缺点

    但是布隆过滤器的缺点和优点一样明显。误算率(False Positive)是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。

    另外,一般情况下不能从布隆过滤器中删除元素. 我们很容易想到把位列阵变成整数数组,每插入一个元素相应的计数器加1, 这样删除元素时将计数器减掉就可以了。然而要保证安全的删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。

    总的来说,布隆很适合来处理海量的数据,而且速度优势很强。


    redis与bloom

    去重”是日常工作中会经常用到的一项技能,在爬虫领域更是常用,并且规模一般都比较大。参考文章《基于Redis的Bloomfilter去重》,作者【九茶】还有另一篇文章可以参考《scrapy_redis去重优化,已有7亿条数据》

    去重需要考虑两个点:去重的数据量、去重速度。

    为了保持较快的去重速度,一般选择在内存中进行去重。

    • 数据量不大时,可以直接放在内存里面进行去重,例如python可以使用set()进行去重。
    • 当去重数据需要持久化时可以使用redis的set数据结构。
      
    • 当数据量再大一点时,可以用不同的加密算法先将长字符串压缩成 16/32/40 个字符,再使用上面两种方法去重;
      
    • 当数据量达到亿(甚至十亿、百亿)数量级时,内存有限,必须用“位”来去重,才能够满足需求。Bloomfilter就是将去重对象映射到几个内存“位”,通过几个位的 0/1值来判断一个对象是否已经存在。
      
    • 然而Bloomfilter运行在一台机器的内存上,不方便持久化(机器down掉就什么都没啦),也不方便分布式爬虫的统一去重。如果可以在Redis上申请内存进行Bloomfilter,以上两个问题就都能解决了。
      
    1. Bloomfilter算法如何使用位去重,这个百度上有很多解释。简单点说就是有几个seeds,现在申请一段内存空间,一个seed可以和字符串哈希映射到这段内存上的一个位,几个位都为1即表示该字符串已经存在。插入的时候也是,将映射出的几个位都置为1。

    2. 需要提醒一下的是Bloomfilter算法会有漏失概率,即不存在的字符串有一定概率被误判为已经存在。这个概率的大小与seeds的数量、申请的内存大小、去重对象的数量有关。下面有一张表,m表示内存大小(多少个位),n表示去重对象的数量,k表示seed的个数。例如我代码中申请了256M,即1<<31(m=2^31,约21.5亿),seed设置了7个。看k=7那一列,当漏失率为8.56e-05时,m/n值为23。所以n = 21.5/23 = 0.93(亿),表示漏失概率为8.56e-05时,256M内存可满足0.93亿条字符串的去重。同理当漏失率为0.000112时,256M内存可满足0.98亿条字符串的去重。

    3. 基于Redis的Bloomfilter去重,其实就是利用了Redis的String数据结构,但Redis一个String最大只能512M,所以如果去重的数据量大,需要申请多个去重块(代码中blockNum即表示去重块的数量)。

    4. 代码中使用了MD5加密压缩,将字符串压缩到了32个字符(也可用hashlib.sha1()压缩成40个字符)。它有两个作用,一是Bloomfilter对一个很长的字符串哈希映射的时候会出错,经常误判为已存在,压缩后就不再有这个问题;二是压缩后的字符为 0~f 共16中可能,我截取了前两个字符,再根据blockNum将字符串指定到不同的去重块进行去重

    总结:基于Redis的Bloomfilter去重,既用上了Bloomfilter的海量去重能力,又用上了Redis的可持久化能力,基于Redis也方便分布式机器的去重。在使用的过程中,要预算好待去重的数据量,则根据上面的表,适当地调整seed的数量和blockNum数量(seed越少肯定去重速度越快,但漏失率越大)。


    编写代码

    安装依赖

    根据github上的资源《BloomFilter_imooc》以及思路来编写bloomfilter的代码。

    先前说过,bloom是一种算法,而不是插件也不是软件,它依赖于mmh3,所以需要在虚拟环境中安装mmh3.

    然而当我在本机的anaconda虚拟环境内安装时,出现了报错:

    g++: error trying to exec 'cc1plus': execvp: 没有那个文件或目录

    网上查阅了很多文章,找到一个适合我的:传送,大致原因是电脑上的gcc版本与g++版本不一致引起的。可以打开终端用命令:

    gcc -v
    
    g++ -v
    

    来查看两个东西的版本,最终发现用g++的时候报错,于是我安装它:

    sudo apt-get install g++
    
    

    如果是在阿里云服务器,命令改成:

    yum install gcc-c++
    

    安装成功后,再次到anaconda虚拟环境中安装mmh3,才成功安装。


    编写bloom代码

    根据文章《将bloomfilter(布隆过滤器)集成到scrapy-redis中》的指引,作者是将github代码下载到本地目录。

    而我为了省事,我在site-package里面写。
    在site-package下新建bloofilter_scrapy_redis的package包(带init那种),然后在里面新建文件bloomfilter.py,编写代码:

    # -*- coding: utf-8 -*-
    # 18-1-21 下午2:22
    # RanboSpider
    
    import mmh3
    import redis
    import math
    import time
    
    
    class PyBloomFilter():
        #内置100个随机种子,种子越多需要的内存就越大,内存小的服务器用30个种子就行了
        SEEDS = [543, 460, 171, 876, 796, 607, 650, 81, 837, 545, 591, 946, 846, 521, 913, 636, 878, 735, 414, 372,
                 344, 324, 223, 180, 327, 891, 798, 933, 493, 293, 836, 10, 6, 544, 924, 849, 438, 41, 862, 648, 338,
                 465, 562, 693, 979, 52, 763, 103, 387, 374, 349, 94, 384, 680, 574, 480, 307, 580, 71, 535, 300, 53,
                 481, 519, 644, 219, 686, 236, 424, 326, 244, 212, 909, 202, 951, 56, 812, 901, 926, 250, 507, 739, 371,
                 63, 584, 154, 7, 284, 617, 332, 472, 140, 605, 262, 355, 526, 647, 923, 199, 518]
    
        #capacity是预先估计要去重的数量
        #error_rate表示错误率
        #conn表示redis的连接客户端
        #key表示在redis中的键的名字前缀
        def __init__(self, capacity=1000000000, error_rate=0.00000001, conn=None, key='BloomFilter'):
            self.m = math.ceil(capacity*math.log2(math.e)*math.log2(1/error_rate))      #需要的总bit位数
            self.k = math.ceil(math.log1p(2)*self.m/capacity)                           #需要最少的hash次数
            self.mem = math.ceil(self.m/8/1024/1024)                                    #需要的多少M内存
            self.blocknum = math.ceil(self.mem/512)                                     #需要多少个512M的内存块,value的第一个字符必须是ascii码,所有最多有256个内存块
            self.seeds = self.SEEDS[0:self.k]
            self.key = key
            self.N = 2**31-1
            self.redis = conn
            # print(self.mem)
            # print(self.k)
    
        def add(self, value):
            name = self.key + "_" + str(ord(value[0])%self.blocknum)
            hashs = self.get_hashs(value)
            for hash in hashs:
                self.redis.setbit(name, hash, 1)
    
        def is_exist(self, value):
            name = self.key + "_" + str(ord(value[0])%self.blocknum)
            hashs = self.get_hashs(value)
            exist = True
            for hash in hashs:
                exist = exist & self.redis.getbit(name, hash)
            return exist
    
        def get_hashs(self, value):
            hashs = list()
            for seed in self.seeds:
                hash = mmh3.hash(value, seed)
                if hash >= 0:
                    hashs.append(hash)
                else:
                    hashs.append(self.N - hash)
            return hashs
    
    
    pool = redis.ConnectionPool(host='127.0.0.1', port=6379, db=0)
    conn = redis.StrictRedis(connection_pool=pool)
    

    这里的pool和conn都是单独连接的,实际上在分布式爬虫中是比较不友好的,多台机器的配置就会烦人,这里暂且这样,后期我再改。

    是否配置密码

    至于是否配置密码,如何配置密码,在bloomfilter.py文件中,有一句:

    pool = redis.ConnectionPool(host='127.0.0.1', port=6379, db=0)
    conn = redis.StrictRedis(connection_pool=pool)
    

    其中redis.StrictRedis方法,跟踪(ctrl+左键点击)进去,可以看到init初始化方法里面有个password=None

    def __init__(self, host='localhost', port=6379,
                     db=0, password=None, socket_timeout=None,
                     socket_connect_timeout=None,
                     socket_keepalive=None, socket_keepalive_options=None,
                     connection_pool=None, unix_socket_path=None,
                     encoding='utf-8', encoding_errors='strict',
                     charset=None, errors=None,
                     decode_responses=False, retry_on_timeout=False,
                     ssl=False, ssl_keyfile=None, ssl_certfile=None,
                     ssl_cert_reqs=None, ssl_ca_certs=None,
                     max_connections=None):
    

    这里应该是设置password,也就是将服务器redis的权限密码auth设置进来。

    pool = redis.ConnectionPool(host='47.98.110.67', port=6379, db=0, password='quinns')
    conn = redis.StrictRedis(connection_pool=pool)
    

    即可完成密码的设置。


    集成到scrapy_redis中

    上面的布隆过滤器代码写好后,需要集成到scrapy_redis中。完成去重任务的是dupefilter.py文件,就要对它进行改造,路径是site-package/scrapy_redis/目录内:

    现将刚才编写的布隆选择器导入此文件

    from bloomfilter_scrapy_redis.bloomfilter import conn,PyBloomFilter  # 从源码包导入布隆
    

    然后在init方法中初始化布隆选择器(这里贴上整个init代码):

        def __init__(self, server, key, debug=False):
            """Initialize the duplicates filter.
    
            Parameters
            ----------
            server : redis.StrictRedis
                The redis server instance.
            key : str
                Redis key Where to store fingerprints.
            debug : bool, optional
                Whether to log filtered requests.
    
            """
            self.server = server
            self.key = key
            self.debug = debug
            self.logdupes = True
    
           """ 集成布隆过滤器,通过连接池连接redis """
            self.bf = PyBloomFilter(conn=conn, key=key)
    

    接下来改动request_seen方法,在里面对request进行判断,如果此次request请求在redis中存在,则直接返回,如果不存在则添加到redis的队列里面去,让爬虫去爬:

        def request_seen(self, request):
            """
                ……
            """
           
            fp = self.request_fingerprint(request)
    
            """
            集成布隆过滤
                判断redis是否存在此指纹,如果存在则直接返回true
                如果不存在添加指纹到redis,同时返回false
            """
            if self.bf.is_exist(fp):
                return True
            else:
                self.bf.add(fp)
                return False
    
            """ 集成布隆过滤器,将下方2行代码注释 """
            # This returns the number of values added, zero if already exists.
            # added = self.server.sadd(self.key, fp)
            # return added == 0
    

    到这里即完成了scrapy_redis对布隆过滤器的集成。


    测试

    在爬虫代码中编写:

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy_redis.spiders import RedisSpider
    from scrapy.http import Request
    from urllib import parse
    
    
    class JobboleSpider(RedisSpider):
        name = 'jobbole'
        allowd_domains = ["www.gxnhyd.com"]
        redis_key = 'jobbole:start_urls'
    
        def parse(self, response):
            """
            将当前列表页的每条标的链接拿到 并传给detail进行深入爬取
            通过已知列表页码数量 进行循环爬取 就不用翻页了
            """
            total = response.css('.item .tl.pl10 a')
            for x in total:
                title = x.css('::text').extract_first("")
                title_url = x.css('::attr(href)').extract_first("")
                yield Request(url=parse.urljoin(response.url, title_url), callback=self.parse_detail)
    
            for i in range(1, 10):
                next_pages = "http://www.gxnhyd.com/deals/p-%s" % (i)
                yield Request(url=next_pages, callback=self.parse)
    
        def parse_detail(self, response):
            """
            获取当前详情页的标的信息 包括金额 收益 期限 借款人
                投资人列表 - 投资人用户名/投资人投资金额/投资方式/投资时间等
            :param response:
            :return:
            """
            print(response.url)
    

    通过print对爬取情况做观察

    开启爬虫后,由于scrapy_redis的特性,需要给redis里面添加start_urls:

    lpush jobbole:start_urls http://www.gxnhyd.com/deals [value ...]
    
    

    爬虫监听到值之后,立即开始爬取,这一步没问题

    但是爬完后它空跑了,不会结束,一直空跑。(事实证明,跑空了也不要紧)


    二次测试

    在第一次测试通过后,我加大了循环次数for i in range(1, 30),看看是否会出现重复的值,结果报错了。

    报错信息与bloom是否重复无关,原因是我之前看到空跑,就主动停止了代码,导致redis报错:

    MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist

    解决办法在这里《redis异常解决:MISCONF Redis 》,在redis-cli用命令解决这个权限问题:

    config set stop-writes-on-bgsave-error no
    

    二次测试后,发现可以正常运行了。然后观察到bloom也生效了,但是还是有空跑的问题


    解决空跑(这个办法其实不太好,不推荐)

    空跑就是爬虫在爬取完所有的队列有,不会自动停止,而是一直请求请求,然后观察redis-server窗口有memory的提示一直在进行。

    解决这个空跑问题参考了一些资料《scrapy-redis所有request爬取完毕,如何解决爬虫空跑问题? 》

    输入图片说明

    根据scrapy-redis分布式爬虫的原理,多台爬虫主机共享一个爬取队列。当爬取队列中存在request时,爬虫就会取出request进行爬取,如果爬取队列中不存在request时,爬虫就会处于等待状态.

    可是,如果所有的request都已经爬取完毕了呢?这件事爬虫程序是不知道的,它无法区分结束和空窗期状态的不同,所以会一直处于上面的那种等待状态,也就是我们说的空跑。

    那有没有办法让爬虫区分这种情况,自动结束呢?


    • 从背景介绍来看,基于scrapy-redis分布式爬虫的原理,爬虫结束是一个很模糊的概念,在爬虫爬取过程中,爬取队列是一个不断动态变化的过程,随着request的爬取,又会有新的request进入爬取队列。进进出出。
    • 爬取速度高于填充速度,就会有队列空窗期(爬取队列中,某一段时间会出现没有request的情况),爬取速度低于填充速度,就不会出现空窗期。所以对于爬虫结束这件事来说,只能模糊定义,没有一个精确的标准。

    可以通过限定爬虫自动关闭时间来完成这个任务,在settings配置:

    # 爬虫运行超过23.5小时,如果爬虫还没有结束,则自动关闭
    CLOSESPIDER_TIMEOUT = 84600
    

    特别注意 :如果爬虫在规定时限没有把request全部爬取完毕,此时强行停止的话,爬取队列中就还会存有部分request请求。那么爬虫下次开始爬取时,一定要记得在master端对爬取队列进行清空操作。


    想象一下,爬虫已经结束的特征是什么?

    那就是爬取队列已空,从爬取队列中无法取到request信息。那着手点应该就在从爬取队列中获取request和调度这个部分。查看scrapy-redis源码,我们发现了两个着手点,调度器site-packages\scrapy_redis\schedluer.py和site-packages\scrapy_redis\spiders.py爬虫。

    但是爬虫在爬取过程中,队列随时都可能出现暂时的空窗期。想判断爬取队列为空,一般是设定一个时限,如果在一个时段内,队列一直持续为空,那我们可以基本认定这个爬虫已经结束了。

    我选择更改调度器,site-packages\scrapy_redis\schedluer.py所以有了如下的改动:

    首先在init里面设定一个初始次数

    import datetime
    
        def __init__(self, server,
                  ……
                    ……
    
            """
    
            """ 为解决空跑问题:设定倒计次数 下方根据次数决定何时关闭爬虫,避免空跑"""
            self.lostGetRequest = 0
    
            if idle_before_close < 0:
                ……
                    ……
    

    完整的init方法代码为:

     def __init__(self, server,
                     persist=False,
                     flush_on_start=False,
                     queue_key=defaults.SCHEDULER_QUEUE_KEY,
                     queue_cls=defaults.SCHEDULER_QUEUE_CLASS,
                     dupefilter_key=defaults.SCHEDULER_DUPEFILTER_KEY,
                     dupefilter_cls=defaults.SCHEDULER_DUPEFILTER_CLASS,
                     idle_before_close=0,
                     serializer=None):
          
    
            """ 为解决空跑问题:设定倒计次数 下方根据次数决定何时关闭爬虫,避免空跑"""
            self.lostGetRequest = 0
    
            if idle_before_close < 0:
                raise TypeError("idle_before_close cannot be negative")
    
            self.server = server
            self.persist = persist
            self.flush_on_start = flush_on_start
            self.queue_key = queue_key
            self.queue_cls = queue_cls
            self.dupefilter_cls = dupefilter_cls
            self.dupefilter_key = dupefilter_key
            self.idle_before_close = idle_before_close
            self.serializer = serializer
            self.stats = None
    

    然后到next_request方法中进行修改:

        def next_request(self):
            block_pop_timeout = self.idle_before_close
            request = self.queue.pop(block_pop_timeout)
            if request and self.stats:
                """ 解决空跑问题,这里判断如果获取到request则重置倒计时lostGetRequest """
                self.lostGetRequest = 0
                self.stats.inc_value('scheduler/dequeued/redis', spider=self.spider)
            if request is None:
                """ 
                scrapy_reids跑完数据后不会自动停止,会产生空跑情况,一直空跑 
                    每次调度Schedule时如果队列没有数据  则倒计时+1
                    50次空跑大约费时5分钟,根据项目需求设定次数,满足空跑次数则主动停止并填写停止原因
                """
                self.lostGetRequest += 1
                if self.lostGetRequest > 10:
                    self.spider.crawler.engine.close_spider(self.spider, 'Queue is empty,So active end')
            return request
    

    这样就可以解决空跑的问题了。(事实证明,高兴得太早)


    真正解决空跑(这个也不好,不建议。因为scrapy_redis已处理空跑问题(我也不确定))

    真是太年轻,不懂事,我以为按照别人的想法实施,就可以解决空跑的问题了。然后当自己亲自测试的时候,发现并不是那么回事。

    scrapy是异步的,而且request队列确实会有空闲状态,如果有空闲状态就会+1,用数字进行累加的话,虽然上编写了重置为0的操作,但貌似是不行的,测试没有那么细致,反正当空闲状态达到N次(关闭条件)的时候,就会自动关闭(request队列还在抽取,也会被关闭),那这就是个bug。

    首先

    思路是对的,然而用+1的方式出错了。我换了个思路,用时间差来决定是否关闭爬虫。逻辑:

    1. 时间差是不会存在累加的情况,所以不会有刚才的bug
    2. 先初始化一个起始时间
    3. 在每次请求队列的时候刷新起始时间
    4. 在每次队列为空的时候开始计时
    5. 计算时间差,如果队列为空的时间减去起始时间的秒数结果大于设定值,则判定为空跑,关闭爬虫

    优点

    1. 通过时间差来判断空跑,解决了刚才的bug;
    2. 可以根据时间来关闭爬虫,而不是次数,这样对于日后爬虫的监控更精准

    具体的代码如下:

    现在init方法设定起始时间

            为解决空跑问题:设定起始时间 
            下方根据记录空跑时间end_times与起始时间的时间差来决定何时关闭爬虫,避免空跑
            """
            self.strat_times = datetime.datetime.now()
    

    然后到next_request方法进行具体的时间差计算和空跑判断,还有爬虫的关闭操作:

        def next_request(self):
            block_pop_timeout = self.idle_before_close
            request = self.queue.pop(block_pop_timeout)
            if request and self.stats:
              """ 解决空跑问题,这里判断如果获取到request则重置起始时间strat_times """
                self.strat_times = datetime.datetime.now()
                self.stats.inc_value('scheduler/dequeued/redis', spider=self.spider)
            if request is None:
               """ 
                scrapy_reids跑完数据后不会自动停止,会产生空跑情况,一直空跑 
                    每次调度Schedule时如果队列没有数据  则计算end_times
                    当end_times与start_times的时间差close_times超过N秒,就判定为空跑且进行关闭爬虫的操作
                """
                self.end_times = datetime.datetime.now()
                self.close_times = (self.end_times - self.strat_times).seconds
                print("tihs close_times is : ")
                print(self.close_times)
                if self.close_times > 180:
                    self.spider.crawler.engine.close_spider(self.spider, 'Queue is empty,So active end')
            return request
    

    看到下图,跑完数据后会根据时间差关闭爬虫

    输入图片说明

    这样才是真正的解决了空跑的问题


    最后运行,可以正常关闭爬虫了。但是结束的时候还会有报错信息:

    builtins.AttributeError: 'NoneType' object has no attribute 'start_requests'
    
    2017-12-14 16:18:56 [twisted] CRITICAL: Unhandled Error
    Traceback (most recent call last):
      File "E:\Miniconda\lib\site-packages\scrapy\commands\runspider.py", line 89, in run
        self.crawler_process.start()
      File "E:\Miniconda\lib\site-packages\scrapy\crawler.py", line 285, in start
        reactor.run(installSignalHandlers=False)  # blocking call
      File "E:\Miniconda\lib\site-packages\twisted\internet\base.py", line 1243, in run
        self.mainLoop()
      File "E:\Miniconda\lib\site-packages\twisted\internet\base.py", line 1252, in mainLoop
        self.runUntilCurrent()
    --- <exception caught here> ---
      File "E:\Miniconda\lib\site-packages\twisted\internet\base.py", line 878, in runUntilCurrent
        call.func(*call.args, **call.kw)
      File "E:\Miniconda\lib\site-packages\scrapy\utils\reactor.py", line 41, in __call__
        return self._func(*self._a, **self._kw)
      File "E:\Miniconda\lib\site-packages\scrapy\core\engine.py", line 137, in _next_request
        if self.spider_is_idle(spider) and slot.close_if_idle:
      File "E:\Miniconda\lib\site-packages\scrapy\core\engine.py", line 189, in spider_is_idle
        if self.slot.start_requests is not None:
    builtins.AttributeError: 'NoneType' object has no attribute 'start_requests'
    
    

    当通过engine.close_spider(spider, ‘reason’)来关闭spider时,有时会出现几个错误之后才能关闭。可能是因为scrapy会开启多个线程同时抓取,然后其中一个线程关闭了spider,其他线程就找不到spider才会报错。


    注意事项

    编写代码的schedule.py有个next_request方法有这么一句代码:

            request = self.queue.pop(block_pop_timeout)
    

    打开同目录的queue.py文件

    输入图片说明

    所以,PriorityQueue和另外两种队列FifoQueue,LifoQueue有所不同,特别需要注意。

    如果会使用到timeout这个参数,那么在setting中就只能指定爬取队列为FifoQueue或LifoQueue

    # 指定排序爬取地址时使用的队列,
    # 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。
    # 'SCHEDULER_QUEUE_CLASS': 'scrapy_redis.queue.SpiderPriorityQueue',
    # 可选的 按先进先出排序(FIFO)
    'SCHEDULER_QUEUE_CLASS': 'scrapy_redis.queue.SpiderQueue',
    # 可选的 按后进先出排序(LIFO)
    # SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderStack'
    

    数据入库测试

    经过多次 的mysql入库测试,发现bloomfilter是生效的,而且增量开始之前,对于那么重复的数据对比过滤是非常快的(仅用了500条数据测试),正常爬取500条数据大约1分钟多一点。在爬取过500多数据后,bloomfilter的略过只用了几秒钟,很短的时间。

    这个还是很强的,我很高兴

    相关文章

      网友评论

          本文标题:Scrayp-集成scrapy_redis和bloomfilte

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