[第12天] 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 | 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
下载保存一首音乐
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
中提供了两种常见的的身份验证方案: HTTPBasicAuth
和 HTTPDigestAuth
。
若要显示此页面,你必须登录到 "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编码
只有字母和数字[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
属性。
块编码请求
背景
- 持续连接的问题:对于非持续连接,浏览器可以通过连接是否关闭来界定请求或响应实体的边界;而对于持续连接,这种方法显然不奏效。有时,尽管我已经发送完所有数据,但浏览器并不知道这一点,它无法得知这个打开的连接上是否还会有新数据进来,只能傻傻地等了。
- 用Content-length解决:计算实体长度,并通过头部告诉对方。浏览器可以通过 Content-Length 的长度信息,判断出响应实体已结束
- Content-length引入的新问题:由于 Content-Length 字段必须真实反映实体长度,但是对于动态生成的内容来说,在内容创建完之前,长度是不可知的。这时候要想准确获取长度,只能开一个足够大的 buffer,等内容全部生成好再计算。但这样做一方面需要更大的内存开销,另一方面也会让客户端等更久。
- 我们需要一个新的机制:不依赖头部的长度信息,也能知道实体的边界——分块编码(Transfer-Encoding: chunked)
分块编码(Transfer-Encoding: chunked)
- Transfer-Encoding,是一个 HTTP 头部字段(响应头域),字面意思是「传输编码」。最新的 HTTP 规范里,只定义了一种编码传输:分块编码(chunked)。
- 分块传输编码(Chunked transfer encoding)是超文本传输协议(HTTP)中的一种数据传输机制,允许HTTP由网页服务器发送给客户端的数据可以分成多个部分。分块传输编码只在HTTP协议1.1版本(HTTP/1.1)中提供。
- 数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。
-
具体方法
- 在头部加入 Transfer-Encoding: chunked 之后,就代表这个报文采用了分块编码。这时,报文中的实体需要改为用一系列分块来传输。
- 每个分块包含十六进制的长度值和数据,长度值独占一行,长度不包括它结尾的 CRLF(\r\n),也不包括分块数据结尾的 CRLF。
- 最后一个分块长度值必须为 0,对应的分块数据没有内容,表示实体结束。
-
例:
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
- 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 进行迭代。简单地设置 stream
为 True
便可以使用 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'])
标签:12,http,速通,get,Python,22%,print,requests,请求 来源: https://www.cnblogs.com/linxiaoxu/p/16024337.html