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() 等请求函数会进行一次请求,并返回请求得到的结果。

响应结果 编辑

对于结果对象 rr.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}

json 响应结果 编辑

使用 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 模块中的方法来传输这些信息。

URL参数 编辑

在 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 会被修改为确定的信息长度。
    • 其它类似的信息源。

cookies 编辑

关于 httpbin.org
httpbin.org 是一个公益性的网站。向 httpbin.org 域名下的对应 URL 发送请求时。请求中的所有信息会在返回信息的正文中出现。善用这一工具有助于读者了解如何发送请求。
详细使用方式参见 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)

https 重定向 编辑

许多网页都会把 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 编辑

requests.head() 函数向服务器请求资料,但服务器不会返回资料的正文部分。与 GET 不同,此函数默认禁用重定向。

r = requests.head('https://httpbin.org/get')

如果要启用重定向,则需要:

r = requests.head('http://github.com/', allow_redirects=True)

requests.options 编辑

OPTIONS 请求的方法如下:

r = requests.options('https://httpbin.org/get')
print(r.headers['allow'])  #输出:OPTIONS, GET, HEAD

从以上输出可以知道,该 URL 支持三种请求:OPTIONS、GET 和 HEAD。

requests.post 编辑

POST 请求用于上传信息。

form 信息的发送 编辑

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

在发送文件时,建议用二进制模式打开文件。

requests.put 编辑

r = requests.put('https://httpbin.org/put', data={'key': 'value'})

requests.delete 编辑

r = requests.delete('https://httpbin.org/delete')

requests.patch 编辑

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 编辑

读者可以通过以下方式,读取网站返回的 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 对象。

HTTP 状态码 编辑

r = requests.get(url)
print(r.status_code) #状态码,比如 404、200 等
print(r.reason)      #状态字符串,比如"Not Found"等

会话 编辑

会话(Session)是一种高级对象。它会自动保存网站所发送的 cookie 并在下一次请求时向对应的网站提供。它同时还提供了连接池和连接配置。

创建 Session 和使用 编辑

以下程序创建了一个 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 包含的设置 编辑

对于经过以下代码所创建的 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 还有一些其它的方法,详情参见文档。