前言
最近工作不是很忙,所以空闲时间我就看看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中的属性
- url(string): 此请求的url。
- callback(callable): 回调函数,就是此请求成功后的响应作为第一个参数被此callback函数所调用,如果此请求未指定callback,就默认调用parse函数。
- method(string): 此请求的请求方式,默认是 "GET"请求方式。
- headers(dict): 此请求的请求头,默认为 None, 则请求时,该http请求头不会被发送。
- body(bytes or string or unicode): 此请求的请求体,默认为 None, 即该请求的请求体为空。
- cookie(dict): 该请求所携带的cookie值。
- meta(dict): 该请求所携带的额外的字段,常常用于 控制中间件中的一些特定行为 或 为开发者存储当前的一些字段值以作为后续回调函数中的某些参数,或者赋值给某些变量,供后续回调函数的使用(在scrapy 1.7版本中该功能进行了更好的区分)。
- encoding(string): 此请求的编码(默认是 “utf-8”)。
- priority(int): 此请求的优先级(默认为 0 )调度程序使用优先级定义用于处理请求的顺序。优先级值较高的请求将更早执行。允许负值以表示相对较低的优先级。
- dont_filter(boolean): 此字段指定该请求是否能够进行多次重复请求。默认值为:False, 即表示 该请求只进行一次请求,若有第二次,则直接过滤。
- errback(callable): 如果在处理请求时引发任何异常,则将调用的函数。这包括404 HTTP错误等失败的页面。它收到一个 Twisted Failure 实例作为第一个参数。默认值为: None。
- 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 。