本站消息

站长简介/公众号


站长简介:逗比程序员,理工宅男,前每日优鲜python全栈开发工程师,利用周末时间开发出本站,欢迎关注我的微信公众号:幽默盒子,一个专注于搞笑,分享快乐的公众号

  价值13000svip视频教程,python大神匠心打造,零基础python开发工程师视频教程全套,基础+进阶+项目实战,包含课件和源码

  出租广告位,需要合作请联系站长

+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2020-07(10)

2020-08(50)

荐scrapy_redis 自定义初始请求

发布于2020-07-25 22:21     阅读(597)     评论(0)     点赞(19)     收藏(5)



先给结果:重写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






所属网站分类: 技术文章 > 博客

作者:丸子

链接:https://www.pythonheidong.com/blog/article/463847/8eb51dd3f0747217d979/

来源:python黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

19 0
收藏该文
已收藏

评论内容:(最多支持255个字符)