Scrapy源码分析(一) -- Request请求对象

前言

最近工作不是很忙,所以空闲时间我就看看scrapy的源码。仔细琢磨了下源码,还是有不少感悟的,所以就利用博客记录下自己的感悟。

import six
from w3lib.url import safe_url_string

from scrapy.http.headers import Headers
from scrapy.utils.python import to_bytes
from scrapy.utils.trackref import object_ref
from scrapy.utils.url import escape_ajax
from scrapy.http.common import obsolete_setter


class Request(object_ref):

    def __init__(self, url, callback=None, method='GET', headers=None, body=None,
                 cookies=None, meta=None, encoding='utf-8', priority=0,
                 dont_filter=False, errback=None, flags=None):

        self._encoding = encoding  # this one has to be set first
        self.method = str(method).upper()
        self._set_url(url)
        self._set_body(body)
        assert isinstance(priority, int), "Request priority not an integer: %r" % priority
        self.priority = priority

        if callback is not None and not callable(callback):
            raise TypeError('callback must be a callable, got %s' % type(callback).__name__)
        if errback is not None and not callable(errback):
            raise TypeError('errback must be a callable, got %s' % type(errback).__name__)
        assert callback or not errback, "Cannot use errback without a callback"
        self.callback = callback
        self.errback = errback

        self.cookies = cookies or {}
        self.headers = Headers(headers or {}, encoding=encoding)
        self.dont_filter = dont_filter

        self._meta = dict(meta) if meta else None
        self.flags = [] if flags is None else list(flags)

    @property
    def meta(self):
        if self._meta is None:
            self._meta = {}
        return self._meta

    def _get_url(self):
        return self._url

    def _set_url(self, url):
        if not isinstance(url, six.string_types):
            raise TypeError('Request url must be str or unicode, got %s:' % type(url).__name__)

        s = safe_url_string(url, self.encoding)
        self._url = escape_ajax(s)

        if ':' not in self._url:
            raise ValueError('Missing scheme in request url: %s' % self._url)

    url = property(_get_url, obsolete_setter(_set_url, 'url'))

    def _get_body(self):
        return self._body

    def _set_body(self, body):
        if body is None:
            self._body = b''
        else:
            self._body = to_bytes(body, self.encoding)

    body = property(_get_body, obsolete_setter(_set_body, 'body'))

    @property
    def encoding(self):
        return self._encoding

    def __str__(self):
        return "<%s %s>" % (self.method, self.url)

    __repr__ = __str__

    def copy(self):
        """Return a copy of this Request"""
        return self.replace()

    def replace(self, *args, **kwargs):
        """Create a new Request with the same attributes except for those
        given new values.
        """
        for x in ['url', 'method', 'headers', 'body', 'cookies', 'meta', 'flags',
                  'encoding', 'priority', 'dont_filter', 'callback', 'errback']:
            kwargs.setdefault(x, getattr(self, x))
        cls = kwargs.pop('cls', self.__class__)
        return cls(*args, **kwargs)
Request中的属性
  1. url(string): 此请求的url。
  2. callback(callable): 回调函数,就是此请求成功后的响应作为第一个参数被此callback函数所调用,如果此请求未指定callback,就默认调用parse函数。
  3. method(string): 此请求的请求方式,默认是 "GET"请求方式。
  4. headers(dict): 此请求的请求头,默认为 None, 则请求时,该http请求头不会被发送。
  5. body(bytes or string or unicode): 此请求的请求体,默认为 None, 即该请求的请求体为空。
  6. cookie(dict): 该请求所携带的cookie值。
  7. meta(dict): 该请求所携带的额外的字段,常常用于 控制中间件中的一些特定行为为开发者存储当前的一些字段值以作为后续回调函数中的某些参数,或者赋值给某些变量,供后续回调函数的使用(在scrapy 1.7版本中该功能进行了更好的区分)
  8. encoding(string): 此请求的编码(默认是 “utf-8”)。
  9. priority(int): 此请求的优先级(默认为 0 )调度程序使用优先级定义用于处理请求的顺序。优先级值较高的请求将更早执行。允许负值以表示相对较低的优先级。
  10. dont_filter(boolean): 此字段指定该请求是否能够进行多次重复请求。默认值为:False, 即表示 该请求只进行一次请求,若有第二次,则直接过滤。
  11. errback(callable): 如果在处理请求时引发任何异常,则将调用的函数。这包括404 HTTP错误等失败的页面。它收到一个 Twisted Failure 实例作为第一个参数。默认值为: None。
  12. flags(list): 发送到请求的标志可用于日志记录或类似用途。其作用跟用法和 meta字段类似。
Request对象实例化及相关的方法
1. 当Request实例化时,它发生了什么?

1.1当 Request对象实例化时:
  1). 首先设置 此请求的编码(默认是:“utf-8")。
  2). 然后 格式化 该 method 字段,统一转换成 大写。
  3). 格式化 url , 调用_set_url 方法。
  4). 格式化body,调用_set_body方法。
  5). 断言判断,判断 priority 是否是 int 类型, 若不是,则报错抛出异常。
  6). 判断是否有 callback,如有,是否具有可调用属性。
  7). 判断是否有 errback,如有,是否具有可调用属性。
  8). 断言判断,如果没有 callback, 但是却有errback, 则会抛出异常。
  9). 初始化headers字段,指定编码为该request所对应的编码(默认是: “utf-8”)。
  10). meta字段进行格式化转换,转为 dict 类型的数据。
  11). flags字段进行格式化转换, 转换为 list 类型的数据。

1.2 Request中的主要方法
1.2.1: _set_url

    def _set_url(self, url):
    	# 首先判断该 url 是否是 str 类型的,若不是,则会抛出异常
        if not isinstance(url, six.string_types):
            raise TypeError('Request url must be str or unicode, got %s:' % type(url).__name__)
            
		# 利用 safe_url_string函数,和self.encoding,格式化url
        s = safe_url_string(url, self.encoding)
        
        # 继续格式化url ,若 url中参数连接符有 “#!”,才会进行格式化
        self._url = escape_ajax(s)
        
		# 判断 url中是否有 “:”,没有则会抛出 ValueError 的异常
        if ':' not in self._url:
            raise ValueError('Missing scheme in request url: %s' % self._url)

1.2.2: _set_body

    def _set_body(self, body):
        # 判断body是否为空,body为空,则 self._body 赋值为 b""
        if body is None:
            self._body = b''
        # 否则转化成 bytes类型,这里编码默认为:utf-8。由此可看出,平时我们body传值可为:unicode, str or bytes或者 None类型, 它会帮我们转化成 bytes类型。
        else:
            self._body = to_bytes(body, self.encoding)

1.2.3 url及body属性的设置

# self.url属性是只读的,不可修改,若想修改,调用 self.replace方法
url = property(_get_url, obsolete_setter(_set_url, 'url'))
# self.body属性也是只读的,不可修改,若想修改,调用 self.replace方法
body = property(_get_body, obsolete_setter(_set_body, 'body'))

1.2.4 copy

    def copy(self):
        """Return a copy of this Request"""
        # 返回该对象的副本,本质上是调用的 self.replace方法,参数为空的特殊情况
        return self.replace()

1.2.5 replace

    def replace(self, *args, **kwargs):
        """Create a new Request with the same attributes except for those
        given new values.
        """
        # for循环遍历 该Request对象的 12个属性 列表
        for x in ['url', 'method', 'headers', 'body', 'cookies', 'meta', 'flags',
                  'encoding', 'priority', 'dont_filter', 'callback', 'errback']:
            # 若kwargs字典中存在 key 为 x, 则不变,若不存在 key 为 x, 则有:kwargs[x] = getattr(self, x), 所以 kwargs中只写 要发生变化的 key 和 value
            kwargs.setdefault(x, getattr(self, x))
       # 若有 cls 属性,则用 cls进行实例化,否则和此时的 请求类型保持一致 
        cls = kwargs.pop('cls', self.__class__)
        # 多参数,实例化生成新的请求对象
        return cls(*args, **kwargs)
小结

通过以上可以看出,Request对象的属性其实就是一些在http请求中常用的字段。同时,self.url和self.body不能直接通过赋值进行修改,只能通过 replace方法进行修改。它这个replace方法设计得比较巧妙,既可以增加新的属性值,也可以copy原来的的属性值,并且重新生成 与当前此对象不同类型的对象,即有可能 Request - -> FormRequest 。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值