Python的HTTP 客户端比较

Python 有很多的 HTTP 客户端,在 GitHub 上搜“Python HTTP Clients”能搜到 1700 多个结果。 ​

本文主要讨论 urllib.request,Requests,AIOHTTP,GRequests,HTTPX 并分析它们的优劣区别。

开头
作为例子,我们将向一个天气API(dev.qweather.com)发送 GET 请求并解析返回的 JSON 数据。返回数据如下:

{
    "code": "200",
    "updateTime": "2023-12-17T18:37+08:00",
    "fxLink": "https://www.qweather.com/weather/beijing-101010100.html",
    "now": {
        "obsTime": "2023-12-17T18:25+08:00",
        "temp": "-10",
        "feelsLike": "-14",
        "icon": "150",
        "text": "晴",
        "wind360": "243",
        "windDir": "西南风",
        "windScale": "2",
        "windSpeed": "9",
        "humidity": "43",
        "precip": "0.0",
        "pressure": "1032",
        "vis": "13",
        "cloud": "0",
        "dew": "-20"
    },
    "refer": {
        "sources": ["QWeather"],
        "license": ["CC BY-SA 4.0"]
    }
}

0、标准库
熟悉 Python 标准库的人可能也知道 urllib 库和 urllib2 库的纠葛历史。Python3 中,最初的 urllib2 库被一分为二,urllib.request 和 urllib.error。 ​

我们先来看看,如何用标准库发送 HTTP 请求。

import json
import urllib.request
 
response = urllib.request.urlopen('https://devapi.qweather.com/v7/weather/now?location=101010100&key=我的KEY')
text = response.read()
print(json.loads(text.decode('utf-8')))
一定要注意,urllib.request 返回的是二进制数据,我们需要手动调用 json 库将其转换为可读数据(有次因为这个问题我排查了1小时...)。

POST 请求的代码如下(由于这个天气API没有可以POST请求的地方,于是我换了个API):​

import json
from urllib import request, parse
 
data = {"name": "Obi-Wan Kenobi", ...}
 
encoded_data = json.dumps({
    "task_name": "每日签到"
})
req = request.Request('api.v2.rainyun.com', data=encoded_data)
req.add_header('Content-Type', 'application/json')
response = request.urlopen(req)
 
text = response.read()
 
print(json.loads(text.decode('utf-8')))
提交数据时,也需要手动执行 json 数据编码,并设置“Content-Type”头。 ​

你可能会觉得这些操作比较麻烦——很多的开发者也是这么认为的(我也认为),这也是Python社区出现这么多 HTTP 客户端的原因。接下来,我会介绍其中(我觉得)最好的 5 个。

1. urllib3
urllib3 是一个强力、易用的 HTTP 客户端,用户很多,它提供了很多标准库缺失的重要特性。(官网是这么说的)

但是,它很奇怪,基于 urllib 的 urllib3 并不是标准库的一部分(wtf)。它提供了很多重要特性,如连接池、TLS 支持以及线程安全等。urllib3 实现了连接复用,在爬虫等领域的性能要比标准库好很多。 ​

import urllib3
import json
 
http = urllib3.PoolManager()
r = http.request('GET', 'https://devapi.qweather.com/v7/weather/now?location=101010100&key=你的KEY')
 
print(json.loads(r.data.decode('utf-8')))
和标准库一样,我们还是得手动执行 JSON 格式转换(就不能方便点吗)。 ​

import json
import urllib3
 
data = {"task_name": "每日签到"}
 
http = urllib3.PoolManager()
 
encoded_data = json.dumps(data).encode('utf-8')
 
r = http.request(
    'POST',
    'https://api.v2.rainyun.com',
    body=encoded_data,
    headers={'Content-Type': 'application/json'}
)
 
print(json.loads(r.data.decode('utf-8')))

Poolmanager 对象负责连接池管理,保证线程安全,而 HTTP 方法只是传入请求函数的一个字符串参数。urllib3 的许多特性都是通过 Poolmanager 实现的。缓存的连接池保证我们在请求同一个服务器时可以复用 HTTP 连接。如果我们需要对同一个服务器发起很多请求,可以增大连接池的容量。 ​

不过,使用连接池也带来了一个问题,就是 cookie 不好管理,只适合无状态的 HTTP 请求。有时必须手动设置 cookie:

headers={'Cookie': 'foo=bar; hello=world'}
2. Requests
requests 包在 Python 社区广受好评,根据 PePy 的数据,每月下载量高达 1100 万次。在 urllib.request 的官方文档中,也推荐大家使用 requests 作为“高阶 HTTP 客户端”(wow)。这个库的使用极其简单,大多数 Python 开发者都将其作为默认选择。 ​

用 requests 发起 HTTP 请求:

import requests
 
r = requests.get('https://devapi.qweather.com/v7/weather/now?location=101010100&key=你的KEY')
 
print(r.json())
POST 请求也很简单,只需修改调用的方法即可:

import requests
 
data = {"task_name": "每日签到"}
 
r = requests.post('https://api.v2.rainyun.com', json=data)
 
print(r.json())
可以理解 requests 为什么这么受欢迎了吧——它的设计实在太优雅了!使用最少的代码,直接调用对应的 HTTP 方法(GET/POST),不需要手动执行 JSON 数据编解码。对开发者来说,即好用又好懂。

发送 POST 请求时,也不需要操心数据编码、设置 HTTP 头之类的事情,一切都由 requests 自动完成。 ​

如果要提交的是表单,将 "json" 字段替换成 "data" 即可。 ​

设置 cookie 的方法也很简单,代码如下:

r = requests.post('https://devapi.qweather.com/v7/weather/now?location=101010100&key=你的KEY', data=data, cookies={'foo': 'bar', 'hello': 'world'}))
 ​同时,requests 还支持 session、请求钩子、自定义重试策略等。使用 session 时,可以在不同请求之间共享 cookie,实现有状态的 HTTP 访问,这正是 urllib3 不支持的特性。下面是一个官方文档中的例子:

而请求钩子可以在每次请求之后都执行同样的动作。大家可能对 git 中的类似概念比较熟悉。更多特性可以参考官方文档。 ​

对大多数应用来说,requests 都是一个不错的选择。

3. AIOHTTP
AIOHTTP 既有客户端也有服务器,适用于既提供 API 又请求别人的 API 的场景,在 GitHub 上有 11k 颗星,也是很多第三方库的依赖项。 ​

使用 AIOHTTP 发送一个 GET 请求的代码如下:

import aiohttp
import asyncio
 
async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://devapi.qweather.com/v7/weather/now?location=101010100&key=你的KEY') as response:
            print(await response.json())
 
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
POST 请求的代码如下:

import aiohttp
import asyncio
 
data = {"task_name": "每日签到"}
 
async def main():
    async with aiohttp.ClientSession() as session:
        async with session.post('https://api.v2.rainyun.com', json=data) as response:
            print(await response.json())
 
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
可以看到,请求方法与 requests 类似,但整体代码长了很多,引入了 asyncio 包并用 async 与 await 调用请求方法。 ​

关于与 requests 等其它库的区别,官方文档做了很好的说明。如果你不熟悉异步概念的话可能得花点时间理解一下。简单地说就是,我们可以同时发起多个请求,而不用等前一个请求响应之后才发起另一个请求。如果只需要请求一次的话,这个问题无关紧要,但在数十个乃至百千万个请求场景下,让 CPU 等待显然是不合适的。 ​

4.GRequests
GRequests 使用 Gevent 实现异步请求,Gevent 是一个“基于协程的网络库”。这个包出来得比较早,第一个 release 是在 2012 年,那时 Python 标准库中还没有 asyncio 包呢。 ​

通过这个包,我们可以发起单次请求,也可以异步发起多个请求:

import grequests
 
reqs = []
 
for ship_id in range(0, 50):
    reqs.append(grequests.get(f'https://swapi.dev/api/starships/{ship_id}/'))
 
for r in grequests.map(reqs):
    print(r.json())
(使用星球大战API查看前50艘战舰示例)

GRequests 的文档比较少,甚至在自己的 Github 页面上也推荐大家使用其它包。毕竟它只有 165 行代码,并没有在 requests 的基础上提供更多特性。在过去 9 年中,GRequests 只发布了 6 个 release 版本,因此,如果不是真的搞不懂异步编程的话,不是很建议使用这个包。

5.http
 http 既有客户端也有服务器,适用于既提供 API 又请求别人的 API 的场景。

使用 http.client 发送一个 GET 请求的代码如下:

import http.client
import json
import time
 
conn = http.client.HTTPSConnection("api.v2.rainyun.com") #连接api服务器
payload = json.dumps({
    "test" : "test"
}) #定义发送的json数据
headers = {
    'x-api-key': '***********************************',
    'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
    'Content-Type': 'application/json'
} #定义headers
conn.request("GET", "/user/reward/tasks",payload,headers) #发送数据
res = conn.getresponse() #获得反馈
data = res.read() #读取反馈
print(data.decode("utf-8")) #用UTF-8查看反馈的内容

POST 请求的代码如下:

import http.client
import json
import time
 
conn = http.client.HTTPSConnection("api.v2.rainyun.com") #连接api服务器
payload = json.dumps({
    "task_name": "每日签到"
}) #定义发送的json数据
headers = {
    'x-api-key': 'V8ERP4L5J2U0PxTmS4kBDSedplMrEhEq',
    'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
    'Content-Type': 'application/json'
} #定义headers
conn.request("POST", "/user/reward/tasks", payload, headers) #发送数据
res = conn.getresponse() #获得反馈
data = res.read() #读取反馈
print(data.decode("utf-8")) #用UTF-8查看反馈的内容

总结
综上,我们看到,requests 库影响了很多其它库的设计,在社区中广受欢迎,是大多数开发者的默认选择,同时也支持 session 和重试等特性,在需求不复杂时,或许可以作为你的首选项。 ​

对需求较复杂,需要同时发起许多请求的开发者来说,AIOHTTP 是目前的最佳选择。它在基准测试中的异步表现最好,下载量与星星数最多,并能提供稳定版本。但是它也比较复杂,不支持重试策略,因此,如果你不在意 beta 版本,可以选择 HTTPX。

不论何种场景,总是能找到适用于你的 Python HTTP 客户端的。

  • 23
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值