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 获取依赖:

  • 相关阅读:
    HDU 2236 无题Ⅱ
    Golden Tiger Claw(二分图)
    HDU 5969 最大的位或 (思维,贪心)
    HDU 3686 Traffic Real Time Query System (图论)
    SCOI 2016 萌萌哒
    Spring Boot支持控制台Banner定制
    构建第一个Spring Boot程序
    Spring Boot重要模块
    Java fastjson JSON和String互相转换
    BCompare 4 Windows激活方法【试用期30天重置】
  • 原文地址:https://www.cnblogs.com/wutaotaosin/p/11404964.html
Copyright © 2011-2022 走看看