User:Xyy23330121/Python/HTTP 请求
本章节,我们将主要讲解如何使用 Python 自动访问互联网内容。本章节默认读者对 HTML 有基本的了解,知道如何使用 正则表达式 等方式,从 HTML 代码中提取信息。
Python 内置的模块 urllib 提供了对互联网内容进行请求的方法。而第三方模块 requests 提供了比 urllib 更好用的方法。本章讲解的内容将基于 requests,而非 urllib。
对于使用从 python.org 下载的 Windows 版本的 Python,我们可以从命令行程序中,输入以下代码以安装 request。
python -m pip install requests
如果链接不畅,或者下载失败。可以用以下代码,改为从清华大学提供的托管服务器中下载 requests。
python -m pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple
访问互联网的基本方式是进行 HTTP 请求。如果读者仅是访问网页,无需登录网站账号等,只要看本章节内容即可。
import requests
r = requests.get("https://zh.wikiversity.org")
requests.get() 等请求函数会进行一次请求,并返回请求得到的结果。
对于结果对象 r,r.content会包含所得到的响应内容。该响应内容是二进制的 bytes 对象。在实际使用时,并不会直接使用二进制的内容,而是通过 HTTP 头中的信息,将二进制内容按指定的编码解码后、再进行处理。
requests 会按照 HTTP 头中的信息自动设置编码方式。读者也可以自行设置编码方式。
r = requests.get("https://www.baidu.com/s?wd=URL")
print(r.encoding) #输出自动的编码方式
r.encoding = "ascii" #将编码方式改为 ascii
类似 r.content,r.text会包含所得到的结果的文本形式。它等同于
r.content.decode(encoding = r.encoding, errors = "ignore")
如果请求的是一个 HTML 页面,则返回的会是该 HTML 页面的源代码,比如:
r = requests.get("https://www.baidu.com/s?wd=URL")
print(r.text)
输出为:
<html>
<head>
<script>
location.replace(location.href.replace("https://","http://"));
</script>
</head>
<body>
<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>
如果请求的并非 HTML 页面,此方法也会原样返回得到的内容。比如以下方式请求得到的是一个 json 文本序列。
s = requests.get("https://api.bilibili.com/x/web-show/page/header")
print(s.text)
输出为:
{"code":-400,"message":"Key: 'ResourceID' Error:Field validation for 'ResourceID' failed on the 'min' tag","ttl":1}
使用 r.json() 即可将获得的 json 结果转换为对应的 Python 对象。该方法等同于:
import json
json.loads(r.text)
在极少数情况,读者可能需要使用原始的响应结果。r.raw 会输出原始的响应结果对象。如果要使用原始响应结果,在请求时,需要添加 stream 参数,比如:
r = requests.get("https://www.baidu.com/s?wd=URL", stream=True)
原始的响应结果对象是第三方库 urllib3 中的一个 urllib3.response.HTTPResponse 对象。这里不多赘述。
除了基本方式之外,HTTP 能传输更多信息。如果要实现更高级的功能,就要了解 HTTP 能传输的信息内容。本章将讲解 HTTP 请求中包含的内容,以及如何用 requests 模块中的方法来传输这些信息。
在 HTTP 标准中, URL 是可以包含参数的。比如:
r = requests.get("https://www.baidu.com/s?wd=URL")
除上述由读者在 URL 中添加参数之外,requests 还可以将参数自动加入到 URL 里面并进行请求:
payload = {"wd" : "URL"}
r = requests.get("https://www.baidu.com/s", params = payload)
print(r.url) #输出:https://www.baidu.com/s?wd=URL
HTTP 请求中,HTTP 标头(HTTP header)会包含比如“发送请求的客户端名称”、“传输的编码方式”等等。具体参见 HTTP 标头。
以“客户端名称”为例,想要自定义发送的客户端名称,可以用如下方式:
head = {'user-agent': 'my-app/0.0.1'}
r = requests.get("https://www.baidu.com/", headers = head)
需要注意的是:
- 标头字典中的所有值必须是字符串、bytes 或者 Unicode 信息。最好不要使用 Unicode 信息。
- 自定义标头的优先级低于其它可确定的信息源,比如:
- (如果有)netrc 配置文件的内容
- (如果有)auth 参数的内容。
- (如果有)在 URL 中提供的代理凭据。标头 Proxy-Authorization 将被 URL 中提供的代理凭据覆盖。
- (可以确定信息长度时)标头 Content-Length 会被修改为确定的信息长度。
- 其它类似的信息源。
详细使用方式参见 https://httpbin.org/#/。
cookie 是 HTTP 标头中的一个字段。
网页常用 cookies 来记录用户的情况。浏览器在请求网页时,也会发送上次浏览网页时留下的 cookies,以实现自动登录等功能。一个 cookie 会包含以下的内容:
- 键
- 值
- 域名
- 路径
- 过期时间
在使用时,我们可以用字典,只传递“键”和“值”,让其它内容自动填充。比如:
url = 'https://httpbin.org/cookies'
cookies = {'name': 'value'}
r = requests.get(url, cookies=cookies)
print(r.text)
也可以使用 requests.cookies.RequestsCookieJar 实例来传递更多内容。比如:
#创建存储 cookies 的对象。
jar = requests.cookies.RequestsCookieJar()
#添加两个 cookie
jar.set('name1', 'value1', domain='httpbin.org', path='/cookies')
jar.set('name2', 'value2', domain='httpbin.org', path='/')
#请求时传递 cookies。
url = 'https://httpbin.org/cookies'
r = requests.get(url, cookies=jar)
print(r.text)
对于需要登录的网页,读者可以使用比如 EditThisCookie 之类的浏览器扩展程序来查看并复制自己浏览网页产生的 cookies,将其详细信息复制,并用 requests 模块发送。此时请求的结果,等同于读者登录账号之后的请求结果。
HTTP 协议支持自定义的身份验证信息。该信息的内容应当由服务器和用户自行约定。并会覆盖位于标头的 Authorization 信息。
该内容应当为一个多个不含空格的ASCII字符串组成的元组,可以使用以下方式插入该信息。
auth = ("Basic","QWxhZGRpbjpvcGVuIHNlc2FtZQ==")
url = 'https://httpbin.org/get'
r = requests.get(url, auth=auth)
在请求时,有时会需要一些时间才能正确接收到服务器返回的信息;也可能请求丢失、服务器未响应。设定响应延时可以解决这些问题。
响应延时是以秒为单位的浮点数或形如 (连接延时, 读取延时)
的浮点数元组。使用方式为:
url = 'https://httpbin.org/get'
r = requests.get(url, timeout=0.1)
许多网页都会把 http 的内容重定向到 https 来处理。虽然禁止重定向没有什么好处,我们是可以禁止这样的重定向的。比如:
r = requests.get('http://github.com/', allow_redirects=False)
可以设置经由代理服务器发送请求。方式如下:
proxies = {
'http': 'http://10.10.1.10:3128',
'https': 'http://10.10.1.10:1080',
}
requests.get('http://example.org', proxies=proxies)
如果在请求时设置了 stream 参数为 True,则返回的内容会是一个“二进制流”。
r = requests.get('https://github.com', stream=True)
if r.encoding is None:
r.encoding = 'utf-8'
for line in r.iter_lines(): #像二进制流一样处理返回的内容
line.decode(r.encoding)
在互相传递信息时,需要验证双方的身份。互联网通过公私钥加密建立了“证书”机制,通过证书可以验证沟通双方的身份。其具体内容可以参见 数字证书 。读者在进行交流时,也需要验证证书。
在进行请求时,可以对服务器返回的 TLS 证书进行验证。
默认总会验证服务器返回的 TLS 证书。如果要不进行验证,可以增加以下参数。
requests.get('http://example.org', verify=False)
如果 verify 参数为一个字符串,则只有服务器使用路径包含 verify 的证书时,才会通过验证。比如:
requests.get('https://github.com', verify='/path/to/certfile')
可以为自己的客户端设置证书。
如果给 cert 参数输入字符串,则应当输入指向 ssl 客户端认证文件(.pem 文件)的路径。如果输入元组,则应当传入形如 ("cert","key")
的元组。
上面章节提到的请求,均为“GET”请求,只用于读取资料。如果要进行其它操作,则需要使用其它的请求。要了解所有的请求方式,参见 HTTP 请求方法。
requests 实现了以下几种请求方式:
- GET,读取资料。
- HEAD,获得资料的元信息,而不获取整个资料。
- OPTIONS,使服务器传回该资源支持的所有HTTP请求方法。
- POST,上传新数据。
- PUT,上传并修改。
- DELETE,删除数据。
- PATCH,将局部更改应用到资源。
在 requests 模块中,这些请求方式支持 requests.get() 函数中支持的一切参数。以下将主要讲解它们在使用时与 GET 的区别。
requests.head() 函数向服务器请求资料,但服务器不会返回资料的正文部分。与 GET 不同,此函数默认禁用重定向。
r = requests.head('https://httpbin.org/get')
如果要启用重定向,则需要:
r = requests.head('http://github.com/', allow_redirects=True)
OPTIONS 请求的方法如下:
r = requests.options('https://httpbin.org/get')
print(r.headers['allow']) #输出:OPTIONS, GET, HEAD
从以上输出可以知道,该 URL 支持三种请求:OPTIONS、GET 和 HEAD。
POST 请求用于上传信息。
http 支持表单内容的发送。其基本使用方法如下:
payload = {'key': 'value'} #要上传的信息
r = requests.post('https://httpbin.org/post', data=payload)
print(r.text)
通过在 data 中传入字典,可以将字典的信息以 form 的形式发送。
在 data 中,可以给同一个键设置多个值,此时可以用以下两种方法:
payload_tuples = [('key1', 'value1'), ('key1', 'value2')]
r1 = requests.post('https://httpbin.org/post', data=payload_tuples)
payload_dict = {'key1': ['value1', 'value2']}
r2 = requests.post('https://httpbin.org/post', data=payload_dict)
如果要发送文件,则可以使用以下方式:
url = 'https://httpbin.org/post'
files = {'file': open('report.xls', 'rb')}
r = requests.post(url, files=files)
print(r.text)
其中,文件内容最好是二进制的,这样有助于自动获取请求中信息的大小。这样发送的文件不包含文件名,也不包含处理方式。仅包含文件的内容。
如果要包含文件名、文件类型等信息,可以使用元组:
url = 'https://httpbin.org/post'
files = {'file': (
'report.xls',
open('report.xls', 'rb'),
'application/vnd.ms-excel',
{'Expires': '0'}
)}
r = requests.post(url, files=files)
print(r.text)
如果读者希望,也可以直接将文本内容当成文件的内容并发送出去:
url = 'https://httpbin.org/post'
files = {'file': (
'report.csv',
'some,data,to,send\nanother,row,to,send\n'
)}
r = requests.post(url, files=files)
print(r.text)
对于较大的文件,可能无法在一个请求中完整发送。此时,需要使用流式发送:
with open('file_path', 'rb') as f:
requests.post("http://some.url/streamed", data = f)
这种流式发送,需要所请求的服务器有对应的支持。
使用以下方法,可以同时发送多个文件:
url = 'https://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
在发送文件时,建议用二进制模式打开文件。
r = requests.put('https://httpbin.org/put', data={'key': 'value'})
r = requests.delete('https://httpbin.org/delete')
r = requests.patch('https://httpbin.org/put', data={'key': 'value'})
requests 模块的请求支持使用钩子。目前支持的钩子仅有一个:"response"
。这个钩子会在请求得到响应时触发,使用方法如下:
def print_url(r):
print(f"向 {r.url} 发出了请求")
r = requests.get('https://httpbin.org/', hooks={'response': print_url})
程序输出为:
向 https://httpbin.org/ 发出了请求
在得到响应后,钩子指定的函数会被传入一个响应结果对象。
特别的,如果 "response"
钩子所指定的函数返回值不为 None,则 requests.get()
函数会返回钩子函数所返回的内容,比如以下的钩子函数:
def record_hook(r, *args, **kwargs):
r.hook_called = True
return r
任何请求所得的回应,都是一个 request.Response 对象。本章节将讲解该对象的方法。
读者可以通过以下方式,读取网站返回时使用的 标头。
r = requests.get(url)
print(r.headers)
标头会以字典形式返回。
读者可以通过以下方式,读取网站返回的 cookies。
r = requests.get(url)
print(r.cookies)
此方法返回的是一个 requests.cookies.RequestsCookieJar 实例,该实例有以下方法:
r = requests.get(url)
print(r.elapsed)
响应延时是发送请求到收到响应之间的时间间隔,为一个 datetime.timedelta 对象。
r = requests.get(url)
print(r.history)
在进行请求时,requests 模块会自动进行重定向。通过这种方法可以获得重定向的历史记录列表,列表的元素为每次重定向时的 request.Response 对象。
r = requests.get(url)
print(r.status_code) #状态码,比如 404、200 等
print(r.reason) #状态字符串,比如"Not Found"等
会话(Session)是一种高级对象。它会自动保存网站所发送的 cookie 并在下一次请求时向对应的网站提供。它同时还提供了连接池和连接配置。
以下程序创建了一个 Session,并用该 Session 发送了一个 GET 请求:
import requests
s = requests.Session()
s.get('https://httpbin.org/get')
也可以用上下文管理器的方式来使用 Session:
import requests
with requests.Session() as s:
s.get('https://httpbin.org/get')
Session 支持上面列出的一切请求方式。使用 Session 发送请求时所使用的“方法名称”和“方法参数”,和使用上面列出的函数来发送请求时的“函数名称”和“函数参数”是一样的。
对于经过以下代码所创建的 Session:
import requests
session = requests.Session()
该对象在进行请求时,会自动在请求中输入各种参数。以下是参数的列表:
属性 | 说明 | 备注 |
---|---|---|
session.cookies | cookie | 一个 RequestsCookieJar 实例,和上面 #cookies 章节一致。 可以修改成其它的、cookielib.CookieJar 的子类型的实例。 |
session.auth | 身份验证信息 | 和上面 #身份验证信息 章节一致。 |
session.verify | 服务器验证 | 和上面 #服务器验证 章节一致。 |
session.cert | 客户端验证 | 和上面 #客户端验证 章节一致。 |
session.headers | 标头信息 | 和上面 #请求的标头 章节一致。 |
session.hooks | 钩子 | 和上面 #请求的钩子 章节一致。 |
session.proxies | 代理服务器 | 和上面 #代理服务器 章节一致。 |
session.stream | 流 | 和上面 #流 章节一致。 |
session.max_redirects | 最大重定向次数 | 可以设置重定向的最大次数,默认为 30。 |
session.params | 请求参数 | 在请求时会被自动加入的,所有参数的字典。 |
除上述属性之外,requests.Sessions 还有一些其它的方法,详情参见文档。