Python中有许多HTTP客户端。使用最广泛且最容易的是requests。
持续连接
持续连接是自HTTP 1.1以来的标准,尽管许多应用程序并未使用它们。以简单模式使用请求时(例如使用get函数),连接会在返回时关闭,Session对象允许重用已经打开的连接。
import requests
session = requests.Session()
session.get("https://china-testing.github.io/")
# Connection is re-used
session.get("https://china-testing.github.io/")
每个连接都存储在连接池中(默认为10)
import requests
session = requests.Session()
adapter = requests.adapters.HTTPAdapter(
pool_connections=100,
pool_maxsize=100)
session.mount('http://', adapter)
response = session.get("http://example.org")
重用TCP连接有许多性能优势:
- 降低CPU和内存使用率(同时打开的连接较少)。
- 减少了后续请求中的延迟(无TCP握手)。
- 可以引发异常而不会关闭TCP连接。
HTTP协议还提供了流水线,流水线化允许在同一连接上发送多个请求,而无需等待答复(批处理)。不幸的是,请求库不支持此功能。但是,流水线请求可能不如并行发送它们那么快。实际上,HTTP 1.1协议强制以与发送请求相同的顺序发送答复-先进先出。
并行
requests的主要缺点是同步的。调用requests.get("http://example.org")会阻塞程序,直到HTTP服务器完全答复为止。可以通过使用并发线程提供的线程池来缓解此问题。它允许以非常快速的方式并行化HTTP请求。
from concurrent import futures
import requests
with futures.ThreadPoolExecutor(max_workers=4) as executor:
futures = [
executor.submit(
lambda: requests.get("http://example.org"))
for _ in range(8)
]
results = [
f.result().status_code
for f in futures
]
print("Results: %s" % results)
也可以借助requests-futures的库:
来自requests_futures导入会话
from requests_futures import sessions
session = sessions.FuturesSession()
futures = [
session.get("http://example.org")
for _ in range(8)
]
results = [
f.result().status_code
for f in futures
]
print("Results: %s" % results)
在请求中使用期货
默认情况下,创建具有两个线程的工作程序,但是程序可以通过将max_workers参数甚至是自己的执行程序传递给FuturSession对象来轻松自定义此值,例如:FuturesSession(executor=ThreadPoolExecutor(max_workers=10))。
异步
如前所述,请求是完全同步的。这会在等待服务器回复时阻止应用程序,从而降低程序速度。在线程中发出HTTP请求是一种解决方案,但是线程确实有其自身的开销,这暗示着并行性,这并不是每个人总是很高兴在程序中看到的东西。
从版本3.5开始,Python使用异步将异步作为其核心。aiohttp库提供了一个基于asyncio之上的异步HTTP客户端。该库允许按顺序发送请求,但无需等待答复回来再发送新请求。与HTTP流水形成对比,aiohttp通过多个连接并行发送请求,避免了前面解释的排序问题。
# Author: 钉钉或微信pythontesting 技术支持qq群:630011153 144081101
import aiohttp
import asyncio
async def get(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return response
loop = asyncio.get_event_loop()
coroutines = [get("https://china-testing.github.io/") for _ in range(8)]
results = loop.run_until_complete(asyncio.gather(*coroutines))
print("Results: %s" % results)
所有这些解决方案都提供了不同的方法来提高HTTP客户端的速度。
性能
下面的代码向HTTPbin.org发送请求。本示例实现了上面列出的所有技术并对它们进行计时。
# Author: 钉钉或微信pythontesting 技术支持qq群:630011153 144081101
import contextlib
import time
import aiohttp
import asyncio
import requests
from requests_futures import sessions
URL = "http://httpbin.org/delay/1"
TRIES = 10
@contextlib.contextmanager
def report_time(test):
t0 = time.time()
yield
print("Time needed for `%s' called: %.2fs"
% (test, time.time() - t0))
with report_time("serialized"):
for i in range(TRIES):
requests.get(URL)
session = requests.Session()
with report_time("Session"):
for i in range(TRIES):
session.get(URL)
session = sessions.FuturesSession(max_workers=2)
with report_time("FuturesSession w/ 2 workers"):
futures = [session.get(URL)
for i in range(TRIES)]
for f in futures:
f.result()
session = sessions.FuturesSession(max_workers=TRIES)
with report_time("FuturesSession w/ max workers"):
futures = [session.get(URL)
for i in range(TRIES)]
for f in futures:
f.result()
async def get(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
await response.read()
loop = asyncio.get_event_loop()
with report_time("aiohttp"):
loop.run_until_complete(
asyncio.gather(*[get(URL)
for i in range(TRIES)]))
运行此程序将给出以下输出:
Time needed for `serialized' called: 12.12s
Time needed for `Session' called: 11.22s
Time needed for `FuturesSession w/ 2 workers' called: 5.65s
Time needed for `FuturesSession w/ max workers' called: 1.25s
Time needed for `aiohttp' called: 1.19s
使用Session对象并因此重新使用连接意味着节省了8%的时间。
如果您的系统和程序允许使用线程,那么最好使用它们来并行化请求。但是,线程有一些开销,而且不是轻量级的。他们需要创建,启动然后加入。
除非您仍在使用旧版本的Python,否则如果您想编写一个快速且异步的HTTP客户端,无疑使用aiohttp应该是当今的方式。它是最快,最具扩展性的解决方案,因为它可以处理数百个并行请求。替代方案是并行管理数百个线程不是一个好选择。
https://www.jianshu.com/p/832b11aecf69 深圳市网新新思软件有限公司 招聘 大数据测试
Streaming
另一个有效的速度优化是流式传输请求。发出请求时,默认情况下会立即下载响应的正文。请求库提供的流参数或aiohttp的content属性都提供了一种在执行请求后不立即将全部内容加载到内存中的方法。
import requests
# Use `with` to make sure the response stream is closed and the connection can
# be returned back to the pool.
with requests.get('http://example.org', stream=True) as r:
print(list(r.iter_content()))
用aiohttp流
import aiohttp
import asyncio
async def get(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.content.read()
loop = asyncio.get_event_loop()
tasks = [asyncio.ensure_future(get("https://china-testing.github.io/"))]
loop.run_until_complete(asyncio.wait(tasks))
print("Results: %s" % [task.result() for task in tasks])
为了避免无用地分配可能的数百兆内存,不加载全部内容非常重要。如果您的程序不需要整体访问整个内容,而是可以处理块,那么最好使用这些方法。例如,如果您要保存内容并将其写入文件,则仅读取一个块并同时写入它将比读取整个HTTP正文(分配大量的内存)具有更高的内存效率。 然后将其写入磁盘。
我希望这可以使您更轻松地编写适当的HTTP客户端和请求。如果您知道任何其他有用的技术或方法,请随时在下面的评论部分写下来!