欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

荐 scrapy_redis 自定义初始请求

程序员文章站 2022-05-20 17:27:20
scrapy_redis框架的RedisSpider类和RedisMixin类源码分析,自定义初始请求...

先给结果:重写make_requests_from_url方法!!

如果想知道原因,请继续往下看

scrapy_redis是做分布式爬虫的时候经常会用到的一个爬虫框架,scrapy_redis框架是基于scrapy框架,提供了一些以redis为基础的组件。

相对于scrapy设置的start_urls,在scrapy_redis中只需要设置redis_key就可以了,爬虫会自动去redis的相应的key中取到url,然后包装成Request对象,保存在redis的待爬取队列(request queue)中。

但是我们有时候可能想自定义url请求,比如我们可能想要初始化的请求是个post而不是get,或者由于某些原因导致我们从redis中取出来的数据并不是可以直接请求的url。这些都需要我们对redis中的url做进一步的处理,这里主要通过对scrapy_redis中的RedisSpider类进行分析,看看怎样修改才能达到目的。

我们自己实现的spider类基本上通过继承RedisSpider来完成任务调度的。首先我们看看RedisSpider类的源码:

class RedisSpider(RedisMixin, Spider):
	@classmethod
    def from_crawler(self, crawler, *args, **kwargs):
        obj = super(RedisSpider, self).from_crawler(crawler, *args, **kwargs)
        obj.setup_redis(crawler)
        return obj

不难看出RedisSpider类继承自scrapy_redis.spiders.RedisMixin类和scrapy.spiders.Spider类,这里使用了多继承,并用RedisMixin调度功能覆盖Spider原生的调度功能。

RedisMixin类主要包括六个方法:

  1. start_requests
  2. setup_redis
  3. next_requests
  4. make_request_from_data
  5. schedule_next_requests
  6. spider_idle

那这些方法都有什么作用呢?我们注意到,RedisSpider类在实例化之后,调用了setup_redis方法,该方法源码如下:

def setup_redis(self, crawler=None):
    """Setup redis connection and idle signal.

    This should be called after the spider has set its crawler object.
    """
    if self.server is not None:
        return

    if crawler is None:
        # We allow optional crawler argument to keep backwards
        # compatibility.
        # XXX: Raise a deprecation warning.
        crawler = getattr(self, 'crawler', None)

    if crawler is None:
        raise ValueError("crawler is required")

    settings = crawler.settings

    if self.redis_key is None:
        self.redis_key = settings.get(
            'REDIS_START_URLS_KEY', defaults.START_URLS_KEY,
        )

    self.redis_key = self.redis_key % {'name': self.name}

    if not self.redis_key.strip():
        raise ValueError("redis_key must not be empty")

    if self.redis_batch_size is None:
        # TODO: Deprecate this setting (REDIS_START_URLS_BATCH_SIZE).
        self.redis_batch_size = settings.getint(
            'REDIS_START_URLS_BATCH_SIZE',
            settings.getint('CONCURRENT_REQUESTS'),
        )

    try:
        self.redis_batch_size = int(self.redis_batch_size)
    except (TypeError, ValueError):
        raise ValueError("redis_batch_size must be an integer")

    if self.redis_encoding is None:
        self.redis_encoding = settings.get('REDIS_ENCODING', defaults.REDIS_ENCODING)

    self.logger.info("Reading start URLs from redis key '%(redis_key)s' "
                     "(batch size: %(redis_batch_size)s, encoding: %(redis_encoding)s",
                     self.__dict__)

    self.server = connection.from_settings(crawler.settings)
    # The idle signal is called when the spider has no requests left,
    # that's when we will schedule new requests from redis queue
    crawler.signals.connect(self.spider_idle, signal=signals.spider_idle)

查看源码总结出这个方法做了以下几个事情:

  1. 查看server是否是none,如果非none,则直接退出
  2. 初始化redis_key,如果是none的话,为其赋值%(name)s:start_urls,并用我们自定义的Spider中的name属性进行替换
  3. 初始化redis_batch_size(并行爬取数量),如果是none的话,用我们在setting文件中的CONCURRENT_REQUESTS或者REDIS_START_URLS_BATCH_SIZE字段的值,对redis_batch_size进行赋值
  4. 初始化redis_encoding,如果是none的话,使用setting文件中的REDIS_ENCODING字段进行赋值,默认为utf-8
  5. 建立与redis的连接,并赋值给server变量
  6. 监听spider_idle信号,当收到spider_idle信号时,将交给spider_idle方法来处理。
schedule_next_requests & spider_idle
def schedule_next_requests(self):
    """Schedules a request if available"""
    # TODO: While there is capacity, schedule a batch of redis requests.
    for req in self.next_requests():
        self.crawler.engine.crawl(req, spider=self)

def spider_idle(self):
    """Schedules a request if available, otherwise waits."""
    # XXX: Handle a sentinel to close the spider.
    self.schedule_next_requests()
    raise DontCloseSpider

spider_idle方法主要是处理spider_idle信号,调用 schedule_next_requests方法

schedule_next_requests方法从next_requests方法获取Request对象,并交给爬虫引擎去执行爬虫操作。

但我们如果想自定义初始请求应该怎么做呢?

我们知道启动一个继承自RedisSpider的分布式爬虫,在实例化完成之后,会调用scrapy_redis.spiders.RedisMixin.start_requests方法,这个方法返回了一个next_requests方法的执行结果。

def start_requests(self):
    """Returns a batch of start requests from redis."""
    return self.next_requests()

那么next_requests方法实现了一个什么样的功能呢?先看看代码是怎么写的

def next_requests(self):
    """Returns a request to be scheduled or none."""
    use_set = self.settings.getbool('REDIS_START_URLS_AS_SET', defaults.START_URLS_AS_SET)
    fetch_one = self.server.spop if use_set else self.server.lpop
    # XXX: Do we need to use a timeout here?
    found = 0
    # TODO: Use redis pipeline execution.
    while found < self.redis_batch_size:
        data = fetch_one(self.redis_key)
        if not data:
            # Queue empty.
            break
        req = self.make_request_from_data(data)
        if req:
            yield req
            found += 1
        else:
            self.logger.debug("Request not made from data: %r", data)

    if found:
        self.logger.debug("Read %s requests from '%s'", found, self.redis_key)

可以得到next_requests方法主要实现了以下几个功能:

  1. 初始化use-set,bool类型,如果setting中没有REDIS_START_URLS_AS_SET字段,则赋值为false,否则用该字段赋值
  2. 定义fetch_one(从redis中获取url的方式,spop和lpop)
  3. 如果redis_key对应的set中,包含比redis_batch_size多的数据的话,遍历返回redis_batch_size个Request对象,如果少的话,就全部返回。这里调用了make_request_from_data方法,来生成Request对象。
def make_request_from_data(self, data):
    """Returns a Request instance from data coming from Redis.

    By default, ``data`` is an encoded URL. You can override this method to
    provide your own message decoding.

    Parameters
    ----------
    data : bytes
        Message from redis.

    """
    url = bytes_to_str(data, self.redis_encoding)
    return self.make_requests_from_url(url)

查看make_request_from_data方法的代码发现在将 data 参数转为str类型之后,调用了scrapy.spiders.Spider.make_requests_from_url方法来生成的Request对象,

def make_requests_from_url(self, url):
        """ This method is deprecated. """
        warnings.warn(
            "Spider.make_requests_from_url method is deprecated: "
            "it will be removed and not be called by the default "
            "Spider.start_requests method in future Scrapy releases. "
            "Please override Spider.start_requests method instead."
        )
        return Request(url, dont_filter=True)

查看make_requests_from_url方法的代码,发现这个方法只是将url包装成Request对象而已,所以我只需要重写make_requests_from_url方法,在其中自定义我们想要的操作,然后返回一个Request对象,就可以完成我们的自定义初始化请求的操作。

本文地址:https://blog.csdn.net/Pual_wang/article/details/107567562

相关标签: python爬虫