目录

python requests的替代者?httpx初体验

python 的 requests 库由于其使用简单,文档丰富成为了很多人在发送 http 请求时候的优选选择。前几天看到了一个类似的实现 httpx,在这里简单使用体验一下,顺便简单分享一下体验心得。

相比较 requests,httpx 支持 sync 和 async 的 API,支持 http1.1 和 http2。httpx 尽最大努力兼容 requests 的 API,这样一来用户从 requests 转换到 httpx 的成本就相对较为低廉了。

基本 API

>>> import httpx
>>> r = httpx.get('https://www.example.org/')
>>> r
<Response [200 OK]>
>>> r.status_code
200
>>> r.headers['content-type']
'text/html; charset=UTF-8'
>>> r.text
'<!doctype html>\n<html>\n<head>\n<title>Example Domain</title>...'

简单扫一圈,满眼都是 requests 当年的样子。下面是 requests 的 API,大家来找茬,看看哪里不一样。

>>> import requests
>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>> r.status_code
200
>> r.headers['content-type']
'application/json; charset=utf8'
>> r.encoding
'utf-8'
>> r.text
'{"type":"User"...'
>> r.json()
{'private_gists': 419, 'total_private_repos': 77, ...}

不能说非常相似,只能说是一模一样。

httpx client

requests 为一组 http 请求提供了 session 对象来进行统一设置和管理,httpx 则相应的提供了 client 对象。我们来对比一下使用方式先。

首先使用 starlette 来创建一个简单的 python api 服务。starlette 项目可以想象成是 async 版本的 flask,跟 httpx 系出同门。

# example.py
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route

async def homepage(request):
    await asyncio.sleep(0.1) # 加一点点等待,不加也可以
    return JSONResponse({'hello': 'world'})

routes = [
    Route("/", endpoint=homepage)
]

# app = Starlette(debug=True, routes=routes)
app = Starlette(debug=False, routes=routes)

使用 uvicorn 运行。

$ uvicorn example:app

上面的服务提供了 1 个接口 localhost:8000,返回值如下

http :8000

HTTP/1.1 200 OK
content-length: 17
content-type: application/json
date: Thu, 11 Aug 2022 07:10:07 GMT
server: uvicorn

{
    "hello": "world"
}

我们先用非 client/session 方式来访问该接口 30 次,顺便统计一下运行时间

requests 先出场。

# without_session
import requests

for i in range(0,30):
	res = requests.get('http://localhost:8000/').json()
	print(res)
python without_session.py  0.24s user 0.08s system 9% cpu 3.500 total

上面是不用 session 的方式,3.5s 完成。

使用 session 试试。

import requests

s = requests.Session()

for i in range(0,30):
	res = s.get('http://localhost:8000/').json()
	print(res)
python with_session.py  0.22s user 0.08s system 8% cpu 3.443 total

3.44s,快了一点点。

下面是 httpx 不使用 client 的方式。

python without_client.py  0.69s user 0.11s system 20% cpu 3.972 total

3.9s。

使用 client 试试

import httpx

with httpx.Client() as client:
	for i in range(0, 30):
		res = client.get('http://localhost:8000/').json()
		print(res)
python with_client.py  0.38s user 0.11s system 13% cpu 3.707 total

3.7s,也快了一些。

这里可以简单总结一下,使用 client/session 可以提升一组请求的发送效率,另外也提供了进行统一配置(比如修改 header 的)的快捷方式。上面的测试由于请求处理的太快效果不是很明显,在日常的测试中两种方式的区别可能会更加容易发现一些。

async

还是 30 个请求,这次我们用 httpx 的 async 方式来试试。

import asyncio
from asyncio import tasks
import httpx

async def send_requests(client):
	r = await client.get('http://localhost:8000')
	print(r.json())
	return r.json()


async def main():
	tasks = []
	async with httpx.AsyncClient() as client:
		for i in range(0, 30):
			tasks.append(send_requests(client))

		await asyncio.gather(*tasks)


asyncio.run(main())
python httpx_async.py  0.47s user 0.13s system 71% cpu 0.848 total

0.84 秒,这大概就是 httpx 的最终奥义吧。

总结

作为下一代的 http client,httpx 出自名门望族(其开发团队开发了**django-rest-framework**),兼容了部分的 requests api,支持 async 操作等,是具有取代 requests 的能力的,在爬虫场景非常有潜力。