Python Requests 速通爆肝、这么牛逼的库你还不会用吗?

资料

Requests文档:Requests: 让 HTTP 服务人类 — Requests 2.18.1 文档

反馈请求信息测试平台:httpbin.org

本文章所有代码Response对象变量统一命名为 response 或 res


浏览器上网原理

浏览器接受链接后,会寻找链接的地址,这个地址的主人也是一台电脑(不过是超级电脑,也叫服务器,支持大量文件的存储和计算),我们访问的网页就是这台电脑(服务器)上的某个文件,如果文件被删除了,我们就找不到啦。

浏览器找到服务器后,会发送一个请求过去,告诉服务器我们需要访问上面文件。

服务器收到请求后,会把文件发送给浏览器,这一步叫响应

先请求后响应,用爬虫程序实现发送请求接受服务器的响应,就是我们的任务啦。


爬虫原理

 1
 2         什么是爬虫?
 3             爬虫指的是爬取数据。
 4 
 5         什么是互联网?
 6             由一堆网络设备把一台一台的计算机互联到一起。
 7 
 8         互联网建立的目的?
 9             数据的传递与数据的共享。
10 
11         上网的全过程:
12             - 普通用户
13                 打开浏览器 --> 往目标站点发送请求 --> 接收响应数据 --> 渲染到页面上。
14 
15             - 爬虫程序
16                 模拟浏览器 --> 往目标站点发送请求 --> 接收响应数据 --> 提取有用的数据 --> 保存到本地/数据库。
17 
18         浏览器发送的是什么请求?
19             http协议的请求:
20                 - 请求url
21                 - 请求方式:
22                     GET、POST
23 
24                 - 请求头:
25                     cookies
26                     user-agent
27                     host
28 
29         爬虫的全过程:
30             1、发送请求 (请求库)
31                 - requests模块
32                 - selenium模块
33 
34             2、获取响应数据(服务器返回)
35 
36             3、解析并提取数据(解析库)
37                 - re正则
38                 - bs4(BeautifulSoup4)
39                 - Xpath
40 
41             4、保存数据(存储库)
42                 - MongoDB
43 
44             1、3、4需要手动写。
45 
46             - 爬虫框架
47                 Scrapy(基于面向对象)
48 
49        使用Chrome浏览器工具
50             打开开发者模式 ----> network ---> preserve log、disable cache

Get、Post 介绍

一般而言,我们所用的 HTTP 协议或 HTTPS 协议,使用的请求方式只有 GET 方式和 POST 方式。

  • GET 方式: 访问某个网页前不需要在浏览器里输入链接之外的东西,因为我们只是想向服务器获取一些资源,可能就是一个网页。
    • HTTP默认的请求方法就是GET
    • 没有请求体
    • 数据必须在1K之内
    • GET请求数据会暴露在浏览器的地址栏中
  • POST 方式:访问某个网页前需要在浏览器里输入链接之外的东西,因为这些信息是服务器需要的。 比如在线翻译,我们需要输入点英文句子,服务器才能翻译吧。
    • 数据不会出现在地址栏中
    • 数据的大小没有上限
    • 有请求体
    • 请求体中如果存在中文,会使用URL编码!

判断Get、Post请求

  • 在浏览器的地址栏中直接给出URL,那么就一定是GET请求
  • 点击页面上的超链接也一定是GET请求
  • 提交表单时,表单默认使用GET请求,但可以设置为POST

requests.post()用法与requests.get()完全一致,特殊的是requests.post()有一个data参数,用来存放请求体数据!


Requests 官方介绍(笑死人)

Requests: 让 HTTP 服务人类 — Requests 2.18.1 文档


安装 Requests

终端输入 pip3 install requests


Requests 常用方法

  • get() 方法能向服务器发送了一个请求,请求类型为 HTTP 协议的 GET 方式
  • post() 方法也能向服务器发送一个请求,请求类型是 HTTP 协议的 POST 方式

Http 协议

请求url:
    https://www.baidu.com

请求方式method:
    GET、Post、Put、Delete、head、options...

请求头headers:
    Cookie: 可能需要关注
    User-Agent: 用来证明你是浏览器
    Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36
    Host: www.baidu.com  

    注意: 去浏览器的request headers中查找


开发者工具网络界面


轻松上手

import requests
# ^ 引入 requests,实现请求
URL = 'http://www.weather.com.cn/data/sk/101010100.html'
# ^ 输入在浏览器的网址
res = requests.get(URL)
# ^ 发送 GET 方式的请求,并把返回的结果(响应)存储在 res 变量里头
# ^ get() 方法需要输入一个网页链接
print(type(res))
# ^ 通过 type 查看返回的数据是什么对象.
res.encoding = 'UTF-8'
print(res.content)
print(res.text)
# ^ 修改encoding编码,输出正确的内容

Response 对象

属性 功能 例子
Response.status_code 检查请求是否成功 200 代表正常,404 代表网页不存在。
Response.encoding 定义编码 如果编码不对,网页就会乱码的。
Response.content 把数据转成二进制 用于获取图片、音频类的数据。
Response.text 把数据转为字符串 用于获取文本、网页原代码类的数据。

Requests 会自动解码来自服务器的内容。大多数 unicode 字符集都能被无缝地解码。Response 就是响应数据 res 的对象类型。

print(response.status_code)  # 获取响应状态码
print(response.url)  # 获取url地址
print(response.text)  # 获取文本
print(response.content)  # 获取二进制流
print(response.headers)  # 获取页面请求头信息
print(response.history)  # 上一次跳转的地址
print(response.cookies)  # # 获取cookies信息
print(response.cookies.get_dict())  # 获取cookies信息转换成字典
print(response.cookies.items())  # 获取cookies信息转换成字典
print(response.encoding)  # 字符编码
print(response.elapsed)  # 访问时间

下载保存一张图片

import requests
import os

# ^ 获取图片内容
cat = requests.get('https://xiaonenglife.oss-cn-hangzhou.aliyuncs.com/static/cnblogs/2020_3/lazy.jpeg')

# ^ 打印响应cat的类型,响应内容cat.content的类型
print(type(cat),type(cat.content))

# ^ 获取当前代码目录
path = os.path.dirname(__file__)

# ^ 尝试打开文件,如果不存在就创建,以二进制的形式,赋给f变量后写入 cat.content 二进制数据
with open(path + '/lazy.jpg', 'wb') as f:
    f.write(cat.content)

关于 open 函数第二个参数:

r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
w 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。

更多参考:Python3 File 方法 | 菜鸟教程 


下载保存一首音乐

print(res.status_code) # 查看网页状态,200 表示正常

import requests
import os

# ^ 获取音乐内容
music = requests.get('https://xiaonenglife.oss-cn-hangzhou.aliyuncs.com/static/cnblogs/2020_3/%E6%AC%A2%E4%B9%90%E6%B0%B4%E7%89%9B~%E8%A0%A2%E8%9B%86%E8%A0%95%E5%8A%A8.mp3')

# ^ 获取当前代码目录
path = os.path.dirname(__file__)

# ^ 判断连接是否成功
if music.status_code == 200:
    # ^ 尝试打开文件,如果不存在就创建,以二进制的形式,赋给f变量后写入 music.content 二进制数据
    with open(path + '/music.mp3', 'wb') as f:
        f.write(music.content)
else:
    print("下载失败")


Get 请求

http://httpbin.org 是一个可以反馈我们请求信息的测试平台

import requests

# ^ 访问 http://httpbin.org 反馈我们的请求

res = requests.get('http://httpbin.org/get')

# ^ 打印响应内容
print(res.text)

# ^ 带参数get
res = requests.get('http://httpbin.org/get?id=A123&price=50')
print(res.text)

# ^ 更方便带参数get
data = {
    'id': 'A123',
    'price': '50'
}

res = requests.get('http://httpbin.org/get', params=data)
print(res.text)  # http://httpbin.org/get?id=A123&price=50

# @ 将列表作为值传入
data = {
    'id': ['A123', 'B456'],
    'price': '50'
}
res = requests.get('http://httpbin.org/get', params=data)
print(res.text)  # http://httpbin.org/get?id=A123&id=B456&price=50

# @ 字典里值为 None 的键都不会被添加到 URL 的查询字符串里
data = {
    'id': ['A123', 'B456'],
    'price': None
}
res = requests.get('http://httpbin.org/get', params=data)
print(res.text)  # http://httpbin.org/get?id=A123&id=B456

# ^ 解析成 json
print(res.json())

第一种

第二种

第三种

字典里值为 None 的键都不会被添加到 URL 的查询字符串里
 

添加Headers发送请求

headers在爬虫中是非常必要的,很多时候如果请求不加headers,那么你可能会被禁掉或出现服务器错误…

import requests

response = requests.get("https://www.baidu.com/s?wd=helloworld")
print(response.text)

# headers = {
#     'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39'
# }
# response = requests.get("https://www.baidu.com/s?wd=helloworld", headers=headers)
# print(response.text)

不加 Headers 站点不返回具体搜索数据


Post 请求

发送一些编码为表单形式的数据——非常像一个 HTML 表单。要实现这个,只需简单地传递一个字典给 data 参数。数据字典在发出请求时会自动编码为表单形式:通过在发送post请求时添加一个data参数,这个data参数可以通过字典构造成,这样对于发送post请求就非常方便
 

发送一个带headers、带参数的post请求,注意data是字典 dict 类型

import requests

data = {'id': 'A123', 'price': '50'}

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39'
}

response = requests.post("http://httpbin.org/post", data=data, headers=headers)

print(response.text)

 

还可以为 data 参数传入一个元组列表。在表单中多个元素使用同一 key 的时候,这种方式尤其有效,字典会第二个值覆盖第一个值

payload = [("key1","value1"),("key1","value2")]
response = requests.post("http://httpbin.org/post",data = payload)
print(response.text)

 


判断HTTP状态码

当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含HTTP状态码的信息头(server header)用以响应浏览器的请求。

HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为5种类型

分类 分类描述
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误

常用状态码

200 请求成功
301 资源(网页等)被永久转移到其它URL
404 请求的资源(网页等)不存在
500 内部服务器错误

参考资料:HTTP状态码大全 - 常用参考表对照表

判断状态码

import requests

res = requests.get('https://baidu.com')

exit() if not res.status_code == requests.codes.OK else print('请求成功')
exit() if not res.status_code == 200 else print('请求成功')

 


Post 文件上传 

上传单个文件

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

可以发送文件,可以显式地设置文件名,文件类型和请求头,也可以发送作为文件来接收的字符串

import requests
import os

path = os.path.dirname(__file__)

files = {'files': open(path+'/lazy.jpg', 'rb')}

res = requests.post("http://httpbin.org/post", files=files)
print(res.text)

# ^ 显式设置文件名,文件类型和请求头

files = {'file': ('cat.jpg', open(path+'/lazy.jpg', 'rb'), 'image/jpeg', {'Expires': '0'})}

res = requests.post("http://httpbin.org/post", files=files)
print(res.text)

# ^ 也可以发送作为文件来接收的字符串:

files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')}

res = requests.post("http://httpbin.org/post", files=files)
print(res.text)

可以看到显示了文件的字节流

上传多个文件

假设你要上传多个图像文件到一个 HTML 表单,使用一个多文件 field 叫做 "images"

<input type="file" name="images" multiple="true" required="true"/>
要实现,只要把文件设到一个元组的列表中

multiple_files = [
 ('images', ('foo.png', open('foo.png', 'rb'), 'image/png')),
 ('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))]

强烈建议你用二进制模式(binary mode)打开文件。这是因为 requests 可能会为你提供 header 中的 Content-Length,在这种情况下该值会被设为文件的字节数。如果你用文本模式打开文件,就可能碰到错误。


Cookies

获取Cookies

通过 response.cookies获取cookies,是一个列表 list

items()方法将其转化为元组组成的列表,遍历输出每一个Cookies的名称和值,实现Cookies的遍历解析

import requests

response = requests.get("https://www.bilibili.com/")
print(response.cookies)
for key, value in response.cookies.items():
    print(key+'='+value)

RequestsCookieJar

Cookie 的返回对象为 RequestsCookieJar,它的行为和字典类似,但接口更为完整,适合跨域名跨路径使用。还可以把 Cookie Jar 传到 Requests 中

# ^ CookieJar
jar = requests.cookies.RequestsCookieJar()
jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies')
jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere')
response = requests.get('http://httpbin.org/cookies', cookies=jar)
print(response.text)

会话维持

在requests中,如果直接利用get()或post()等方法的确可以做到模拟网页的请求,但是这实际上是相当于不同的会话,也就是说相当于你用了两个浏览器打开了不同的页面

设想这样一个场景,第一个请求用post()方法登录了某个网站,第二次想获取成功登陆后的自己的个人信息,你又用了一次get()方法去请求这个人信息页面。实际上,这相当于打开了两个浏览器,是两个完全不相关的会话,能成功获取个人信息吗?那当然不能。

Session()

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

假设我们需要读取cookie,先请求当前页面设置一个,再获取

import requests

# ^ 调用 接口 创建设置一个 cookie
requests.get('http://httpbin.org/cookies/set/id/A123')
# ^ 获取 cookies
response = requests.get('http://httpbin.org/cookies')
print(response.text)

上面那段代码中发起了两次get请求,相当于两个浏览器,相互独立,所以第二次get并不能得到第一次的cookie

现在我们声明Session对象来发起两次get请求,Session用于会话维持

import requests

# ^ 调用 接口 创建设置一个 cookie
requests.get('http://httpbin.org/cookies/set/id/A123')
# ^ 获取 cookies
response = requests.get('http://httpbin.org/cookies')
print(response.text)

# ^ 声明Session对象发起两次get请求
s = requests.session()
s.get('http://httpbin.org/cookies/set/id/A123')
response = s.get('http://httpbin.org/cookies')
print(response.text)

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

# ^ 会话也可用来为请求方法提供缺省数据。这是通过为会话对象的属性提供数据来实现的
s.auth = ('user', 'pass')
s.headers.update({'x-test': 'true'})

# both 'x-test' and 'x-test2' are sent
res = s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})
print(res.text)

需要注意,就算使用了会话,像方法级别 (get、post...) 的参数 (如cookies=) 不会被跨请求保持。下面的例子只会和第一个请求发送 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": {}}'

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

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

Cookies维持登录状态

打开开发者工具,查找headers里的Cookie

获取到的Cookies

JSESSIONID=0FF182F56EECA3672EBC134378DAE75F; grade=2017; 
period=%7B%22periodid%22%3A%222021-2022-2%22%2C%22periodstartdate%22%3A%222022-02-01%22%2C%22periodenddate%22%3A%222022-08-31%22%2C%22sys_periodid%22%3A%222021-2022-2%22%2C%22periodtitle%22%3A%222021-2022%u5B66%u5E74%u7B2C2%u5B66%u671F%22%2C%22sysrowno%22%3A%221%22%2C%22rowid%22%3A%227%22%7D; 
user=%7B%22userid%22%3A%222013333501223%22%2C%22autologin%22%3A0%7D; JSESSIONID=6DFF81A2FCCB38E17320596A7008CF1D

代码

import requests

# ^ 方法一:在请求头中拼接cookies
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39',
    'Cookie': "JSESSIONID=0FF182F56EECA3672EBC134378DAE75F; grade=2017; period=%7B%22periodid%22%3A%222021-2022-2%22%2C%22periodstartdate%22%3A%222022-02-01%22%2C%22periodenddate%22%3A%222022-08-31%22%2C%22sys_periodid%22%3A%222021-2022-2%22%2C%22periodtitle%22%3A%222021-2022%u5B66%u5E74%u7B2C2%u5B66%u671F%22%2C%22sysrowno%22%3A%221%22%2C%22rowid%22%3A%227%22%7D; user=%7B%22userid%22%3A%222013333501223%22%2C%22autologin%22%3A0%7D; JSESSIONID=6DFF81A2FCCB38E17320596A7008CF1D"
}
response = requests.get("http://www.imlab.top/imlab/index.jsp", headers=headers)
print(response.text)

# ^ 方法二:将cookies做为get的一个参数

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39',
}
cookies = {
  'Cookie': "JSESSIONID=0FF182F56EECA3672EBC134378DAE75F; grade=2017; period=%7B%22periodid%22%3A%222021-2022-2%22%2C%22periodstartdate%22%3A%222022-02-01%22%2C%22periodenddate%22%3A%222022-08-31%22%2C%22sys_periodid%22%3A%222021-2022-2%22%2C%22periodtitle%22%3A%222021-2022%u5B66%u5E74%u7B2C2%u5B66%u671F%22%2C%22sysrowno%22%3A%221%22%2C%22rowid%22%3A%227%22%7D; user=%7B%22userid%22%3A%222013333501223%22%2C%22autologin%22%3A0%7D; JSESSIONID=6DFF81A2FCCB38E17320596A7008CF1D"
}
response = requests.get("http://www.imlab.top/imlab/index.jsp", headers=headers,cookies=cookies)
print(response.text)

成功用Cookies来维持登录状态

InvalidHeader

发生异常: InvalidHeader
Invalid return character or leading space in header: Cookie

出现此报错的原因是header请求头不能有空格


重定向与请求历史

  • 默认情况下,除了 HEAD, Requests 会自动处理所有重定向。
  • 可以使用响应对象的 history 方法来追踪重定向。
  • Response.history 是一个 Response 对象的列表,为了完成请求而创建了这些对象。这个对象列表按照从最老到最近的请求进行排序。例如,Github 将所有的 HTTP 请求重定向到 HTTPS:
import requests

# ^ 访问 http 百度自动重定向到 https
res = requests.get('http://bilibili.com/')

print(res.url)
print(res.history)

# https://www.bilibili.com/?rt=V%2FymTlOu4ow%2Fy4xxNWPUZ9cMXB0I9gJlG5%2FQ5%2FMXJts%3D
# [<Response [301]>, <Response [302]>]

 如果使用的是GET、OPTIONS、POST、PUT、PATCH 或者 DELETE,那么可以通过 allow_redirects 参数禁用重定向处理,HEAD需要设置allow_redirects 开启重定向

res = requests.get('http://bilibili.com/',allow_redirects=False)

 


SSL 证书验证

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

response = requests.get('https://requestb.in')
print(response)         # 抛出异常  SSLError:
response = requests.get('https://github.com', verify=True)
print(response)

为了避免这种情况的发生可以通过verify=False但是这样是可以访问到页面,但是会提示:InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings InsecureRequestWarning)

解决方法为:

import requests
from requests.packages import urllib3
urllib3.disable_warnings()    # 就这一句就可以解决
response = requests.get("https://www.12306.cn",verify=False)
print(response.status_code)

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

requests.get('https://github.com', verify='路径')

或者将其保存在会话中:

s = requests.Session()
s.verify = '路径'

注意:如果 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'

警告本地证书的私有 key 必须是解密状态。目前,Requests 不支持使用加密的 key。


设置代理

代理设置:先发送请求给代理,然后由代理帮忙发送(封ip是常见的事情)

命令行输入 pip install pysocks

import requests

# ^ SOCKS5 代理
proxies = {
    "http": "socks5://127.0.0.1:10808",
    "https": "socks5://127.0.0.1:10808"
}

res = requests.get("https://www.google.com/", proxies=proxies)

print(res.text)

# ^ HTTPS 代理
# proxies = {
#     "http": "http://127.0.0.1:10809",
#     "https": "https://127.0.0.1:10809"
# }

# ^ 带密码
# proxies={
# 	"http":"http://uesr:password@127.0.0.1:9743/",
# }

 

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

# Linux terminal:
# export HTTP_PROXY="http://10.10.1.10:3128"
# export HTTPS_PROXY="http://10.10.1.10:1080"

import requests
requests.get("http://example.org")

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

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

 


超时设置

为防止服务器不能及时响应,大部分发至外部服务器的请求都应该带着 timeout 参数。在默认情况下,除非显式指定了 timeout 值,requests 是不会自动进行超时处理的。如果没有 timeout,你的代码可能会挂起若干分钟甚至更长时间。

连接超时指的是在你的客户端实现到远端机器端口的连接时(对应的是connect()),Request 会等待的秒数。一个很好的实践方法是把连接超时设为比 3 的倍数略大的一个数值,因为 TCP 数据包重传窗口 (TCP packet retransmission window) 的默认大小是 3。

一旦你的客户端连接到了服务器并且发送了 HTTP 请求,读取超时指的就是客户端等待服务器发送请求的时间。(特定地,它指的是客户端要等待服务器发送字节之间的时间。在 99.9% 的情况下这指的是服务器发送第一个字节之前的时间)。

可以告诉 requests 在经过以 timeout 参数设定的秒数时间之后停止等待响应。

timeout 仅对连接过程有效,与响应体的下载无关。 timeout 并不是整个下载响应的时间限制,而是如果服务器在 timeout 秒内没有应答,将会引发一个异常(更精确地说,是在 timeout 秒内没有从基础套接字上接收到任何字节的数据时)If no timeout is specified explicitly, requests do not time out.

import requests

response = requests.get("https://www.baidu.com", timeout=0.1)
# ^ 设置一个时间限制,必须在0.1秒内得到应答
print(response.status_code)

捕捉超时异常

import requests

# ^ 捕捉超时异常
try:
    response = requests.get("https://httpbin.org/get", timeout=0.1)
    print(response.status_code)
except requests.exceptions.Timeout:
    print('Timeout')

 


自定义身份验证

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

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

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

 

若要显示此页面,你必须登录到 "xxx.xxx.xxx.xxx"

在访问时需要输入用户名和密码,输入之后才能看到网站的内容。我们可以通过auth参数,把用户名和密码传入

import requests
from requests.auth import HTTPBasicAuth

r = requests.get("网页地址", auth=HTTPBasicAuth('123456', '123456'))

print(r.status_code)

 

假设我们有一个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'))

 


异常处理

http://www.python-requests.org/en/master/api/#exceptions

为保证爬虫的正常运转,需要对异常进行妥善处理

  • 遇到网络问题(如:DNS 查询失败、拒绝连接等)时,Requests 会抛出一个 ConnectionError 异常。
  • 如果 HTTP 请求返回了不成功的状态码, Response.raise_for_status() 会抛出一个 HTTPError 异常。
  • 若请求超时,则抛出一个 Timeout 异常。
  • 若请求超过了设定的最大重定向次数,则会抛出一个 TooManyRedirects 异常。
  • 所有Requests显式抛出的异常都继承自 requests.exceptions.RequestException 。
import requests
from requests.exceptions import ReadTimeout, HTTPError, RequestException
try:
    response = requests.get('http://httpbin.org/get', timeout=0.5)
    print(response.status_code)
except ReadTimeout:  # @ 捕获超时异常
    print('Timeout')
except HTTPError:  # @ 捕获HTTP异常
    print('Http error')
except ConnectionError:  # @ 捕获连接异常
    print('Connection error')
except RequestException:  # @ 捕获父类异常
    print('Error')
 

转换成JSON

调用res.json()方法,就可以将返回结果是JSON格式的字符串转化为字典。

如果返回的结果不是JSON 格式,便会出现解析错误,抛出json.decoder.JSONDecoderError异常


Url编码

关于URL编码 - 阮一峰的网络日志

只有字母和数字[0-9a-zA-Z]、一些特殊符号"$-_.+!*'(),"[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL

import requests
from urllib.parse import urlencode

wd = "春节"

wd = urlencode({"wd": wd}, encoding="utf-8")

print(wd)

 


响应体内容工作流

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

tarball_url = 'https://github.com/kennethreitz/requests/tarball/master'
r = requests.get(tarball_url, stream=True)
可以进一步使用 Response.iter_content 和 Response.iter_lines 方法来控制工作流,或者以 Response.raw 从底层 urllib3 的 urllib3.HTTPResponse <urllib3.response.HTTPResponse 读取未解码的相应体。
 
如果在请求中把 stream 设为 True,Requests 无法将连接释放回连接池,除非 消耗了所有的数据,或者调用了 Response.close。 这样会带来连接效率低下的问题。如果发现在使用 stream=True 的同时还在部分读取请求的 body(或者完全没有读取 body),那么就应该考虑使用 with 语句发送请求,这样可以保证请求一定会被关闭:
with requests.get('http://httpbin.org/get', stream=True) as r:
    # 在此处理响应。

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


块编码请求

背景

  1. 持续连接的问题:对于非持续连接,浏览器可以通过连接是否关闭来界定请求或响应实体的边界;而对于持续连接,这种方法显然不奏效。有时,尽管我已经发送完所有数据,但浏览器并不知道这一点,它无法得知这个打开的连接上是否还会有新数据进来,只能傻傻地等了。
  2. 用Content-length解决:计算实体长度,并通过头部告诉对方。浏览器可以通过 Content-Length 的长度信息,判断出响应实体已结束
  3. Content-length引入的新问题:由于 Content-Length 字段必须真实反映实体长度,但是对于动态生成的内容来说,在内容创建完之前,长度是不可知的。这时候要想准确获取长度,只能开一个足够大的 buffer,等内容全部生成好再计算。但这样做一方面需要更大的内存开销,另一方面也会让客户端等更久。
  4. 我们需要一个新的机制:不依赖头部的长度信息,也能知道实体的边界——分块编码(Transfer-Encoding: chunked)

分块编码(Transfer-Encoding: chunked)

  1. Transfer-Encoding,是一个 HTTP 头部字段(响应头域),字面意思是「传输编码」。最新的 HTTP 规范里,只定义了一种编码传输:分块编码(chunked)。
  2. 分块传输编码(Chunked transfer encoding)是超文本传输协议(HTTP)中的一种数据传输机制,允许HTTP由网页服务器发送给客户端的数据可以分成多个部分。分块传输编码只在HTTP协议1.1版本(HTTP/1.1)中提供。
  3. 数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。
  4. 具体方法
    1. 在头部加入 Transfer-Encoding: chunked 之后,就代表这个报文采用了分块编码。这时,报文中的实体需要改为用一系列分块来传输。
    2. 每个分块包含十六进制的长度值和数据,长度值独占一行,长度不包括它结尾的 CRLF(\r\n),也不包括分块数据结尾的 CRLF。
    3. 最后一个分块长度值必须为 0,对应的分块数据没有内容,表示实体结束。
  5. 例:
    HTTP/1.1 200 OK
    Content-Type: text/plain
    Transfer-Encoding: chunked
    
       
    25\r\n
    This is the data in the first chunk\r\n
    
    
    1C\r\n
    and this is the second one\r\n
       
    
    3\r\n
    con\r\n
    
       
    8\r\n
    sequence\r\n
       
    
    0\r\n
    \r\n
  6. Content-Encoding 和 Transfer-Encoding 二者经常会结合来用,其实就是针对 Transfer-Encoding 的分块再进行 Content-Encoding压缩。

对于出去和进来的请求,Requests 也支持分块传输编码。要发送一个块编码的请求,仅需为你的请求体提供一个生成器

def gen():
    yield 'hi'
    yield 'there'

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

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

回调钩子

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

callback_function会接受一个数据块作为它的第一个参数。
def print_url(res, *args, **kwargs):
  print(res.url)

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

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


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

requests.get('http://httpbin.org', hooks=dict(response=print_url))
# http://httpbin.org

 


流式请求

使用 Response.iter_lines() 你可以很方便地对流式 API 进行迭代。简单地设置 streamTrue 便可以使用 iter_lines 对相应进行迭代:
import json
import requests

res = requests.get('http://httpbin.org/stream/20', stream=True)

for line in res.iter_lines():

    # filter out keep-alive new lines
    if line:
        decoded_line = line.decode('utf-8')
        print(json.loads(decoded_line))
当使用 <cite>decode_unicode=True</cite> 在 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)

 


其他操作

# ^ 查看网页允许什么HTTP请求,有些网站并未实现options方法
res = requests.options('http://bilibili.com')
print(res.headers['allow'])

 

posted @ 2022-03-19 04:25  小能日记  阅读(363)  评论(0编辑  收藏  举报