scrapy_redis源码分析(一):RedisSpider类(自定义初始请求)_potato_big的博客-程序员宅基地

技术标签: 爬虫  python  python爬虫  分布式  redis  

先给结果:重写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对象,就可以完成我们的自定义初始化请求的操作。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Pual_wang/article/details/107567562

智能推荐

最浅显易懂的 SAPGUI 里 ABAP 调试器的使用方法介绍_abap调试_汪子熙的博客-程序员宅基地

本文介绍 SAPGUI 里 ABAP 调试器的用法。我们编写的 ABAP 程序,如果执行遇到问题,可以使用 ABAP 调试器,采用单步排错的方式进行调试。我们使用下面这个简单的 ABAP 程序作为例子来讲解。REPORT z.DATA: lv_count TYPE int4 VALUE 0.DO 10 TIMES. lv_count = lv_count + sy-index.ENDDO.WRITE:/ lv_count.这个 ABAP 程序的逻辑很简单,计算整数1 + 2 + 3 _abap调试

oracle 基础知识练习(自增长触发器,自定义函数)_oracle 自定义函数练习_jokertiger的博客-程序员宅基地

文章目录创建用户解锁用户创建表空间自增序列触发器创建表和触发器自定义函数带参数的自定义函数,两种执行方式方式一方式二创建用户$ sqlplus / as sysdbaSQL> startupORACLE instance started.SQL> connect system@prodEnter password:Connected.SQL> create user gree identified by ok;User created.SQL> gran_oracle 自定义函数练习

Nested Classes(嵌套类) - Java Tutorials 翻译_祥先生的博客-程序员宅基地

一、原文地址:https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html二、原文翻译:1、嵌套类 java编程语言允许在另一个类内部定义一个类。这样的类被叫做嵌套类,这里举例说明:/** 外部类 **/public class OuterClass { /** 嵌套类 **/ ...

干货操作:Shell脚本一键安装samba服务,任何人都可以访问,目录只读------------------------小董偷懒系列-程序员宅基地

Shell脚本一键安装samba服务一、shell要求二、实验一、shell要求1、写一个shell脚本,能够实现一键安装并配置samba服务,执行该脚本时需要带一个路径(格式$0 $1) /opt/samba.sh /opt/samba2、目录若存在,则自动创建,任何人都可以访问,并且不需要密码,并且是只读的二、实验创建编写一个samba.sh脚本vi /opt/samba.sh#!/bin/bashif [ "$#" -ne 1 ]then echo "运行脚本格式为:

怎么将html转换为ipynb,IPython笔记本:将HTML笔记本转换为ipynb_邵浩博士的博客-程序员宅基地

我最近使用BeautifulSoup和JSON将html笔记本转换为ipynb。诀窍是查看笔记本的JSON模式并模拟它。代码只选择输入代码单元格和标记单元格这是我的密码from bs4 import BeautifulSoupimport jsonimport urllib.requesturl = 'http://nbviewer.jupyter.org/url/jakevdp.github.c..._html转ipynb

罗技Logitech MX Vertical 和 MX Lift Vertial无线鼠标简单测评_罗技lift和vertical_陈少伯的博客-程序员宅基地

最近鼠标用的太久感觉手快要废了打算换一个人体工学鼠标,于是打算尝试罗技的人体工学鼠标,目前Logitech有两款垂直鼠标,分别是MX Vertical 和 MX Lift。我选择两个一起买然后试试水,不行再退货其实与此同时还购买了LogitechG502 和LogitechG903,但是将分开简单测评。既然买都买了,就利用这个千载难逢的机会写点测评,有纠结的小伙伴可以参考一下。目前是在英国购买的,这两款鼠标均通过官网购买,有学生优惠,但是亚马逊时机好的时候还会打折。..._罗技lift和vertical

随便推点

写个俄罗斯方块,感受嵌入式linux应用开发_基于linux开发板的俄罗斯方块_闪耀大叔的博客-程序员宅基地

俄罗斯方块是一个简单的小游戏,完全可以采用单片机,裸机完成。但在嵌入式linux环境下实现,可以充分感受linux应用开发,同样是写一个程序,但功力可能大不相同。由单片机转嵌入式linux应用,框架感很重要,好的框架方便扩展,修改,可移植性强。_基于linux开发板的俄罗斯方块

SpringBoot+cache缓存技术_Mr_LiYYD的博客-程序员宅基地

springBoot中使用缓存1. 在boot启动类上加@EnableCaching注解启动缓存/** * 一. 快速体验注解 * 步骤: * 1. 开启基于注解的缓存@EnableCacheing * 2. 标注缓存注解即可 * @Cacheable 方法执行后结果放进缓存 * @...

按钮状态选择(选中,取消)_按钮选中状态_TXM12138的博客-程序员宅基地

android:state_enabled 设置触摸或点击事件是否可用状态,一般只在false时设置该属性,表示不可用状态 android:state_pressed 设置是否按压状态,一般在true时设置该属性,表示已按压状态,默认为false android:state_selected 设置是否选中状..._按钮选中状态

电脑基本操作常用快捷键_电脑基本操作的快捷键_hjp353939864的博客-程序员宅基地

常见用法:F1显示当前程序或者windows的帮助内容。F2当你选中一个文件的话,这意味着“重命名”F3当你在桌面上的时候是打开“查找:所有文件”对话框F10或ALT激活当前程序的菜单栏windows键或CTRL ESC打开开始菜单CTRL ALT DELETE在win9x中打开关闭程序对话框DELETE删除被选择的选择项目,如果是文件,将被放入回收站SHIFT DELETE删除被选择的选择项目,_电脑基本操作的快捷键

使用poi实现excel的上传下载_java poi 下载excel_努力秃头的每一天的博客-程序员宅基地

文章目录前言一、poi是什么?二、使用步骤1.引入依赖2.导出excel3.导入excel总结前言excel的上传下载是常见的需求,这里记录一下使用poi实现Excel的上传和下载一、poi是什么?Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。二、使用步骤1.引入依赖代码如下(示例):<!--poi依赖--><dependency> <grou._java poi 下载excel

pycharm代码格式化快捷键Ctrl+Alt+L失效解决方案_pycharm格式化代码快捷键没反应_夢君莫笑的博客-程序员宅基地

选中Reformat Code选项,然后右键,先移除,然后添加自己喜欢的快捷键。一次进入File—>Setting—>Keymap。1、格式化代码快捷键被占用失效的原因。然后在搜索框内输入:reformat。快捷键被占用 或者被修改。_pycharm格式化代码快捷键没反应

推荐文章

热门文章

相关标签