一. 介绍

# 介绍:使用requests可以模拟浏览器的请求,比起之前用到的urllib,requests模块的api更加便捷(本质就是封装了urllib3)

# 注意:requests库发送请求将网页内容下载下来以后,并不会执行js代码,这需要我们自己分析目标站点然后发起新的request请求

# 安装:pip3 install requests

# 各种请求方式:常用的就是requests.get()和requests.post()
    >>> import requests
    >>> r = requests.get('https://api.github.com/events')
    >>> r = requests.post('http://httpbin.org/post', data = {'key':'value'})
    >>> r = requests.put('http://httpbin.org/put', data = {'key':'value'})
    >>> r = requests.delete('http://httpbin.org/delete')
    >>> r = requests.head('http://httpbin.org/get')
    >>> r = requests.options('http://httpbin.org/get')

# 建议在正式学习requests前,先熟悉下HTTP协议
    http://www.cnblogs.com/linhaifeng/p/6266327.html

二. 基于GET请求

1. 发送get请求

知识储备: 什么是referer?

import requests

headers = {
    # 模拟浏览器
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36',
    # 解决妹子图防盗链问题: referer
    'referer': 'https://www.mzitu.com/taiwan/'
}

# response = requests.get('https://mzitu.com', headers=headers)
# print(response.text)      # 文本内容

response1 = requests.get("https://i.mmzztt.com/thumb/2020/07/221376_236.jpg", headers=headers)
# print(response1.content)  # 二进制内容

# 将爬取的图片写入本地
with open('221376_236.jpg', 'wb') as f:
    for line in response1.iter_content():
        f.write(line)

2. 请求地址中携带数据

1) 请求地址中携带数据方式一: 直接携带 (中文一般不会进行url编码, 会出现编码问题)

import requests

headers = {
    # 模拟浏览器
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36',
}
response = requests.get('https://www.baidu.com/s?wd=wozhengshuai', headers=headers)
print(response.text)

2) 请求地址中携带数据方式二: 使用params (中文会自动进行url编码)

import requests

headers = {
    # 模拟浏览器
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36',
}
response = requests.get('https://www.baidu.com/s', params={'wd': '我真帅'}, headers=headers)
print(response.text)

3) 使用urllib解决方式一编码问题

from urllib.parse import urlencode, unquote


# 编码
res = urlencode({'wd': '我真帅'}, encoding='utf-8')
print(res)  # wd=%E6%88%91%E7%9C%9F%E5%B8%85

# 解码
res = unquote('%E6%88%91%E7%9C%9F%E5%B8%85', encoding='utf-8')
print(res)  # 我真帅

3. 请求带cookie

1) 方式一: 存放在header

import requests

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36',
    'cookie': 'Hm_lvt_b72418f3b1d81bbcf8f99e6eb5d4e0c3=1596202661; UM_distinctid=173a517b3192a1-07d51af695c35e-3a65420e-1fa400-173a517b31a70b;'
}

# 注意: url不要写错成了 'http://127.0.0.1:8050/index'
response = requests.get('http://127.0.0.1:8050/index/', headers=headers)
print(response.text)
# 服务端获取: 
# 注意: 放在headers中, cookie对应的value如果有等于号, 那么等于号左边作为cookie的key, 右边作为cookie的value. 如果没有那么, key为空字符串. value为值.
"""
{
    'Hm_lvt_b72418f3b1d81bbcf8f99e6eb5d4e0c3': '1596202661', 
    'UM_distinctid': '173a517b3192a1-07d51af695c35e-3a65420e-1fa400-173a517b31a70b'
} 
"""

2) 方式二: 存放在指定的cookies参数中

import requests

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36',
}
cookies = {
    'cookie': 'Hm_lvt_b72418f3b1d81bbcf8f99e6eb5d4e0c3=1596202661; UM_distinctid=173a517b3192a1-07d51af695c35e-3a65420e-1fa400-173a517b31a70b;'
}

# 注意: url不要写错成了 'http://127.0.0.1:8050/index'
response = requests.get('http://127.0.0.1:8050/index/', headers=headers, cookies=cookies)
print(response.text)

# 服务端获取: 
# 注意: cookies直接指定. 字典中的key对应的服务端cookie获取的key. 字典中value冒号分隔的等于号左边作为cookie的key, 右边作为value
'''
{
    'cookie': 'Hm_lvt_b72418f3b1d81bbcf8f99e6eb5d4e0c3=1596202661', 
    'UM_distinctid': '173a517b3192a1-07d51af695c35e-3a65420e-1fa400-173a517b31a70b'
}

4. 总结

# headers参数: 
    1. 模拟浏览器: user-agent
    2. 解决防盗链: referer

# 解决url编码问题
    1. params参数默认解决url编码问题 
    2. urllib模块
    
# 携带cookie
    第一种方式: 存放在headers中
    客户端发送: {'cookie': 'key=value;key1=value1'}
    服务端获取: {key: value, key1: value1} 

    第二种方式: 指定cookie参数 (提示: 可以存放dict 和 CookieJar对象)
        客户端发送: {key: value, key1: value1}
        服务端获取: {key: value, key1: value1}   
    
response.text  文本
response.content  二进制
response.iter_content()  迭代器

三. 基于POST请求

1. 携带数据发送post请求

1) 携带数据: urlencoded

import requests

response = requests.post('http://127.0.0.1:8050/index/', data={'name': 'yang'})
print(response.text)

# 服务端获取
'''
request.body: b'name=yang'
request.POST: <QueryDict: {'name': ['yang']}>
'''

2) 携带数据: json

import requests

response = requests.post('http://127.0.0.1:8050/index/', json={'name': 'yang'})
print(response.text)

# 服务端获取
'''
request.body: b'{"name": "yang"}'
request.POST: <QueryDict: {}>
'''

2. 自动携带cookie

import requests

session = requests.session()      # 注意: 是session()方法, 不是sessions()
session.post('http://127.0.0.1:8050/login/', json={'username': 'yang', 'password': '123'})          # 假设这个请求登录了
response = session.get('http://127.0.0.1:8050/index/')  # 现在不需要手动带cookie, session会自动处理
print(response)

3. 自定义请求头

requests.post(url='xxxxxxxx',
              data={'xxx': 'yyy'})  # 没有指定请求头,# 默认的请求头:application/x-www-form-urlencoed

# 如果我们自定义请求头是application/json,并且用data传值, 则服务端取不到值
requests.post(url='',
              data={'': 1, },
              headers={
                  'content-type': 'application/json'
              })

requests.post(url='',
              json={'': 1, },
              )  # 默认的请求头:application/json

4. 总结

# 携带数据:
    携带json数据: json={}
    携带urlencoded数据: data={}
    
# 自动携带cookie:     
    session = requests.session()
    res = session.post(认证url)
    res1 = session.get(访问url)
    
# 自定义请求头:
    默认: application/x-www-form-urlencoed
    headers={'content-type': 'application/json'}  

四. 响应Response

1. response对象方法

import requests

response = requests.post('http://127.0.0.1:8050/index/?username=yang', data={'name': 'yang'})

# 响应的文本
res = response.text
print(res)  # ok

# 响应体的二进制
res = response.content
print(res)  # b'ok'

# 获取响应状态码
res = response.status_code
print(res)  # 200

# 获取响应头
res = response.headers
print(res)
'''
{
    'Date': 'Fri, 31 Jul 2020 14:32:06 GMT', 'Server': 'WSGIServer/0.2 CPython/3.6.3', 
    'Content-Type': 'application/json', 
    'X-Frame-Options': 'SAMEORIGIN', 
    'Content-Length': '16', 
    'Set-Cookie': 'cookie_key=cookie_value; Path=/'
}
'''

# 注意: 它是CookieJar对象, 获取后端设置的cookie
res = response.cookies
print(res)   # <RequestsCookieJar[<Cookie cookie_key=cookie_value for 127.0.0.1/>]>

# 把cookie转成字典
res = response.cookies.get_dict()
print(res)  # {'cookie_key': 'cookie_value'}

# 把cookie转成列表套元组
res = response.cookies.items()
print(res)  # [('cookie_key', 'cookie_value')]

# 获取请求的url地址
res = response.url
print(res)  # http://127.0.0.1:8050/index/?username=yang 如果重定向了那么就是重定向的地址 http://127.0.0.1:8050/home/


# 将重定向之前的地址存放到列表当中. 注意: 是response对象
res = response.history
print(res)             # [<Response [302]>, <Response [302]>]
print(type(res[0]))    # <class 'requests.models.Response'>
print(res[0].content)  # b''

# 响应的编码方式
res = response.encoding
print(res)  # ISO-8859-1

# iter_content主要是针对图片,视频,大文件. 可以迭代取值
res = response.iter_content()
print(res)   # <generator object iter_slices at 0x0000021A72B5F360>

2. 编码问题

import requests

response = requests.get('http://www.autohome.com/news')
# 方式一: 手动指定编码 (前提: 知道它的编码方式)
# response.encoding = 'gb2312'

# 方式二: 自动指定编码, 由方法内部获取.
response.encoding = response.apparent_encoding
print(response.text)

3. 解析json

import requests

response = requests.post('http://127.0.0.1:8050/index/', data={'name': 'yang'})
response_text = response.text
print(type(response_text), response_text)  # <class 'str'> {"name": "yang"}

# 解析方式一: 使用json模块解析 (繁琐)
import json
res = json.loads(response_text)
print(type(res), res)  # <class 'dict'> {'name': 'yang'}

# 解析方式二: 使用requests提供的方法解析(提示: 内部本质还是调用了json)
res = response.json()
print(type(res), res)  # <class 'dict'> {'name': 'yang'}

# 注意: 方式一是通过获取到文本内容再进行的json序列化, 而方式二是直接透过response对象调用json()方法.

4. 总结

# response对象方法: 
    响应文本                      response.text      
    响应二进制数据                 response.content
    响应状态码                    response.status_code
    响应头                        response.headers
    响应CookieJar对象             response.cookies
    响应cookie字典                response.cookies.get_dict()
    响应cookie列表套元组           response.cookies.items()
    响应重定向之前的response对象    response.history
    响应url地址                   response.url
    响应编码                      response.encoding
    响应数据的迭代器               response.iter_content()
    
# 解决响应内容编码: 
    手动: response.encoding = '你知道你获取url资源的编码'
    自动: response.encoding = response.apparent_encoding
    
# 解析json:
    1. json模块解析
        json.loads(response.text)
    2. requests提供的json()方法解析
        response.json()

五. 高级用法

1. SSL Cerf Verification

import requests

response = requests.get('https://www.12306.cn', verify=False)
print(response.status_code)  # 不验证证书,报警告,返回200


# 使用证书,需要手动携带
import requests

response = requests.get('https://www.12306.cn',
                        cert=('/path/server.crt',
                              '/path/key'))
print(response.status_code)

2. 使用代理

知识储备: 什么是http代理,什么是socks5代理?两者有什么不同?

1) HTTP代理

import requests
response = requests.get('https://www.baidu.com/', proxies={'http': '165.225.8.94:10605', })
print(response.status_code)

2) socks代理

# 安装: pip install requests[socks]
import requests

proxies = {
    # 'https': 'socks5://47.244.192.12:17518',
    'http': 'socks5://47.244.192.12:17518',
}

response = requests.get('https://www.baidu.com',
                        proxies=proxies)

print(response.status_code)

3. 超时设置

import requests

# 两种超时:float or tuple
# timeout = 0.001  # 代表接收数据的超时时间
timeout = (0.0001, 0.002)  # 0.1代表链接超时  0.2代表接收数据的超时时间
response = requests.get('https://www.baidu.com',
                        timeout=timeout)
print(response.text)
print(response.status_code)
# 注意: 超时以后抛出异常. 
'''
# 0.0001表示链接超时
requests.exceptions.ReadTimeout: HTTPSConnectionPool(host='www.baidu.com', port=443): Read timed out. (read timeout=0.0001)
'''

4. 认证设置

官网链接:http://docs.python-requests.org/en/master/user/authentication/

'''
认证设置:登陆网站是,弹出一个框,要求你输入用户名密码(与alter很类似),此时是无法获取html的
但本质原理是拼接成请求头发送
        r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
一般的网站都不用默认的加密方式,都是自己写
那么我们就需要按照网站的加密方式,自己写一个类似于_basic_auth_str的方法
得到加密字符串后添加到请求头
        r.headers['Authorization'] =func('.....')
'''

# 看一看默认的加密方式吧,通常网站都不会用默认的加密设置
import requests
from requests.auth import HTTPBasicAuth

r = requests.get('xxx', auth=HTTPBasicAuth('user', 'password'))
print(r.status_code)

# HTTPBasicAuth可以简写为如下格式
import requests

r = requests.get('xxx', auth=('user', 'password'))
print(r.status_code)

5. 异常处理

import requests
from requests.exceptions import *

try:
    response = requests.get('https://www.baidu.com', timeout=(0.001, 0.002))
    print(response.status_code)
except ReadTimeout:
    print('读取超时!')
except ConnectionError:
    print('连接失败!')
except Timeout:
    print('超时')
except RequestException:
    print("请求异常")
except Exception as e:
    print(e)

6. 上传文件

import requests
import  os

dir_path = os.path.join(os.path.dirname(__file__), '2')
dir_list = os.listdir(dir_path)
file1, file2 = dir_list[0], dir_list[1]
# print(file1, file2)  # 220089_236.jpg 220304_236.jpg
file1_path = os.path.join(dir_path, file1)
file2_path = os.path.join(dir_path, file2)

files = {
    'file1': open(file1_path, 'rb'),
    'file2': open(file2_path, 'rb'),
}
response = requests.post('http://127.0.0.1:8050/index/', files=files)
print(response.status_code)  # 200
  
# 后端数据获取: 
'''
<MultiValueDict: 
    {
    'file1': [<InMemoryUploadedFile: 220089_236.jpg ()>], 
    'file2': [<InMemoryUploadedFile: 220304_236.jpg ()>]
    }
>
'''

7. 总结

# SSL认证
    verify=False不校验
    verify=True校验.  cert=(证书格式)
    
# 代理
    # HTTP代理
    proxies={'http': 'IP:PORT'}
    
    # socks代理: 安装requests[socks]
    proxies={'http': 'socks://IP:PORT'}
    
# 超时设置
    timeout=(连接超时时间, 接受数据超时时间)
    抛出异常: ReadTimeOut

# 认证设置
    from requests.auth import HTTPBasicAuth
    auth=HTTPBasicAuth('user', 'password')
    
# 异常处理
    from requests.exceptions import *
    ReadTimeOut       连接 或者 获取数据超时
    TimeOut           超时
    ConnectionError   连接错误
    RequestException  请求异常 
    
# 上传文件
    files={key: file, key1: file1}