Python HTTP 客户端:httpx 异步用法与 requests 的对比
大家好,今天我们来聊聊 Python 中 HTTP 客户端,重点对比 httpx
和 requests
在异步场景下的使用。requests
库以其简洁易用而闻名,长期以来一直是 Python HTTP 请求的首选方案。然而,在需要高并发和异步处理的场景下,requests
的同步阻塞特性就显得力不从心。httpx
库的出现,填补了这一空白,它提供了与 requests
类似的 API,同时支持同步和异步操作,更好地满足了现代 Web 应用的需求。
1. 两种库的基础概念
在深入异步用法之前,我们先简单回顾一下 requests
和 httpx
的基本概念。
-
requests: 一个优雅而简洁的 Python HTTP 库,基于 urllib3 构建,以人类友好的 API 著称。它默认是同步阻塞的。
-
httpx: 一个完全兼容
requests
的 HTTP 客户端,但增加了对 HTTP/2 的支持,并且支持同步和异步操作。它构建于asyncio
之上,能够充分利用异步编程的优势。
2. requests 的同步请求
首先,我们来看 requests
的基本用法。以下是一个使用 requests
发送 GET 请求的例子:
import requests
url = "https://www.example.com"
try:
response = requests.get(url)
response.raise_for_status() # 检查请求是否成功
print(f"Status code: {response.status_code}")
print(f"Content: {response.text[:100]}...") # 打印前100个字符
except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")
这段代码非常简单易懂。requests.get()
函数发送一个 GET 请求,并返回一个 Response
对象。我们可以通过 response.status_code
访问状态码,通过 response.text
访问响应内容。response.raise_for_status()
方法会在状态码表示错误时抛出异常,方便我们进行错误处理。
然而,requests
的同步特性意味着,当程序执行 requests.get()
时,它会阻塞,直到服务器返回响应。在高并发的场景下,这种阻塞会导致程序性能下降。
3. httpx 的同步请求
httpx
也支持同步请求,API 与 requests
非常相似。
import httpx
url = "https://www.example.com"
try:
with httpx.Client() as client:
response = client.get(url)
response.raise_for_status()
print(f"Status code: {response.status_code}")
print(f"Content: {response.text[:100]}...")
except httpx.RequestError as e:
print(f"An error occurred: {e}")
except httpx.HTTPStatusError as e:
print(f"An HTTP error occurred: {e}")
这里我们使用 httpx.Client()
创建一个客户端实例,并使用 client.get()
发送请求。需要注意的是,httpx.Client()
最好用 with
语句管理,确保连接在使用完毕后正确关闭。 错误处理也需要考虑httpx.RequestError
和 httpx.HTTPStatusError
两种异常。
尽管 httpx
提供了同步接口,但它的真正优势在于异步支持。
4. httpx 的异步请求
httpx
的异步支持基于 asyncio
库。要使用 httpx
进行异步请求,我们需要使用 async
和 await
关键字。
import asyncio
import httpx
async def fetch_url(url):
async with httpx.AsyncClient() as client:
try:
response = await client.get(url)
response.raise_for_status()
return response.text[:100]
except httpx.RequestError as e:
print(f"An error occurred: {e} while requesting {url}")
return None
except httpx.HTTPStatusError as e:
print(f"An HTTP error occurred: {e} while requesting {url}")
return None
async def main():
urls = ["https://www.example.com", "https://www.python.org", "https://www.google.com"]
tasks = [fetch_url(url) for url in urls]
results = await asyncio.gather(*tasks) # 并发执行所有任务
for url, result in zip(urls, results):
if result:
print(f"Content from {url}: {result}...")
if __name__ == "__main__":
asyncio.run(main())
让我们逐步分析这段代码:
async def fetch_url(url)
: 定义一个异步函数,用于获取指定 URL 的内容。注意async
关键字。async with httpx.AsyncClient() as client:
: 创建一个异步客户端实例。AsyncClient
必须用async with
语句管理。response = await client.get(url)
: 发送异步 GET 请求。await
关键字表示等待请求完成。async def main():
: 定义主函数,用于组织异步任务。tasks = [fetch_url(url) for url in urls]
: 创建一个任务列表,每个任务对应一个 URL。- *`results = await asyncio.gather(tasks)
:** 使用
asyncio.gather()并发执行所有任务。
asyncio.gather()` 接受一个任务列表,并返回一个包含所有任务结果的列表。 asyncio.run(main())
: 运行异步主函数。
与 requests
的同步请求相比,httpx
的异步请求不会阻塞程序。当一个请求正在等待服务器响应时,程序可以继续执行其他任务。这使得程序能够更有效地利用 CPU 资源,提高并发性能。
5. 异步任务并发数控制
虽然异步可以提高并发,但如果不加以控制,过多的并发请求可能会压垮服务器或客户端自身。我们需要限制并发数量。可以使用 asyncio.Semaphore
来控制并发数。
import asyncio
import httpx
CONCURRENCY = 5 # 设置最大并发数为 5
semaphore = asyncio.Semaphore(CONCURRENCY)
async def fetch_url(url):
async with semaphore: # 获取一个信号量
async with httpx.AsyncClient() as client:
try:
response = await client.get(url)
response.raise_for_status()
return response.text[:100]
except httpx.RequestError as e:
print(f"An error occurred: {e} while requesting {url}")
return None
except httpx.HTTPStatusError as e:
print(f"An HTTP error occurred: {e} while requesting {url}")
return None
async def main():
urls = ["https://www.example.com"] * 20 # 模拟20个请求
tasks = [fetch_url(url) for url in urls]
results = await asyncio.gather(*tasks)
for url, result in zip(urls, results):
if result:
print(f"Content from {url}: {result}...")
if __name__ == "__main__":
asyncio.run(main())
在这个例子中,我们创建了一个 asyncio.Semaphore
对象,设置最大并发数为 5。在 fetch_url()
函数中,我们使用 async with semaphore:
获取一个信号量。只有当信号量可用时,才能执行请求。当请求完成后,信号量会自动释放。这样,我们就可以有效地控制并发数量,避免压垮服务器或客户端。
6. 超时设置
在进行网络请求时,设置超时时间非常重要,以避免程序长时间阻塞。requests
和 httpx
都提供了超时设置。
requests:
import requests
url = "https://www.example.com"
try:
response = requests.get(url, timeout=5) # 设置超时时间为 5 秒
response.raise_for_status()
print(f"Status code: {response.status_code}")
print(f"Content: {response.text[:100]}...")
except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")
httpx (同步):
import httpx
url = "https://www.example.com"
try:
with httpx.Client(timeout=5) as client: # 设置全局超时时间
response = client.get(url)
response.raise_for_status()
print(f"Status code: {response.status_code}")
print(f"Content: {response.text[:100]}...")
except httpx.RequestError as e:
print(f"An error occurred: {e}")
except httpx.HTTPStatusError as e:
print(f"An HTTP error occurred: {e}")
except httpx.TimeoutException as e: #处理超时异常
print(f"Timeout occurred: {e}")
httpx (异步):
import asyncio
import httpx
async def main():
url = "https://www.example.com"
async with httpx.AsyncClient(timeout=5) as client: #设置全局超时
try:
response = await client.get(url)
response.raise_for_status()
print(f"Status code: {response.status_code}")
print(f"Content: {response.text[:100]}...")
except httpx.RequestError as e:
print(f"An error occurred: {e}")
except httpx.HTTPStatusError as e:
print(f"An HTTP error occurred: {e}")
except httpx.TimeoutException as e: #处理超时异常
print(f"Timeout occurred: {e}")
if __name__ == "__main__":
asyncio.run(main())
requests
使用 timeout
参数设置超时时间,单位为秒。 httpx
也可以使用 timeout
参数,但需要注意,httpx
的 timeout
参数可以设置全局超时时间(在 Client
或 AsyncClient
初始化时设置)或单个请求的超时时间(在 get()
、post()
等方法中设置)。
7. 会话管理 (Session)
会话管理允许我们跨多个请求保持状态,例如 cookie。requests
和 httpx
都提供了会话管理功能。
requests:
import requests
session = requests.Session()
# 设置 cookie
session.cookies.set("my_cookie", "my_value", domain="www.example.com")
# 发送请求
response = session.get("https://www.example.com")
print(response.status_code)
# 关闭会话
session.close()
httpx (同步):
import httpx
with httpx.Client() as client:
# 设置 cookie
client.cookies.set("my_cookie", "my_value", domain="www.example.com")
# 发送请求
response = client.get("https://www.example.com")
print(response.status_code)
httpx (异步):
import asyncio
import httpx
async def main():
async with httpx.AsyncClient() as client:
# 设置 cookie
client.cookies.set("my_cookie", "my_value", domain="www.example.com")
# 发送请求
response = await client.get("https://www.example.com")
print(response.status_code)
if __name__ == "__main__":
asyncio.run(main())
requests
使用 requests.Session()
创建会话对象。 httpx
使用 httpx.Client()
或 httpx.AsyncClient()
创建客户端实例,该实例也充当会话管理器。cookie 可以使用 cookies.set()
方法设置。
8. 数据提交 (POST)
发送 POST 请求并提交数据是常见的 Web 开发任务。 requests
和 httpx
都提供了方便的 API。
requests:
import requests
url = "https://www.example.com/post"
data = {"key1": "value1", "key2": "value2"}
response = requests.post(url, data=data)
print(response.status_code)
httpx (同步):
import httpx
url = "https://www.example.com/post"
data = {"key1": "value1", "key2": "value2"}
with httpx.Client() as client:
response = client.post(url, data=data)
print(response.status_code)
httpx (异步):
import asyncio
import httpx
async def main():
url = "https://www.example.com/post"
data = {"key1": "value1", "key2": "value2"}
async with httpx.AsyncClient() as client:
response = await client.post(url, data=data)
print(response.status_code)
if __name__ == "__main__":
asyncio.run(main())
requests
和 httpx
都使用 post()
方法发送 POST 请求。数据可以通过 data
参数传递。 如果需要发送 JSON 数据,可以使用 json
参数。
9. 区别总结
下表对 requests
和 httpx
在异步方面的使用进行了总结:
Feature | requests | httpx |
---|---|---|
异步支持 | 不支持,同步阻塞 | 支持,基于 asyncio |
异步客户端 | 无 | httpx.AsyncClient |
异步方法 | 无 | async with client.get() 、async with client.post() 等 |
并发控制 | 需要使用线程或进程实现 | asyncio.Semaphore |
异常处理 | requests.exceptions.RequestException |
httpx.RequestError , httpx.HTTPStatusError , httpx.TimeoutException |
是否需要事件循环 | 不需要 | 需要,使用 asyncio.run() |
10. 应该选择哪个库?
- requests: 如果你的应用是同步的,并且不需要处理高并发,那么
requests
仍然是一个不错的选择,它简单易用。 - httpx: 如果你需要处理高并发,或者你的应用是异步的,那么
httpx
是更好的选择。它提供了与requests
类似的 API,同时支持异步操作,能够更好地满足现代 Web 应用的需求。
11. 异步编程是关键
httpx
的异步功能依赖于 asyncio
库。理解异步编程的概念,例如事件循环、协程、async
和 await
关键字,对于使用 httpx
进行异步开发至关重要。
12. httpx 异步请求优势明显
总而言之,httpx
在异步场景下相对于 requests
具有明显的优势。它能够更好地利用 CPU 资源,提高并发性能,并且提供了方便的 API 和丰富的功能。如果你正在开发需要处理高并发的 Web 应用,那么 httpx
绝对值得尝试。