zoukankan      html  css  js  c++  java
  • python接口自动化

    高级用法

    本篇文档涵盖了 Requests 的一些高级特性。

    会话对象

    会话对象让你能够跨请求保持某些参数。它也会在同一个 Session 实例发出的所有请求之间保持 cookie, 期间使用 urllib3 的 connection pooling 功能。所以如果你向同一主机发送多个请求,底层的 TCP 连接将会被重用,从而带来显著的性能提升。 (参见 HTTP persistent connection).

    会话对象具有主要的 Requests API 的所有方法。

    我们来跨请求保持一些 cookie:

    s = requests.Session()
    s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
    r = s.get("http://httpbin.org/cookies")
    print(r.text)
    
    {
      "cookies": {
        "sessioncookie": "123456789"
      }
    }

    会话也可用来为请求方法提供缺省数据。这是通过为会话对象的属性提供数据来实现的:

    s = requests.Session()
    s.auth = ('user', 'pass')
    s.headers.update({'x-test': 'true'})
    
    # both 'x-test' and 'x-test2' are sent
    s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})

    任何你传递给请求方法的字典都会与已设置会话层数据合并。方法层的参数覆盖会话的参数。

    不过需要注意,就算使用了会话,方法级别的参数也不会被跨请求保持。下面的例子只会和第一个请求发送 cookie ,而非第二个:

    s = requests.Session()
    
    r = s.get('http://httpbin.org/cookies', cookies={'from-my': 'browser'})
    print(r.text)
    # '{"cookies": {"from-my": "browser"}}'
    
    r = s.get('http://httpbin.org/cookies')
    print(r.text)
    # '{"cookies": {}}'

    如果你要手动为会话添加 cookie,就使用 Cookie utility 函数 来操纵 Session.cookies

    会话还可以用作前后文管理器:

    with requests.Session() as s:
        s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')

    这样就能确保 with 区块退出后会话能被关闭,即使发生了异常也一样。

    从字典参数中移除一个值:
    有时你会想省略字典参数中一些会话层的键。要做到这一点,你只需简单地在方法层参数中将那个键的值设置为 None ,那个键就会被自动省略掉。

    包含在一个会话中的所有数据你都可以直接使用。学习更多细节请阅读 会话 API 文档

    请求与响应对象

    任何时候进行了类似 requests.get() 的调用,你都在做两件主要的事情。其一,你在构建一个 Request 对象, 该对象将被发送到某个服务器请求或查询一些资源。其二,一旦 requests 得到一个从服务器返回的响应就会产生一个 Response 对象。该响应对象包含服务器返回的所有信息,也包含你原来创建的 Request 对象。如下是一个简单的请求,从 Wikipedia 的服务器得到一些非常重要的信息:

     r = requests.get('http://en.wikipedia.org/wiki/Monty_Python')

    如果想访问服务器返回给我们的响应头部信息,可以这样做:

    print( r.headers)
    
    
    
    {'content-length': '56170', 'x-content-type-options': 'nosniff', 'x-cache':
    'HIT from cp1006.eqiad.wmnet, MISS from cp1010.eqiad.wmnet', 'content-encoding':
    'gzip', 'age': '3080', 'content-language': 'en', 'vary': 'Accept-Encoding,Cookie',
    'server': 'Apache', 'last-modified': 'Wed, 13 Jun 2012 01:33:50 GMT',
    'connection': 'close', 'cache-control': 'private, s-maxage=0, max-age=0,
    must-revalidate', 'date': 'Thu, 14 Jun 2012 12:59:39 GMT', 'content-type':
    'text/html; charset=UTF-8', 'x-cache-lookup': 'HIT from cp1006.eqiad.wmnet:3128,
    MISS from cp1010.eqiad.wmnet:80'}

    然而,如果想得到发送到服务器的请求的头部,我们可以简单地访问该请求,然后是该请求的头部:

    r = requests.get('http://en.wikipedia.org/wiki/Monty_Python')
    print(r.request.headers)
    
    {'User-Agent': 'python-requests/2.21.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}

    准备的请求 (Prepared Request)

    当你从 API 或者会话调用中收到一个 Response 对象时,request 属性其实是使用了 PreparedRequest。有时在发送请求之前,你需要对 body 或者 header (或者别的什么东西)做一些额外处理,下面演示了一个简单的做法:

    from requests import Request, Session
    
    s = Session()
    req = Request('GET', url,
        data=data,
        headers=header
    )
    prepped = req.prepare()
    
    # do something with prepped.body
    # do something with prepped.headers
    
    resp = s.send(prepped,
        stream=stream,
        verify=verify,
        proxies=proxies,
        cert=cert,
        timeout=timeout
    )
    
    print(resp.status_code)

    由于你没有对 Request 对象做什么特殊事情,你立即准备和修改了 PreparedRequest 对象,然后把它和别的参数一起发送到 requests.* 或者 Session.*

    然而,上述代码会失去 Requests Session 对象的一些优势, 尤其 Session 级别的状态,例如 cookie 就不会被应用到你的请求上去。要获取一个带有状态的 PreparedRequest, 请用 Session.prepare_request() 取代 Request.prepare() 的调用,如下所示:

    from requests import Request, Session
    
    s = Session()
    req = Request('GET',  url,
        data=data
        headers=headers
    )
    
    prepped = s.prepare_request(req)
    
    # do something with prepped.body
    # do something with prepped.headers
    
    resp = s.send(prepped,
        stream=stream,
        verify=verify,
        proxies=proxies,
        cert=cert,
        timeout=timeout
    )
    
    print(resp.status_code)

    SSL 证书验证

    Requests 可以为 HTTPS 请求验证 SSL 证书,就像 web 浏览器一样。SSL 验证默认是开启的,如果证书验证失败,Requests 会抛出 SSLError:

    >>> requests.get('https://requestb.in')
    requests.exceptions.SSLError: hostname 'requestb.in' doesn't match either of '*.herokuapp.com', 'herokuapp.com'

    在该域名上没有设置 SSL,所以失败了。但 Github 设置了 SSL:

    >>> requests.get('https://github.com', verify=True)
    <Response [200]>

    你可以为 verify 传入 CA_BUNDLE 文件的路径,或者包含可信任 CA 证书文件的文件夹路径:

    requests.get('https://github.com', verify='/path/to/certfile')

    或者将其保持在会话中:

    s = requests.Session()
    s.verify = '/path/to/certfile'
    注解
    如果 verify 设为文件夹路径,文件夹必须通过 OpenSSL 提供的 c_rehash 工具处理。

    你还可以通过 REQUESTS_CA_BUNDLE 环境变量定义可信任 CA 列表。

    如果你将 verify 设置为 False,Requests 也能忽略对 SSL 证书的验证。

    >>> requests.get('https://kennethreitz.org', verify=False)
    <Response [200]>

    默认情况下, verify 是设置为 True 的。选项 verify 仅应用于主机证书。

    # 对于私有证书,你也可以传递一个 CA_BUNDLE 文件的路径给 verify。你也可以设置 # REQUEST_CA_BUNDLE 环境变量。

    客户端证书

    你也可以指定一个本地证书用作客户端证书,可以是单个文件(包含密钥和证书)或一个包含两个文件路径的元组:

    >>> requests.get('https://kennethreitz.org', cert=('/path/client.cert', '/path/client.key'))
    <Response [200]>

    或者保持在会话中:

    s = requests.Session()
    s.cert = '/path/client.cert'

    如果你指定了一个错误路径或一个无效的证书:

    >>> requests.get('https://kennethreitz.org', cert='/wrong_path/client.pem')
    SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib
    警告
    本地证书的私有 key 必须是解密状态。目前,Requests 不支持使用加密的 key。

    CA 证书

    Requests 默认附带了一套它信任的根证书,来自于 Mozilla trust store。然而它们在每次 Requests 更新时才会更新。这意味着如果你固定使用某一版本的 Requests,你的证书有可能已经 太旧了。

    从 Requests 2.4.0 版之后,如果系统中装了 certifi 包,Requests 会试图使用它里边的 证书。这样用户就可以在不修改代码的情况下更新他们的可信任证书。

    为了安全起见,我们建议你经常更新 certifi!

    响应体内容工作流

    默认情况下,当你进行网络请求后,响应体会立即被下载。你可以通过 stream 参数覆盖这个行为,推迟下载响应体直到访问 Response.content 属性:

    tarball_url = 'https://github.com/kennethreitz/requests/tarball/master'
    r = requests.get(tarball_url, stream=True)

    此时仅有响应头被下载下来了,连接保持打开状态,因此允许我们根据条件获取内容:

    if int(r.headers['content-length']) < TOO_LONG:
      content = r.content
      ...

    你可以进一步使用 Response.iter_content 和 Response.iter_lines 方法来控制工作流,或者以 Response.raw 从底层 urllib3 urllib3.HTTPResponse <urllib3.response.HTTPResponse 读取未解码的响应体。

    如果你在请求中把 stream 设为 TrueRequests 无法将连接释放回连接池,除非你 消耗了所有的数据,或者调用了 Response.close。 这样会带来连接效率低下的问题。如果你发现你在使用 stream=True 的同时还在部分读取请求的 body(或者完全没有读取 body),那么你就应该考虑使用 with 语句发送请求,这样可以保证请求一定会被关闭:

    with requests.get('http://httpbin.org/get', stream=True) as r:
        # 在此处理响应。

    保持活动状态(持久连接)

    好消息——归功于 urllib3,同一会话内的持久连接是完全自动处理的!同一会话内你发出的任何请求都会自动复用恰当的连接!

    注意:只有所有的响应体数据被读取完毕连接才会被释放为连接池;所以确保将 stream 设置为 False 或读取 Response 对象的 content 属性。

    流式上传

    Requests支持流式上传,这允许你发送大的数据流或文件而无需先把它们读入内存。要使用流式上传,仅需为你的请求体提供一个类文件对象即可:

    with open('massive-body') as f:
        requests.post('http://some.url/streamed', data=f)
    警告
    我们强烈建议你用二进制模式(binary mode)打开文件。这是因为 requests 可能会为你提供 header 中的 Content-Length,在这种情况下该值会被设为文件的字节数。如果你用文本模式打开文件,就可能碰到错误。

    块编码请求

    对于出去和进来的请求,Requests 也支持分块传输编码。要发送一个块编码的请求,仅需为你的请求体提供一个生成器(或任意没有具体长度的迭代器):

    def gen():
        yield 'hi'
        yield 'there'
    
    requests.post('http://some.url/chunked', data=gen())

    对于分块的编码请求,我们最好使用 Response.iter_content() 对其数据进行迭代。在理想情况下,你的 request 会设置 stream=True,这样你就可以通过调用 iter_content 并将分块大小参数设为 None,从而进行分块的迭代。如果你要设置分块的最大体积,你可以把分块大小参数设为任意整数。

    POST 多个分块编码的文件

    你可以在一个请求中发送多个文件。例如,假设你要上传多个图像文件到一个 HTML 表单,使用一个多文件 field 叫做 "images":

    <input type="file" name="images" multiple="true" required="true"/>

    要实现,只要把文件设到一个元组的列表中,其中元组结构为 (form_field_name, file_info):

    >>> url = 'http://httpbin.org/post'
    >>> multiple_files = [
            ('images', ('foo.png', open('foo.png', 'rb'), 'image/png')),
            ('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))]
    >>> r = requests.post(url, files=multiple_files)
    >>> r.text
    {
      ...
      'files': {'images': 'data:image/png;base64,iVBORw ....'}
      'Content-Type': 'multipart/form-data; boundary=3131623adb2043caaeb5538cc7aa0b3a',
      ...
    }
    警告
    
    我们强烈建议你用二进制模式(binary mode)打开文件。这是因为 requests 可能会为你提供 header 中的 Content-Length,在这种情况下该值会被设为文件的字节数。如果你用文本模式打开文件,就可能碰到错误。

    事件挂钩

    Requests有一个钩子系统,你可以用来操控部分请求过程,或信号事件处理。

    可用的钩子:

    response:
    从一个请求产生的响应

    你可以通过传递一个 {hook_name: callback_function} 字典给 hooks 请求参数为每个请求分配一个钩子函数:

    hooks=dict(response=print_url)

    callback_function 会接受一个数据块作为它的第一个参数。

    def print_url(r, *args, **kwargs):
        print(r.url)

    若执行你的回调函数期间发生错误,系统会给出一个警告。

    若回调函数返回一个值,默认以该值替换传进来的数据。若函数未返回任何东西,也没有什么其他的影响。

    我们来在运行期间打印一些请求方法的参数:

    >>> requests.get('http://httpbin.org', hooks=dict(response=print_url))
    http://httpbin.org
    <Response [200]>

    自定义身份验证

    Requests 允许你使用自己指定的身份验证机制。

    任何传递给请求方法的 auth 参数的可调用对象,在请求发出之前都有机会修改请求。

    自定义的身份验证机制是作为 requests.auth.AuthBase 的子类来实现的,也非常容易定义。Requests 在 requests.auth 中提供了两种常见的的身份验证方案: HTTPBasicAuth 和 HTTPDigestAuth 

    假设我们有一个web服务,仅在 X-Pizza 头被设置为一个密码值的情况下才会有响应。虽然这不太可能,但就以它为例好了。

    from requests.auth import AuthBase
    
    class PizzaAuth(AuthBase):
        """Attaches HTTP Pizza Authentication to the given Request object."""
        def __init__(self, username):
            # setup any auth-related data here
            self.username = username
    
        def __call__(self, r):
            # modify and return the request
            r.headers['X-Pizza'] = self.username
            return r

    然后就可以使用我们的PizzaAuth来进行网络请求:

    >>> requests.get('http://pizzabin.org/admin', auth=PizzaAuth('kenneth'))
    <Response [200]>

    流式请求

    使用 Response.iter_lines() 你可以很方便地对流式 API (例如 Twitter 的流式 API ) 进行迭代。简单地设置 stream 为 True 便可以使用 iter_lines 对相应进行迭代:

    import json
    import requests
    
    r = requests.get('http://httpbin.org/stream/20', stream=True)
    
    for line in r.iter_lines():
    
        # filter out keep-alive new lines
        if line:
            decoded_line = line.decode('utf-8')
            print(json.loads(decoded_line))

    当使用 decode_unicode=True 在 Response.iter_lines() 或 Response.iter_content() 中时,你需要提供一个回退编码方式,以防服务器没有提供默认回退编码,从而导致错误:

    r = requests.get('http://httpbin.org/stream/20', stream=True)
    
    if r.encoding is None:
        r.encoding = 'utf-8'
    
    for line in r.iter_lines(decode_unicode=True):
        if line:
            print(json.loads(line))
    警告
    
    iter_lines 不保证重进入时的安全性。多次调用该方法 会导致部分收到的数据丢失。如果你要在多处调用它,就应该使用生成的迭代器对象:
    lines = r.iter_lines()
    # 保存第一行以供后面使用,或者直接跳过
    
    first_line = next(lines)
    
    for line in lines:
        print(line)

    代理

    如果需要使用代理,你可以通过为任意请求方法提供 proxies 参数来配置单个请求:

    import requests
    
    proxies = {
      "http": "http://10.10.1.10:3128",
      "https": "http://10.10.1.10:1080",
    }
    
    requests.get("http://example.org", proxies=proxies)

    你也可以通过环境变量 HTTP_PROXY 和 HTTPS_PROXY 来配置代理。

    $ export HTTP_PROXY="http://10.10.1.10:3128"
    $ export HTTPS_PROXY="http://10.10.1.10:1080"
    
    $ python
    >>> import requests
    >>> requests.get("http://example.org")

    若你的代理需要使用HTTP Basic Auth,可以使用 http://user:password@host/ 语法:

    proxies = {
        "http": "http://user:pass@10.10.1.10:3128/",
    }

    要为某个特定的连接方式或者主机设置代理,使用 scheme://hostname 作为 key, 它会针对指定的主机和连接方式进行匹配。

    proxies = {'http://10.20.1.128': 'http://10.10.1.10:5323'}

    注意,代理 URL 必须包含连接方式。

    SOCKS

    2.10.0 新版功能.

    除了基本的 HTTP 代理,Request 还支持 SOCKS 协议的代理。这是一个可选功能,若要使用, 你需要安装第三方库。

    你可以用 pip 获取依赖:

  • 相关阅读:
    知识点拾遗——装饰器
    知识点拾遗——函数部分
    re模块和分组
    re模块进阶
    面向对象总结
    模块总结整理
    项目目录结构
    日志(模块logging)
    位、字节、字符 概念
    socketserver实现并发通讯
  • 原文地址:https://www.cnblogs.com/wutaotaosin/p/11404964.html
Copyright © 2011-2022 走看看