Requests库请求过程简析

目录

API接口和Session

API接口

session

小结

请求流程介绍

Request对象

Request对象小结

prepare_request方法

cookie处理

cookie处理小结

身份验证

 构造PreparedRequest对象

prepare_request方法小结

环境参数合并

请求发送 

附源码结构梳理 


Requests是python中使用率极高的一个请求库,具体使用方法可以查询官网.

本文旨在从源码入手,梳理逻辑,以更好的理解Requsets库的请求过程.

API接口和Session

Requests库发起网络请求有两种方式,最简单的一种是API接口方式,一种是Session方式.

API接口是在方法层面,Session方式是在会话层面.

通过官方的介绍,我们知道方法层面的每次请求都是独立的,所有的请求的参数都是不持续的.

而会话层面的方法是可以将一些请求参数持续化的.比如cookie.

  • API接口

通过API接口方法的调用,就能得到响应内容,接下来用代码来看看API接口的方式

import requests
 
r = requests.get('https://api.github.com/events')
r = requests.post('https://httpbin.org/post', data = {'key':'value'})
r = requests.put('https://httpbin.org/put', data = {'key':'value'})
r = requests.delete('https://httpbin.org/delete')
r = requests.head('https://httpbin.org/get')
r = requests.options('https://httpbin.org/get')

上面见到的get,post等等方法都是在Reqeusts库的__init__文件提前导入的,很明显这些方法都来自api.py的文件.

from .api import request, get, head, post, patch, put, delete, options

接下来看看这些方法在api.py文件中是怎么定义的呢?(仅为展现需要,代码进行了删减)

from . import sessions

def request(method, url, **kwargs):
    pass

def get(url, params=None, **kwargs):
    pass

def post(url, data=None, json=None, **kwargs):
    pass

......

该api.py文件中一共定义了八个方法

除了第一个reqeust方法,其他的都是HTTP协议中的请求方法

暂时先不管这个reqeust方法,我们就从最常用的get方法入手,源码很简短,如下:

def get(url, params=None, **kwargs):
    kwargs.setdefault('allow_redirects', True)
    return request('get', url, params=params, **kwargs)

 要搞懂这段代码,首先要了解**kwargs形式的参数.简单来说,这是python中一种对参数的封装方式.

 可以把 key=value 这样键值对形式的参数封装成字典,举个例子:

def func(**kwargs):
    print(kwargs)
func(a=1,b=2)
#打印:{'a': 1, 'b': 2}

了解了kwargs就是一个字典后,就能很容易的看懂第一行代码了.

通过字典的内置setdefault方法,为kwargs这个参数字典设置一个allow_redirects主键,如果该主键不存在则值为True.

实际上allow_redirects是代表允许重定向

最后通过调用之前提到的reqeust方法,并将request方法的结果返回.

至此,可以来研究下reqeust方法的源码了:

from . import sessions

def request(method, url, **kwargs):
    with sessions.Session() as session:
        return session.request(method=method, url=url, **kwargs)

这个request方法的源代码也极为简洁 ,但是注意该方法调用了session模块的内容.

通过上下文管理的方式调用了session.py文件中的Session类的request方法.

如果熟悉with关键字的作用的话,便理解为什么方法层面的请求中的参数是不可持续的了.

虽然现在还不清楚session.Session.request方法中的逻辑

但起码知道每一次方法层面的请求都是在调用会话层的方法,并在一次请求结束后关闭了session.

在此就仅以get方法为例了,其他的post,put等等方法也都是类似的.

  • session

由上述所知,方法层面都是调用的session.py文件中的方法.

那么现在来谈谈会话层了.首先来看看session.py文件中的内容.

# import so much things!!!

def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
    pass

def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
    pass

class SessionRedirectMixin(object):
    pass

class Session(SessionRedirectMixin):
    pass

def session():
    pass

该文件中导入的其他模块或者方法很多,等碰到有用的模块或方法时在介绍,此处就不啰嗦了.

在该文件中定义了3个方法和2个类

既然之前被调用的是Session类,那么就从Session类先看.

在研究之前要声明一点,本文只探讨Request库的请求过程,至于网络传输等等其他方面的不在本文研究范围内.

下面我们先列出Session类中的在本次讨论范围内的部分代码(同样做了删减)

class Session(SessionRedirectMixin):

    def prepare_request(self, request):
        pass
    
    def request(self, method, url,
            params=None, data=None, headers=None, cookies=None, files=None,
            auth=None, timeout=None, allow_redirects=True, proxies=None,
            hooks=None, stream=None, verify=None, cert=None, json=None):
        pass
    def get(self, url, **kwargs):
        kwargs.setdefault('allow_redirects', True)
        return self.request('GET', url, **kwargs)

    def send(self, request, **kwargs):
        pass

既然刚刚是调用的reqeust方法,那么就直接研究Session类中的这个reqeust方法吧.

咋眼一看,该方法的参数实在不少...... 还是直接上源码吧

    def request(self, method, url,
            params=None, data=None, headers=None, cookies=None, files=None,
            auth=None, timeout=None, allow_redirects=True, proxies=None,
            hooks=None, stream=None, verify=None, cert=None, json=None):
        
        req = Request(
            method=method.upper(),
            url=url,
            headers=headers,
            files=files,
            data=data or {},
            json=json,
            params=params or {},
            auth=auth,
            cookies=cookies,
            hooks=hooks,
        )
        prep = self.prepare_request(req)

        proxies = proxies or {}

        settings = self.merge_environment_settings(
            prep.url, proxies, stream, verify, cert
        )

        send_kwargs = {
            'timeout': timeout,
            'allow_redirects': allow_redirects,
        }
        send_kwargs.update(settings)
        resp = self.send(prep, **send_kwargs)

        return resp

不管怎么样,看到最后的return时,起码知道这个request方法才是最后拿到响应内容的核心方法了.

其实这一堆的参数都和HTTP协议是相关了,也就是在请求体中要发给Sever端的相关的必要的参数和内容.

先大概看看这个reqeust方法干了些什么事吧,把最关键的几个步骤梳理下.

首先实例化了一个Request对象req(request这个词用的比较多,注意区分)

然后调用 self.prepare_request 方法处理这个实例对象req,并生成一个新的prep对象

接下来进行了一些参数合并之类的工作,也就是把方法层面请求的参数与会话层面请求的参数进行合并

最后通过self.send方法将构造好的prep对象发送出去,并得到响应内容对象resp.

  • 小结

通过之前的分析,我们知道了以下几点:

1、api.py文件中定义的方法(如get)是通过request方法封装后调用的session

2、session.py文件中的Session类的request方法才是拿到网络响应的核心方法

3、上下文管理with的方式导致了每次API方法请求的参数的不可持续性

4、在向网络发送请求前,API方法携带的参数与session的参数进行了合并

请求流程介绍

前面的流程梳理中,我们已经知道Session类的request方法是整个请求调用的核心方法

同时也梳理出了整个流程的步骤

先构造Request对象req

然后使用self.prepare_request 方法处理这个实例对象req,并生成一个新的prep对象

接着合并环境参数

最后发送prep对象和环境参数并得到网络响应

接下来就按照这个步骤对整个流程进行分析

Request对象

在HTTP协议中,Client端、Sever端的使用Request(请求)和Response(响应)两个对象进行交互.

要进行网络交互,必然就存在着这两个对象

而之前的已经介绍到了session.Session.request方法,该方法中的第一步也是实例化了一个Request对象req.

其实这个Request类位于Requests库中的models.py文件中

在models.py文件中,声明了五个类, 分别是:

RequestEncodingMixin,RequestHooksMixin,Request,PreparedRequest,Response

很明显RequestResponse分别与HTTP协议中的请求和响应对象相对应

要继续了解整个请求流程,我们只有从Request类入手,看看源码:

class Request(RequestHooksMixin):
    
    def __init__(self,
            method=None, url=None, headers=None, files=None, data=None,
            params=None, auth=None, cookies=None, hooks=None, json=None):

        data = [] if data is None else data
        files = [] if files is None else files
        headers = {} if headers is None else headers
        params = {} if params is None else params
        hooks = {} if hooks is None else hooks

        self.hooks = default_hooks()
        for (k, v) in list(hooks.items()):
            self.register_hook(event=k, hook=v)

        self.method = method
        self.url = url
        self.headers = headers
        self.files = files
        self.data = data
        self.json = json
        self.params = params
        self.auth = auth
        self.cookies = cookies

    def __repr__(self):
        return '' % (self.method)

    def prepare(self):
        p = PreparedRequest()
        p.prepare(
            method=self.method,
            url=self.url,
            headers=self.headers,
            files=self.files,
            data=self.data,
            json=self.json,
            params=self.params,
            auth=self.auth,
            cookies=self.cookies,
            hooks=self.hooks,
        )
        return p

这个Request类对象其实也比较简单,整个源码中该只有一堆的各种属性和一个方法.

之所以说只有一个方法,是因为__repr__方法是与print相对应使用的魔术方法

也就是,在使用print函数打印该对象时,会查找__repr__方法.

先从一大堆的属性说起吧

虽然都是属性,但需要区分类属性和实例属性

简单点说,在类的定义中,有self的都是实例属性,没有的就可以看做类属性

在__init__初始化构造方法中

首先通过三元判断的方法对默认或者传入参数的检查核对,构造出几个关键的类属性.

data = [] if data is None else data
files = [] if files is None else files
headers = {} if headers is None else headers
params = {} if params is None else params
hooks = {} if hooks is None else hooks

很明显,data,files参数是列表,而headers,parama,hooks都是字典.

在构造了类属性后,接下来就是创建10个实例属性.

self.hooks = default_hooks()
for (k, v) in list(hooks.items()):
    self.register_hook(event=k, hook=v)

self.method = method
self.url = url
self.headers = headers
self.files = files
self.data = data
self.json = json
self.params = params
self.auth = auth
self.cookies = cookies

这10个实例属性理论上是与传入的参数相对应的.

但注意self.data,self.files,self.headers,slef.params这四个实例属性对应的是之前创建的类属性了

最后还剩下一个hooks有一些特殊的处理.

在官网文档的高阶用法中,也是提到了requests库是支持事件钩子的

事件钩子也就是回调函数,并且是通过hooks参数来进行支持的.

在requests库,回调函数是用来操控response对象的属性的

我们先看完源码中对hooks的定义,再来看回调函数的例子.

为了方便阅读理解,把相关的涉及到其他模块的函数和源代码放到了一起.

self.hooks = default_hooks()
for (k, v) in list(hooks.items()):
    self.register_hook(event=k, hook=v)

#hooks.py文件中的内容:
HOOKS = ['response']
def default_hooks():
    return {event: [] for event in HOOKS}

#继承的方法:
def register_hook(self, event, hook):
        if event not in self.hooks:
            raise ValueError('Unsupported event specified, with event name "%s"' % (event))
        if isinstance(hook, Callable):
            self.hooks[event].append(hook)
        elif hasattr(hook, '__iter__'):
            self.hooks[event].extend(h for h in hook if isinstance(h, Callable))

通过源码,我们知道default_hooks函数生成的是 {'response' : [ ] } 这样的数据结构

也就说实例属性self.hooks默认情况就是 {'response' : [ ] } 这样的数据结构

若构造该对象时hooks参数为空,则类属性hooks是空字典.,否则就是原传入的hooks参数(很明显也必须为字典类型)

在model.py文件中,定义了五个类,而Request类是继承于RequestHooksMixin类的

class Request(RequestHooksMixin)

所以self.register_hook方法也就是继承而来的

RequestHooksMixin的源代码也很简单,就是定义了一个注册hook的函数和一个注销hook的函数

class RequestHooksMixin(object):
    def register_hook(self, event, hook):
        pass #上文已有代码

    def deregister_hook(self, event, hook):
        try:
            self.hooks[event].remove(hook)
            return True
        except ValueError:
            return False

还是说回之前的流程, 实例化属性self.hooks是 {'response' : [ ] } 这样的数据结构

类属性hooks是一个字典(可能为空,可能不为空).

然后通过对类属性hooks的主键和值进行迭代,并将 键值对 作为参数传入继承而来的self.register_hook方法

self.register_hook方法首先判断主键是否存在于实例化属性self.hooks之中,如果不存在则抛出错误.

很明显,类属性hooks这个字典的主键值也只允许是'respones'.

这也很好理解,因为我们回调函数也只有一个respones对象可以处理而已.

self.register_hook方法其次判断value值是否是可调用对象

如果可调用就直接将其添加到实例化属性self.hooks数据 {'response' : [ ] }的值的列表中

如果不可调用,则判断该value是否可被迭代

如果可以被迭代再判断每个迭代的元素是否可以被调用

只将可以被调用的元素添加到实例化属性self.hooks数据 {'response' : [ ] }的值的列表中

自此,实例属性self.hooks创建完成.

PS:deregister_hook函数也就是从实例属性self.hooks数据 {'response' : [ ] }的值的列表中删除指定元素.

Request对象的属性介绍完了,至于还有一个prepare方法就暂时省略

因为该方法与后面的内容有重复,就留到后面一起再介绍.

Request对象小结

1、Rquest对象是与HTTP协议中的请求对象相对象的,通过构建属性来与HTTP协议相匹配

2、Rquest对象有类属性和实例属性,逻辑上是有不同含义的

3、Rquest对象对属性进行了数据结构的构建或默认值,但对回调函数hooks有特殊的结构设置

prepare_request方法

在Request对象构造好之后,就应该调用self.prepare_request方法来处理req对象了

那么看下prepare_request方法的源码:

    def prepare_request(self, request):
        cookies = request.cookies or {}

        if not isinstance(cookies, cookielib.CookieJar):
            cookies = cookiejar_from_dict(cookies)

        merged_cookies = merge_cookies(
            merge_cookies(RequestsCookieJar(), self.cookies), cookies)

        auth = request.auth
        if self.trust_env and not auth and not self.auth:
            auth = get_netrc_auth(request.url)

        p = PreparedRequest()
        p.prepare(
            method=request.method.upper(),
            url=request.url,
            files=request.files,
            data=request.data,
            json=request.json,
            headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict),
            params=merge_setting(request.params, self.params),
            auth=merge_setting(auth, self.auth),
            cookies=merged_cookies,
            hooks=merge_hooks(request.hooks, self.hooks),
        )
        return p

从源码上看,整个prepare_request方法大概进行了三个方面的处理

一是处理了cookie相关的数据

二是处理了身份验证auth相关的数据

三是构造了一个PreparedRequest对象,并将其作为整个函数的返回值

下面就按照这个流程来进行分析.

cookie处理

本流程涉及到HTTP协议里面cookie的规范问题,同时也涉及到python内置库http中的内容

如果要全部展开的话......将是很恐怖的篇幅,所以此处不再进行详细的说明,仅对流程进行讲解.

特别注意如果在源码中看到cookielib的话,得需要注意下,因为在3版本的python中是不存在这个库的

这个库实际就是http.cookiejar,为了兼容性问题用了cookielib这个别名而已

在compat.py这个专门处理兼容性的文件中可以找到答案:

from http import cookiejar as cookielib

在介绍prepare_request方法之前,做一点基础铺垫

http是根据HTTP协议规范开发的python内置模块,cooliejar是该包中的一个.py文件.

cookiejar.py文件中有定义了很多的类和方法,而在本流程中要使用到其中的CookJar和Cookie两个类

而requests库在继承于CookJar类的基础上进行了扩展,创造了具有字典操作功能的RequestCookieJar类

有了这点铺垫,我们来看一看prepare_request方法,其涉及到cookie处理的代码很简短:

cookies = request.cookies or {}

if not isinstance(cookies, cookielib.CookieJar):
    cookies = cookiejar_from_dict(cookies)

merged_cookies = merge_cookies(merge_cookies(RequestsCookieJar(), self.cookies), cookies)

在这简单的代码中,有涉及到了两个方法,cookiejar_from_dict和merge_cookies,而且这两个方法会被多次使用

所以要理解cookie的处理过程,是必须要理解这个两个方法的.这两个方法都为于cookies.py文件中.

首先来说cookiejar_from_dict,该方法:

def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True):
    if cookiejar is None:
        cookiejar = RequestsCookieJar()

    if cookie_dict is not None:
        names_from_jar = [cookie.name for cookie in cookiejar]
        for name in cookie_dict:
            if overwrite or (name not in names_from_jar):
                cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))

    return cookiejar

首先注意该方法的三个参数,除了第一个位置参数cookie_dict,其余两个都有默认值.

cookiejar_from_dict方法中又调用了creat_cookie方法和一个set.cookie的方法

creat_cookie方法也位于cookies.py中,而set.cookie是RequestCookieJar类的实例方法.

从上面的代码中也可以发现,代码中的变量cookiejar也RequestCookieJar的实例

但这里的这个set.cookie不仅仅是RequestCookieJar的实例方法,也是CookJar类的实例方法

我们可以在RequestCookieJar的实例方法set.cookie源码中找到答案:

    def set_cookie(self, cookie, *args, **kwargs):
        if hasattr(cookie.value, 'startswith') and cookie.value.startswith('"') and cookie.value.endswith('"'):
            cookie.value = cookie.value.replace('\\"', '')
        return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs)

而之前提到过, RequestCookieJar是继承于cookielib.CookJar的

class RequestsCookieJar(cookielib.CookieJar, MutableMapping)

所以最终调用的 cookielib.CookJar的set.cookie方法.

这里搞清楚逻辑关系即可,也就不展开了,总之这个set.cookie方法处理的是creat_cookie方法的返回值

那么再在看看这个creat_cookie方法的源码:

def create_cookie(name, value, **kwargs):
    result = {
        'version': 0,
        'name': name,
        'value': value,
        'port': None,
        'domain': '',
        'path': '/',
        'secure': False,
        'expires': None,
        'discard': True,
        'comment': None,
        'comment_url': None,
        'rest': {'HttpOnly': None},
        'rfc2109': False,
    }

    badargs = set(kwargs) - set(result)
    if badargs:
        err = 'create_cookie() got unexpected keyword arguments: %s'
        raise TypeError(err % list(badargs))

    result.update(kwargs)
    result['port_specified'] = bool(result['port'])
    result['domain_specified'] = bool(result['domain'])
    result['domain_initial_dot'] = result['domain'].startswith('.')
    result['path_specified'] = bool(result['path'])

    return cookielib.Cookie(**result)

可以看到该方法最后返回的是一个上面提到的Cookie类对象.

这里就不进行展开了,简单的说creat_cookie方法就是以一对键值为参数对创造出一个符合cookie协议规范的对象.

再结合set.cookie方法,将相关的cookie参数封装到这个RequestCookieJar的实例对象cookiejar中

介绍完了cookiejar_from_dict方法中的使用的creat_cookie方法和set.cookie方法

再回到cookiejar_from_dict方法来,再贴一次源代码:

def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True):
    if cookiejar is None:
        cookiejar = RequestsCookieJar()

    if cookie_dict is not None:
        names_from_jar = [cookie.name for cookie in cookiejar]
        for name in cookie_dict:
            if overwrite or (name not in names_from_jar):
                cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))

    return cookiejar

该方法的三个参数,除了第一个位置参数cookie_dict,其余两个都有默认值.

因为的默认cookjar参数默认值就是None,所以该方法中首先创造了一个RequestCookieJar类的实例对象cookiejar.

其次判断cookie_dict参数传入的值是否为空,还记得cookie_dict参数传入的值是什么吗?

再看一下prepare_request方法出来cookie的代码:

cookies = request.cookies or {}

if not isinstance(cookies, cookielib.CookieJar):
    cookies = cookiejar_from_dict(cookies)

merged_cookies = merge_cookies(merge_cookies(RequestsCookieJar(), self.cookies), cookies)

没错,就是Request对象的实例rep对象中的cookie属性或者空字典.

若判断 cookie_dict不为空的话,

先以列表的方式提取RequestCookieJar类的实例对象cookiejar的所有属性

再判断cookie_dict中的主键是否与cookiejar的属性名存在重叠

如果存在重叠则通过creat_cookie方法和set.cookie方法

将cookie_dict的键值对按照协议规范封装到RequestCookieJar类的实例对象cookiejar中

也就是代码中的变量cookiejar并返回.

现在来看看另外一个重要的merge_cookies方法:

def merge_cookies(cookiejar, cookies):
    if not isinstance(cookiejar, cookielib.CookieJar):
        raise ValueError('You can only merge into CookieJar')

    if isinstance(cookies, dict):
        cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False)
    elif isinstance(cookies, cookielib.CookieJar):
        try:
            cookiejar.update(cookies)
        except AttributeError:
            for cookie_in_jar in cookies:
                cookiejar.set_cookie(cookie_in_jar)

    return cookiejar

该 merge_cookies 方法接收两个参数

首先对第一个参数进行类型判断,若不是cookielib.CookieJar的实例就抛出错误.

这也就是说第一个参数必须是cookielib.CookieJar的实例.

其次判断第二个参数是否是字典,如果是则调用刚刚讲解的cookiejar_from_dict

返回一个RequestCookieJar类的实例对象cookiejar ,该对象也是cookielib.CookieJar的实例

并且该返回值直接覆盖掉第一个参数传入的cookiejar,并返回该值.

如果第二个参数不是字典,则判断其是否是cookielib.CookieJar的实例

如果是的话则直接将其数据更新到第一个参数传入的cookiejar中并返回.

如果第二个参数既不是字典,也不是cookielib.CookieJar的实例

那么采用迭代和set_cookie方法,将其更新到第一个参数传入的cookiejar并返回.

综上所述,该merge_cookies方法的作用是将第二参数传入的数据合并到第一个参数的cookielib.CookieJar的实例中

在看看在之前代码中 merge_cookies方法的用处:

cookies = request.cookies or {}

if not isinstance(cookies, cookielib.CookieJar):
    cookies = cookiejar_from_dict(cookies)

merged_cookies = merge_cookies(merge_cookies(RequestsCookieJar(), self.cookies), cookies)

这里连续两次调用 merge_cookies方法

第一个调用时将session.Session类的实例属性self.cookies合并和一个RequestsCookieJar实例对象中

那么Session类的实例属性self.cookies是什么数据呢?从源码中可以找到答案(代码进行了删减,只保留要需要的)

class Session(SessionRedirectMixin):
    def __init__(self):
        self.cookies = cookiejar_from_dict({})

是调用 cookiejar_from_dict方法处理一个空字典,那么返回值必然是一个RequestCookieJar类的实例对象.

 那么在回到之前的逻辑中

第一次调用merge_cookies方法就是将两个RequestCookieJar类的实例对象合并为一个.

然后再将这个合并得到的RequestCookieJar类的实例对象与cookie进行合并

cookie是源自Request实例对象req或者空字典调用cookiejar_from_dict方法得到的一个RequestCookieJar类的实例

这样也就是再次将两个RequestCookieJar类的实例对象合并成一个

本质也就是将方法层携带的cookie数据与会话层设置的cookie数据进行合并.

自此,cookie数据处理完成.

cookie处理小结

1、需要梳理清楚Request自创的RequestCookieJar类和http.cookiejar中CookJar类、Cookie类的关系

2、理解cookiejar_from_dict方法,merge_cookies方法以及http.cookiejar.CookJar.set_cookie方法的逻辑

3、cookie处理的本质是合并方法层的cookie数据与会话层设置的cookie数据

身份验证

身份验证的代码相对cookie处理来说,不仅更简短,也更简单.

auth = request.auth
if self.trust_env and not auth and not self.auth:
    auth = get_netrc_auth(request.url)

这里涉及到两个实例属性在Session类的源码中能找到对应的值(代码进行了删减,只保留要需要的):

​
class Session(SessionRedirectMixin):
    def __init__(self):
        self.trust_env = True
        self.auth = None​

从这两个默认属性可以看出,如果Request实例req对象的auth不为空,也就是说有auth参数的话

该判断既为False,既不行身份认证了.

如果auth参数为None的话,会调用 get_netrc_auth方法默认的user目录中的netrc文件

如果与req对象中访问的url地址能匹配上,最后会返回一个auth认证需要的的tuple.

该方法涉及了python内置的netrc模块,此处也不进行展开了,有兴趣可以自行研究.

贴上源码:

def get_netrc_auth(url, raise_errors=False):
    """Returns the Requests tuple auth for a given url from netrc."""
    try:
        from netrc import netrc, NetrcParseError
        netrc_path = None
        for f in NETRC_FILES:
            try:
                loc = os.path.expanduser('~/{}'.format(f))
            except KeyError:
                return
            if os.path.exists(loc):
                netrc_path = loc
                break
        if netrc_path is None:
            return
        ri = urlparse(url)
        splitstr = b':'
        if isinstance(url, str):
            splitstr = splitstr.decode('ascii')
        host = ri.netloc.split(splitstr)[0]
        try:
            _netrc = netrc(netrc_path).authenticators(host)
            if _netrc:
                login_i = (0 if _netrc[0] else 1)
                return (_netrc[login_i], _netrc[2])
        except (NetrcParseError, IOError):
            if raise_errors:
                raise
    except (ImportError, AttributeError):
        pass

 构造PreparedRequest对象

在prepare_reqeust方法中关于PreparedReques对象的构造的的代码中

一开始就要实例化一个 PreparedRequest对象,那还是只能从PreparedRequest的源码看起

PreparedRequest的源码位于model.py文件中.下面只贴了部分比较重要的代码:

class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
    def __init__(self):
        self.method = None
        self.url = None
        self.headers = None
        self._cookies = None
        self.body = None
        self.hooks = default_hooks()
        self._body_position = None

    def prepare(self,
            method=None, url=None, headers=None, files=None, data=None,
            params=None, auth=None, cookies=None, hooks=None, json=None):

        self.prepare_method(method)
        self.prepare_url(url, params)
        self.prepare_headers(headers)
        self.prepare_cookies(cookies)
        self.prepare_body(data, files, json)
        self.prepare_auth(auth, url)
        self.prepare_hooks(hooks)

同样在__init__初始化函数中设置了七个实例属性,其中default_hooks方法在之前已经讲解过,不在重复

最重要的是prepare方法,在prepare_reqeust方法中也是调用这个prepare方法

prepare方法中又调用了七个实例方法(鉴于部分方法代码较长,就不贴了详细代码了):

def prepare_method(self, method):
     pass

def prepare_url(self, url, params):
    pass

def prepare_headers(self, headers):
    pass

def prepare_body(self, data, files, json=None):
    pass

def prepare_auth(self, auth, url=''):
    pass

def prepare_cookies(self, cookies):
    pass

def prepare_hooks(self, hooks):
    pass

其实这七个实例方法与七个实例属性相对应,每个实例方法对一个实例属性进行操作.

也就说,默认情况下七个实例属性都是None,除了self.hooks是有特殊结构的.

每个实例方法都执行后,都会给七个实例属性赋于新的值,不在是默认值

下面简单总结下每个方法的作用:

prepare_method:按协议规范将method转换为大写并进行ascii编码

prepare_url:按照协议规范对url进行修正检查,能修正的进行修正,如果不符合协议抛出错误

prepare_headers:按照协议对headers的各个数据进行规范检查,不符合抛出错误,这里引入了大小写不敏感的字典类.

prepare_body:将data,josn参数的数据按协议规范封装入body

prepare_auth:按照协议对auth参数进行封装

prepare_cookies:按照之前分析过的原理检查cookie,并最终将其封装入headers中

prepare_hooks:注册回调函数,原理与之前介绍的一致.

现在看回prepare_reqeust方法中的代码:

p = PreparedRequest()
p.prepare(
            method=request.method.upper(),
            url=request.url,
            files=request.files,
            data=request.data,
            json=request.json,
            headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict),
            params=merge_setting(request.params, self.params),
            auth=merge_setting(auth, self.auth),
            cookies=merged_cookies,
            hooks=merge_hooks(request.hooks, self.hooks),
        )

 PreparedRequest的实例对象和parpred方法都已经了解了

只是调用parpred方法时,一些参数传入前调用了merge_setting方法进行处理

那么就再研究下merge_setting方法:

def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
    if session_setting is None:
        return request_setting
    if request_setting is None:
        return session_setting
    if not (
            isinstance(session_setting, Mapping) and
            isinstance(request_setting, Mapping)
    ):
        return request_setting
    merged_setting = dict_class(to_key_val_list(session_setting))
    merged_setting.update(to_key_val_list(request_setting))

    none_keys = [k for (k, v) in merged_setting.items() if v is None]
    for key in none_keys:
        del merged_setting[key]
    return merged_setting

此方法中又涉及一个 to_key_val_list方法,位于utils.py文件中:

def to_key_val_list(value):
    if value is None:
        return None
    if isinstance(value, (str, bytes, bool, int)):
        raise ValueError('cannot encode objects that are not 2-tuples')
    if isinstance(value, Mapping):
        value = value.items()

这个 to_key_val_list方法对value进行类型检查,若为None就直接返回None

若为str, bytes, bool, int之中的任意类型也抛出错误

若为Mapping的实例,则返回其他items,也就是字典的items()方法

回到merge_setting方法中

同样最初也就进行空值检查,分别检查方法层和会话层的数据,若一方为空则返回另一方的数据

若两方都不为None且都是Mapping的实例,则先将会话层的数据转换成dict_class参数指定的数据类型

然后将方法层的数据更新到由会话层转换而来的这个数据结构中

最后从这个数据结构中查找并删除value值为空的键值对,将最终的结构返回.

出了merge_setting方法,还用到一个merge_hooks方法:

def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
    if session_hooks is None or session_hooks.get('response') == []:
        return request_hooks

    if request_hooks is None or request_hooks.get('response') == []:
        return session_hooks

    return merge_setting(request_hooks, session_hooks, dict_class)

同样的,该方法最终也是调用merge_setting方法进行方法层和会话层hooks参数的合并,只是在之前进行了条件判断. 

了解完了这些代码,也就明白了相关数据或者参数在PreparedRequest对象构造

以及prepare方法是如何对PreparedRequest对象进行更进一步的属性值操作的

最后将进行了以系列属性操作后的PreparedRequest对象作为self.prepare_request方法的返回结果

prepare_request方法小结

1、prepare_request方法是请求过程中最核心的方法

2、prepare_request方法中经过了cookie处理,身份验证,PreparedRequest对象构造三个步骤

3、PreparedRequest对象是requests库中最底层也是最核心的对象

4、prepare_request方法中最核心的思想就是按照HTTP协议进行数据检查和数据合并

环境参数合并

还是先贴上环境参数合并的这部代码:

proxies = proxies or {}
settings = self.merge_environment_settings( prep.url, proxies, stream, verify, cert)
send_kwargs = {'timeout': timeout,'allow_redirects': allow_redirects,}
send_kwargs.update(settings)

这段代码中,最重要的就是理解 self.merge_environment_settings方法:

def merge_environment_settings(self, url, proxies, stream, verify, cert):
    if self.trust_env:
        no_proxy = proxies.get('no_proxy') if proxies is not None else None
        env_proxies = get_environ_proxies(url, no_proxy=no_proxy)
        for (k, v) in env_proxies.items():
            proxies.setdefault(k, v)

        if verify is True or verify is None:
            verify = (os.environ.get('REQUESTS_CA_BUNDLE') or
                      os.environ.get('CURL_CA_BUNDLE'))

    proxies = merge_setting(proxies, self.proxies)
    stream = merge_setting(stream, self.stream)
    verify = merge_setting(verify, self.verify)
    cert = merge_setting(cert, self.cert)

    return {'verify': verify, 'proxies': proxies, 'stream': stream,'cert': cert}

总的来说,这个 self.merge_environment_settings方法也并不复杂

首先从本机环境中检查代理和SSL 证书的情况

然后通过很熟悉的merge_setting方法将各种参数进行合并

最后返回一个与HTTP协议中关键名称相对应的包含各个参数的字典对象

再回到下面这段代码:

proxies = proxies or {}
settings = self.merge_environment_settings( prep.url, proxies, stream, verify, cert)
send_kwargs = {'timeout': timeout,'allow_redirects': allow_redirects,}
send_kwargs.update(settings)

代理,SSL证书等参数设置合并完成后,在添加上超时和重定向参数,构成最后的参数字典对象.

请求发送 

请求发送仅仅是调用self.send方法将封装好的prep对象和参数字典发送到指定的服务器

从而等待网络响应且将自动的将响应封装成一个response对象

resp = self.send(prep, **send_kwargs)

这里涉及到的socket等网络传输过程就不展开了. 

附源码结构梳理 

总结前梳理下reqeust库中各个py文件代码的大概用途:

py文件名 作用说明
__init__ 必须的python包指示文件,主要进行兼容,版本验证
__version__ 作者版权等相关信息
_internal_utils 提供内部使用的函数方法
adapters 传输适配器相关
api API接口调用方法
auth 身份验证相关的方法
certs certifi包的CA证书
compat 主要处理python2和python3版本之间的兼容性问题
cookies cookie相关处理
exceptions 异常定义
help 帮助,bug报告相关
hooks 事件钩子,回调函数
models Requests中的主要对象,包括请求对象,响应对象等
packages 三方依赖库的向后的兼容能力
sessions session对象

status_codes

HTTP协议状态码
structures requests库中定义的数据结构
utils requests库中使用以及外部扩展所需要的函数方法

 

你可能感兴趣的:(爬虫)