• requests:用于发送http请求,专为人类设计


    介绍

    requests模块是一个专门用来发送http请求的模块
    

    如何发送请求

    import requests
    
    """
    使用requests模块发送请求非常简单
    首先请求有get、post、delete、put、head
    这些请求直接通过requests来调用即可
    """
    # 这样就发送了一个get请求
    res = requests.get("http://www.baidu.com")
    # res就是我们发送请求之后,得到的返回值,这是一个Response对象。里面包含了很多的属性
    """
    url:我们请求的url
    status_code:返回的状态码
    reason:一般网站中跟在状态码后边的那个,比如200 ok,404 not found
    headers:返回的头部信息
    cookies:返回的cookie
    encoding:返回网站的编码
    text:网页的html
    content:网页的html(字节)
    """
    print(res.url)  # http://www.baidu.com/
    print(res.status_code)  # 200
    print(res.reason)  # OK
    print(res.headers)  # {'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform', 'Connection': 'Keep-Alive', 'Content-Encoding': 'gzip', 'Content-Type': 'text/html', 'Date': 'Wed, 26 Jun 2019 12:22:26 GMT', 'Last-Modified': 'Mon, 23 Jan 2017 13:27:36 GMT', 'Pragma': 'no-cache', 'Server': 'bfe/1.0.8.18', 'Set-Cookie': 'BDORZ=27315; max-age=86400; domain=.baidu.com; path=/', 'Transfer-Encoding': 'chunked'}
    
    print(res.cookies)  # <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
    print(res.encoding)  # ISO-8859-1
    
    
    # 调用res.text可以直接打印html信息,但是由于可能会出现乱码
    # 因此需要加上一个res.encoding = res.apparent_encoding
    # 因为实际上获得的最原始的流数据,还是字节的形式,也就是我们上面说的content。
    # 只不过requests为了调用者方便,从而进行了封装,将content解码成了text
    # 但是使用res.encoding进行解码,有可能会造成编码错误,于是将apparent_encoding赋值给了encoding
    # 至于apparent_encoding是什么?
    # 实际上是根据python的一个可以对字节流所使用的编码进行检测的第三方模块(chardet),对content检测所得到的编码
    from chardet import detect
    # 可以看到检测出来是utf-8,如果我们再用ISO-8859-1那么可能会得到乱码
    print(detect(res.content).get("encoding"))  # utf-8
    # 实际上,这在requests内部也是这么做的
    """
    @property
    def apparent_encoding(self):
        return chardet.detect(self.content)['encoding']
    """
    
    # 至于其他的请求也是一样的
    # 实际上这些方法在requests中,都统一调用一个方法,都调用requests.request()
    """
    requests.get(url) == requests.request('get', url) 
    requests.post(url) == requests.request('post', url) 
    requests.head(url) == requests.request('head', url) 
    requests.delete(url) == requests.request('delete', url) 
    requests.put(url) == requests.request('put', url) 
    """
    # 为了方便,创建了这些对应的api
    

    传递url参数

    有些时候我们相对url添加一些查询字符串(query string),比如我们百度搜索python,那么就可以输入https://www.baidu.com/s?wd=python,但是这样显然不够人性化,因为requests就是号称for humans。
    
    import requests
    
    # 调用get方法时,可以加上一个参数params,以字典的形式传入
    res = requests.get("http://www.baidu.com/s", params={"wd": "python"})
    # 可以看到,自动帮我们拼接在一起了
    print(res.url)  # http://www.baidu.com/s?wd=python
    
    # 当然也可以传递多个参数
    res = requests.get("http://httpbin.org/get", params={"k1": "v1", "k2": ["v2", "v3"]})
    print(res.url)  # http://httpbin.org/get?k1=v1&k2=v2&k2=v3
    

    响应内容

    import requests
    import re
    
    res = requests.get("http://www.baidu.com/s", params={"wd": "python"})
    # res.text可以打印出返回的html页面内容,也就是单击右键查看网页源代码所得到的内容
    # 但是之前说过requests,是基于http头部响应的编码进行解码,也就是res.encoding
    # 但是返回的内容的编码并不一定是res.encoding,因此我们可以在获取text之前改变编码,然后在获取text的时候就会使用我们指定的编码来对content进行解码了
    res.encoding = res.apparent_encoding
    
    text = res.text
    match = re.findall(r".{20}python.{20}", text)
    for i in match:
        print(i)
    """
    节选一部分输出
    
    ta-tools='{"title":"python吧-百度贴吧--python学习交流基地
    r=baidu&fm=sc&query=python&qid=e49f6f960022996
    ><th><a href="/s?wd=python%E8%87%AA%E5%AD%A6&r
    000013&rsp=0&f=1&oq=python&ie=utf-8&usm=1&rsv_
    WRNBwvCY6eGkEholvw">python自学</a></th><td></td>
    000001&rsp=1&f=1&oq=python&ie=utf-8&usm=1&rsv_
    WRNBwvCY6eGkEholvw">python为什么叫爬虫</a></th><td><
    9F%E6%95%99%E7%A8%8Bpython&rsf=1000012&rsp=2&f
    wvCY6eGkEholvw">菜鸟教程python</a></th></tr><tr><t
    &rsf=8&rsp=3&f=1&oq=python&ie=utf-8&usm=1&rsv_
    """
    

    二进制响应内容

    实际上,也正如我们之前说的,我们还可以以二进制的格式获取响应内容。比如图片,不像文本,图片这种内容只能以二进制的格式返回。然而我们之前也说过,即便是文本,仍是以二进制返回的,只不过为了文本方便阅读,在requests内部又封装了一个text,可以方便我们获取文本内容
    
    import requests
    
    res = requests.get("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561565986554&di=028d5ad55109447f9a136ad7654c7268&imgtype=0&src=http%3A%2F%2Fi0.hdslb.com%2Fbfs%2Farticle%2Fa79025d0a0789582f90110411355bffd81ccdac8.jpg")
    # 这个url是百度图片的一张我太太蕾姆的照片,如果这个时候再用text打印,会是一大堆乱码
    # 因为我们可以以二进制的格式写入文件里面
    with open("太太蕾姆.jpg", "wb") as f:
        f.write(res.content)
    


    json响应内容

    之前说过返回的是二进制的流数据,text是由content进行解码得到的。那么同理requests中json数据(如果符合json规范的话),也是由content转化得来的。这里不再代码演示了,和text一样,只不过由于没有@property,因此需要调用res.json()。
    
    但需要注意的是,如果返回的内容不符合json格式,导致JSON解码失败,那么 res.json() 就会抛出一个异常。然而成功调用 r.json() 并**不**意味着响应的成功。有的服务器会在失败的响应中包含一个 JSON 对象(比如 HTTP 500 的错误细节)。这种 JSON 会被解码返回。要检查请求是否成功,请使用 r.raise_for_status() 或者检查 r.status_code 是否和你的期望相同。
    

    原始响应内容

    在罕见的情况下,你可能会想获取来自服务器的原始套接字响应,那么可以使用res.raw。如果真的想这么做,那么请加上参数stream=True,否则得到的是一个空字节串。
    基本上不用这种方式获取数据
    
    
    import requests
    
    res = requests.get("https://api.github.com/events", stream=True)
    raw = res.raw
    # 可以对raw进行读取
    print(raw.read(10))  # b'x1fx8bx08x00x00x00x00x00x00x03'
    
    

    定制请求头

    很多网站,都设置了反爬虫机制。最常用的反爬虫机制就是,判断请求头,如果请求头不是浏览器的请求头,会直接将你屏蔽掉。
    
    
    import requests
    
    res = requests.get("http://www.baidu.com")
    # 我们可以通过res.request.headers 来看看请求头是什么?
    # 之前说的res.headers指的是服务器端返回的http报头
    # 而这里的res.request.headers指的是我们传过去的请求头
    print(res.request.headers)  # {'User-Agent': 'python-requests/2.19.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
    # 可以看到默认是'User-Agent': 'python-requests/2.19.1'
    
    # 这是百度,用来搜索的,所以没有做这种限制。本身就是爬虫,再搞反爬,也太。。。。
    # 但是其他网站,比如拉钩网,基本上百分百屏蔽
    # 所以我们可以加上一个请求头,也是通过字典来传值
    res = requests.get("http://www.baidu.com", headers={"User-Agent": "emmmmmmmm"})
    # 我这里是随便指定的,真正爬虫的时候,从浏览里拷贝一下就行
    print(res.request.headers)  # {'User-Agent': 'emmmmmmmm', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
    
    

    post请求

    上面都是关于get请求的,下面我们介绍post请求。post请求一般是用于表单提交的,比如登录页面,我们通过脚本来模拟表单登录
    
    
    import requests
    
    payload = {"username": "komeijisatori", "password": "123456", "remember": "1"}
    
    # 通过参数data指定
    res = requests.post("http://httpbin.org/post", data=payload)
    print(res.text)
    """
    {
      "args": {}, 
      "data": "", 
      "files": {}, 
      "form": {
        "password": "123456", 
        "remember": "1", 
        "username": "komeijisatori"
      }, 
      "headers": {
        "Accept": "*/*", 
        "Accept-Encoding": "gzip, deflate", 
        "Content-Length": "49", 
        ...
        ...
        ...
        ...
    """
    
    
    但有些时候,我们想发送的数据并不是以表单形式提交的,也就是需要传递一个string,但是格式是字典格式的。想到了什么,json。
    
    
    import requests
    
    payload = {"some": "data"}
    
    # 这时候指定参数json即可
    res = requests.post("https://api.github.com/some/endpoint", json=payload)
    
    # 或者手动转化为json数据也可以
    import json
    res = requests.post("https://api.github.com/some/endpoint", json=json.dumps(payload))
    
    

    post提交文件

    如果我们需要提交文件的话,那么requests也是支持的
    
    
    import requests
    
    payload = {"some": "data"}
    
    res = requests.post("http://httpbin.org/post", files={"file": open("1.txt", "rb")})
    print(res.text)
    """
    {
      "args": {}, 
      "data": "", 
      "files": {
        "file": "this is a file"
      }, 
      "form": {}, 
      "headers": {
        "Accept": "*/*", 
        "Accept-Encoding": "gzip, deflate", 
        "Content-Length": "155", 
        "Content-Type": "multipart/form-data; boundary=e34f63359be140870846d7fd5340daff", 
        "Host": "httpbin.org", 
        "User-Agent": "python-requests/2.19.1"
      }, 
      "json": null, 
      "origin": "120.244.41.108, 120.244.41.108", 
      "url": "https://httpbin.org/post"
    }
    """
    
    # 此外还可以显示的指定文件名
    res = requests.post("http://httpbin.org/post", files={"file": ("2.txt", open("1.txt", "rb"))})
    print(res.text)
    """
    {
      "args": {}, 
      "data": "", 
      "files": {
        "file": "this is a file"
      }, 
      "form": {}, 
      "headers": {
        "Accept": "*/*", 
        "Accept-Encoding": "gzip, deflate", 
        "Content-Length": "155", 
        "Content-Type": "multipart/form-data; boundary=ab3d5c2226580ff643ca947c0b4e2cee", 
        "Host": "httpbin.org", 
        "User-Agent": "python-requests/2.19.1"
      }, 
      "json": null, 
      "origin": "120.244.41.108, 120.244.41.108", 
      "url": "https://httpbin.org/post"
    }
    """
    
    

    可以看到,将我们文件的内容打印了出来。但是上传文件的话,我们推荐一种更好的方式

    import requests
    import os
    from requests_toolbelt.multipart.encoder import MultipartEncoder
    
    multipart_encoder = MultipartEncoder(
        fields={
            # 表单所需要的参数直接填在fields里面即可
            # 包括上传的文件也是
            'parentId': "xxx",
            'size': f'{os.stat("1.png").st_size}',
            'lParentId': "xxxxx",
            'isDir': "false",
            "fileSize": f'{os.stat("1.png").st_size}',
            "fileName": "1.png",
            "userId": "admin",
            "directCheck": "true",
            "mode": "0",
            'file': ('file', open("1.png", 'rb'))
        },
        boundary="----WebKitFormBoundaryIHASM5Ndp6lUZPA8"
    )
    
    session = requests.session()
    # 设置Content-Type
    session.headers["Content-Type"] = multipart_encoder.content_type
    session.post(
        "http://xxx.com",
        data=multipart_encoder
    )
    

    响应状态码

    我们还可以检测返回来的状态码
    
    
    import requests
    
    res = requests.get("https://www.baidu.com")
    print(res.status_code)  # 200
    
    # 如果发送了一个错误请求,那么可以调用raise_for_status来抛出异常
    # 由于我们这里返回正常,所以打印个None
    print(res.raise_for_status())  # None
    
    bad_res = requests.get("http://httpbin.org/status/404")
    print(bad_res.status_code)  # 404
    
    try:
        bad_res.raise_for_status()
    except Exception as e:
        import traceback
        print(traceback.format_exc())
        """
        Traceback (most recent call last):
          File "D:/mashiro/python模块/requests模块.py", line 14, in <module>
            bad_res.raise_for_status()
          File "C:python37libsite-packages
    equestsmodels.py", line 939, in raise_for_status
            raise HTTPError(http_error_msg, response=self)
        requests.exceptions.HTTPError: 404 Client Error: NOT FOUND for url: http://httpbin.org/status/404
    
        """
    
    

    响应头

    import requests
    
    res = requests.get("https://www.baidu.com")
    print(res.headers)
    """
    {'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform', 
    'Connection': 'Keep-Alive', 
    'Content-Encoding': 'gzip', 
    'Content-Type': 'text/html', 
    'Date': 'Wed, 26 Jun 2019 15:45:05 GMT', 
    'Last-Modified': 'Mon, 23 Jan 2017 13:23:55 GMT', 
    'Pragma': 'no-cache', 
    'Server': 'bfe/1.0.8.18', 
    'Set-Cookie': 'BDORZ=27315; max-age=86400; domain=.baidu.com; path=/', 
    'Transfer-Encoding': 'chunked'}
    """
    print(res.headers["Content-Type"])  # text/html
    
    

    import requests
    
    res = requests.get("https://www.baidu.com")
    cookies = res.cookies
    print(cookies.get_dict())  # {'BDORZ': '27315'}
    
    # 此外还可以将得到cookie作为参数传进去,cookies={}
    # 得到的cookies是一个 RequestsCookieJar类型,行为和字典类型,但是功能比字典更多,并且可以跨路径使用
    from requests.cookies import RequestsCookieJar
    jar = RequestsCookieJar()
    jar.set("cookie1_name", "cookie1_value")
    jar.set("cookie2_name", "cookie2_value")
    res = requests.get("http://httpbin.org/cookies", cookies=jar)
    print(res.text)
    """
    {
      "cookies": {
        "cookie1_name": "cookie1_value", 
        "cookie2_name": "cookie2_value"
      }
    }
    """
    
    

    重定向与请求历史

    有些时候当我们访问一个域名时,这个域名已经被修改了,但是呢?又怕你不知道,因此你访问以前的域名依旧可以,但是会被重定向到新的域名。
    重定向分为暂时性重定向和永久性重定向。
    暂时性重定向:code为302,当我们发表评论时,但是没有登录,此时就会被暂时重定向到登录页面
    永久性重定向:code为301,比如我们访问http://www.taobao.com,但是这个域名已经废弃了,因此会被永久性重定向到https://www.taobao.com
    
    调用res.history可以查看历史
    
    
    import requests
    
    res = requests.get("http://www.taobao.com")
    print(res.status_code)  # 200
    print(res.history)  # [<Response [301]>]
    
    

    此外我们还可以禁止重定向,只需要指定allow_redirects=False即可

    import requests
    
    res = requests.get("http://www.taobao.com", allow_redirects=False)
    print(res.status_code)  # 301
    print(res.history)  # []
    
    

    超时

    告诉requests,如果在规定的时间内一直没有连接上的话,那么就放弃连接,可以通过timeout指定
    
    
    import requests
    
    """
    timeout可以是一个元组,比如(m, n)
    第一个元素表示发送连接请求所使用的最大时间,如果m秒内连接没有建立成功,则放弃
    第二个元素则是等待服务器响应的所使用的最大时间,如果m秒内连接建立好了,但是n秒内服务器一直没有返回数据,也放弃
    
    这两种情况都会抛出一个Timeout异常
    
    如果timeout是一个int,比如m,那么两者的最大时间都是m
    """
    try:
        res = requests.get("https://www.google.com", timeout=(2, 3))
    except requests.exceptions.Timeout as e:
        print(e)  # HTTPSConnectionPool(host='www.google.com', port=443): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.VerifiedHTTPSConnection object at 0x000001E44616C320>, 'Connection to www.google.com timed out. (connect timeout=2)'))
    
    
    

    错误与异常

    • ConnectionError:遇到网络问题,如DNS查询失败,拒绝连接时
    • HTTPError:如果http请求返回了不成功的状态码,res.raise_for_status会抛出一个异常
    • Timeout:若请求超时,则抛出此异常
    • TooManyRedirects:若请求超过了最大重定向次数,则抛出此异常
    • requests.exceptions.RequestException:所以requests显示抛出的异常,都继承自该异常

    会话对象

    import requests
    
    """
    之前我们说过,requests里面的get、post等方法,底层都是调用的request方法
    但是request调用的是谁呢?
    
    去掉注释的话,长这样
    def request(method, url, **kwargs):
        with sessions.Session() as session:
            return session.request(method=method, url=url, **kwargs)
    
    可以看到首先是调用Session这个类实例化一个session对象,然后调用session下的request方法。
    
    session(会话对象),是一个类实例化的对象,那么将我们的一些参数给保存起来
    会话对象可以让我们跨请求保持某些参数,它也会在同一个Session实例发出的所有请求之间保持cookie,期间使用urllib3的connection pooling功能
    因为requests主要是基于urllib3的。
    如果我们向同一个主机发送多次请求,底层的tcp连接会被重用,从而带来显著的性能提升
    """
    session = requests.Session()  # 也可以调用requests.session,两者是一样的
    session.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
    
    res1 = session.get("http://httpbin.org/cookies")
    print(res1.text)
    """
    {
      "cookies": {
        "sessioncookie": "123456789"
      }
    }
    """
    
    res2 = requests.get("http://httpbin.org/cookies")
    print(res2.text)
    """
    {
      "cookies": {}
    }
    """
    
    

    可以看到,当我们使用res1.text的时候,能够打印出cookie,但是使用res2.text,就打印不出cookie了,这是为什么?

    当我们执行session.get('http://httpbin.org/cookies/set/sessioncookie/123456789')的时候,会返回一个cookie,这个cookie已经被保存在会话里了,因此当我们再次使用session(会话)发送请求的时候会自动将这个cookie带过去。但是当我们使用requests.get的时候,则是创建一个新会话。因此只能先拿到cookie,然后再显示的将cookie传过去,但如果是会话的话就不用了,因为会自动的将cookie保存起来

    此外会话还可以为请求方法提供缺省数据

    import requests
    
    
    session = requests.Session()
    """
    Session这个类里面,有很多的属性,比如headers,auth,cookies,proxies等等
    但是没有在__init__中定义,即便如此我们仍然可以通过属性访问的方式来添加
    """
    session.headers.update({"User-Agent": "kubernetes"})
    # 此时调用get方法的时候,会将headers带过去
    res = session.get("http://www.baidu.com")
    print(res.request.headers)  # {'User-Agent': 'kubernetes', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
    
    # 但是如果我们在请求的方法中又传了相应的参数,那么请求方法中的参数,会覆盖会话当中的参数
    res = session.get("http://www.baidu.com", headers={"User-Agent": "docker", "name": "satori"})
    print(res.request.headers)
    """
    {'User-Agent': 'docker', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 
    'Connection': 'keep-alive', 'name': 'satori', 
    'Cookie': 'BAIDUID=B2A159333ED50D72BC07D66F1040A68E:FG=1; BIDUPSID=B2A159333ED50D72BC07D66F1040A68E; H_PS_PSSID=26525_1447_21090_29135_29238_28519_29099_29131_28832_29221_26350_29072; PSTM=1561642196; delPer=0; BDSVRTM=0; BD_HOME=0'}
    """
    # 可以看到kubernetes被docker覆盖了,而且我们设置的name也出现了。最关键的是,居然还出现了cookie,这是为什么?
    # 因为在第一次访问百度的时候,返回给我们一个cookie,第二次访问的时候,使用的是同一个session,所以会将上一步得到的cookie带过去
    
    res = session.get("http://www.baidu.com")
    print(res.request.headers)
    """
    {'User-Agent': 'kubernetes', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 
    'Cookie': 'BAIDUID=AB0351E419827120EABABE13ECC4C9ED:FG=1; BIDUPSID=AB0351E419827120EABABE13ECC4C9ED; H_PS_PSSID=1422_21110_29135_29237_28519_29098_29131_28838_29220_22160; PSTM=1561642425; delPer=0; BDSVRTM=0; BD_HOME=0'}
    """
    # 这里问题又来了,可以看到,我们第三次访问的时候,User-Agent又变回了kubernetes,而且name也没了,cookie还在。这是为什么?
    # 因为方法中的参数不会跨请求保持,我们在请求的时候所设置的参数,仅仅对那一次请求有效。如果之后不指定,那么请求还是会使用会话里面的,所以是这个结果。
    
    
    

    并且,由于Session类实现了__enter__和__exit__方法,所以还可以使用with语句


    请求与响应对象

    在任何时候,只要进行了类似于requests.get的调用,都在做两件主要的事情。第一,在构建一个Request对象,该对象被发送到某个服务器上请求或查询一些资源。第二,一旦requests得到一个从服务器返回的相应就会产生一个Response对象、该响应对象包含服务器返回的所有信息,也包含原来创建的Request对象。

    import requests
    
    
    session = requests.Session()
    # 先来看看session.request的源码
    # 值得一提的是,requests.get、post等请求方法,都是调用的requests.request
    # 但是requests.requests实际上也是创建了一个session对象,调用session对象下的request方法,只不过这个session对象在这一次请求之后就没有了,因为我们没有保存
    # 而session对象下也有get、post等方法,当然调用也是session.request。
    # 因此无论是通过requests主模块、还是创建的session对象,其所调用的get、post、delete等请求方法,最终调用的都是session对象下的request方法
    """
    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):
    
        # Create the Request.创建一个Request对象
        # 里面主要包装我们的一些参数
        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 the request.
        send_kwargs = {
            'timeout': timeout,
            'allow_redirects': allow_redirects,
        }
        send_kwargs.update(settings)
        # 发送请求,得到Response对象
        resp = self.send(prep, **send_kwargs)
    
        return resp
    """
    res = session.get("https://www.baidu.com")
    # 获取服务器返回的头部信息
    print(res.headers)
    """
    {'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform', 
    'Connection': 'Keep-Alive', 
    'Content-Encoding': 'gzip', 
    'Content-Type': 'text/html', 
    'Date': 'Thu, 27 Jun 2019 14:07:13 GMT', 
    'Last-Modified': 'Mon, 23 Jan 2017 13:23:55 GMT', 
    'Pragma': 'no-cache', 'Server': 'bfe/1.0.8.18', 
    'Set-Cookie': 'BDORZ=27315; max-age=86400; domain=.baidu.com; path=/', 
    'Transfer-Encoding': 'chunked'}
    """
    
    # 还可以获取发送给服务器的请求的头部
    print(res.request.headers)  # {'User-Agent': 'python-requests/2.19.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
    
    
    

    准备的请求(prepared request)

    当从api或者会话调用中收到一个Response对象时,request属性实际上使用了Prepared Request。因此在发送请求之前,我们可以对body、header做一些额外的处理

    import requests
    
    
    session = requests.Session()
    
    req = requests.Request("get", url="https://www.baidu.com")
    prepped = req.prepare()
    
    # 可以做一些额外的处理,这里处理请求头
    prepped.headers["User-Agent"] = "satori"
    
    # 调用session的send方法,这个requests模块内部的session.request本质上也是这么做的,返回的就是Response对象
    res = session.send(prepped)
    print(res.request.headers)  # {'User-Agent': 'satori'}
    print(res.cookies.get_dict())  # {'BIDUPSID': '6A947E75D63F889E10C89D7E6A2870AA', 'PSTM': '1561645151', 'BD_NOT_HTTPS': '1'}
    print(res.url)  # https://www.baidu.com/
    
    

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

    只需要将prepped = req.prepare()换成prepped = session.prepare_request()即可

    不过感觉不常用,至少我基本上没怎么用过


    ssl证书验证

    requests可以为HTTPS请求验证ssl证书,就像web浏览器一样。ssl验证默认是开启的,如果证书验证失败,那么requests会抛出一个SSLError,对于那些没有设置ssl的,如果验证失败那么可以在请求方法中加上verify=False,表示不验证

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

    或者让其保持在会话中

    s = requests.Session()
    s.verify = '证书路径'
    # 值得一提的是
    # 如果 verify 设为文件夹路径,文件夹必须通过 OpenSSL 提供的 c_rehash 工具处理。
    
    

    客户端证书(不常用)

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

    requests.get('url', cert=('client.cert', 'client.key'))
    
    

    或者让其保持在会话中

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

    如果指定了一个错误的证书,那么同样会引发一个SSLError


    钩子函数(不常用)

    import requests
    
    
    def foo(response, *args, **kwargs):
        print(response.url)
    
    
    # 绑定一个钩子函数,那么当请求执行完毕后,会触发foo函数,key要指定为response
    res = requests.get("http://www.baidu.com", hooks={"response": foo})
    
    # http://www.baidu.com/
    
    

    自定义身份验证

    Requests 允许你使用自己指定的身份验证机制。
    
    任何传递给请求方法的 auth 参数的可调用对象,在请求发出之前都有机会修改请求。
    
    自定义的身份验证机制是作为 requests.auth.AuthBase 的子类来实现的,也非常容易定义。Requests 在 requests.auth 中提供了两种常见的的身份验证方案: HTTPBasicAuth 和 HTTPDigestAuth 。
    
    假设我们有一个web服务,仅在 X-Pizza 头被设置为一个密码值的情况下才会有响应。虽然这不太可能,但就以它为例好了。
    
    
    from requests.auth import AuthBase
    import requests
    
    class PizzaAuth(AuthBase):
        def __init__(self, username):
            self.username = username
    
        def __call__(self, r):
            r.headers['X-Pizza'] = self.username
            return r
        
        
    requests.get('http://pizzabin.org/admin', auth=PizzaAuth('fuck_you'))
    
    

    流式请求

    import requests
    
    
    # 使用Response.iter_lines(),可以很方便地对流式 API进行迭代。简单地设置stream为True便可以使用iter_lines进行迭代
    res = requests.get("https://www.baidu.com", stream=True)
    res.encoding = res.apparent_encoding
    
    # 加上decode_unicode=True表示默认解码, 否则得出的是字节类型
    # 而且也要加上res.encoding = res.apparent_encoding,否则会是乱码
    # 还可以指定chunk_size,默认是512
    for line in res.iter_lines(decode_unicode=True):
        print(line[: 55])
    """
    <!DOCTYPE html>
    <!--STATUS OK--><html> <head><meta http-equiv=content-t
                    </script> <a href=//www.baidu.com/more/
    """
    
    # 除了iter_lines,还有一个iter_content,两者类似,但是这里必须要指定chunk_size,因为默认是1
    for line in res.iter_content(chunk_size=200, decode_unicode=True):
        print(line[: 100])
    """
    <!DOCTYPE html>
    <!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charse
    l=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baid
    v id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img h
     action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden n
    t type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"
    pan class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </for
    /a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.c
    /a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.b
    ame=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz
    = "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');
                    </scrip
    /a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度
     href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedbac
    </p> </div> </div> </div> </body> </html>
    """
    
    

    代理

    如果需要使用代理,可以通过为任意请求方法提供 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 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

    除了基本的http代理,requests还支持socks代理。如果要使用的话,需要安装第三方库,pip install requests[socks]。

    一般我们使用socks都是获取接口数据,在python中还有一个比较好用的模块,叫suds。

    from suds.client import Client
    client = Client(url="xxx")
    res = getattr(client.service, "func")(*params)
    
    

    但是在requests中,使用socks代理和http代理是一样的

    proxies = {
        'http': 'socks5://user:pass@host:port',
        'https': 'socks5://user:pass@host:port'
    }
    
    
  • 相关阅读:
    [原]JsDoc:JavaScript文档生成工具相关
    [原]代码片段编辑器
    [原]openlayers+ext
    [原]符合W3C标准的类innerText
    [原]关于鼠标滚轮的编程
    [原]IE8中开启IE7的兼容模式
    MAC下安装mysql
    intellij idea 代码正常,但是编译出现 java:需要";"
    eclipse 安装插件不生效
    [转]Linux下修改/设置环境变量JAVA_HOME
  • 原文地址:https://www.cnblogs.com/traditional/p/11111305.html
Copyright © 2020-2023  润新知