在武漢,房子裡待著,不出去影響世界了,轉載點文章。
在 Python 眾多的 HTTP 客戶端中,最有名的莫過於requests、aiohttp和httpx。在不藉助其他第三方庫的情況下,requests只能發送同步請求;aiohttp只能發送異步請求;httpx既能發送同步請求,又能發送異步請求。
所謂的同步請求,是指在單進程單線程的代碼中,發起一次請求後,在收到返回結果之前,不能發起下一次請求。所謂異步請求,是指在單進程單線程的代碼中,發起一次請求後,在等待網站返回結果的時間裡,可以繼續發送更多請求。
今天我們來一個淺度測評,僅僅以多次發送 POST 請求這個角度來對比這三個庫的性能。
測試使用的 HTTP 服務地址為http://122.51.39.219:8000/query,向它發送 POST 請求的格式如下圖所示:
請求發送的 ts 欄位日期距離今天大於10天,那麼返回{"success": false},如果小於等於10天,那麼返回{"success": true}。
首先我們通過各個客戶端使用相同的參數只發送一次請求,看看效果。
發送一次請求
requests
import requestsresp = requests.post('http://122.51.39.219:8000/query', json={'ts': '2020-01-20 13:14:15'}).json()print(resp)運行效果如下圖所示:
httpx
使用 httpx 發送同步請求:
import httpxresp = httpx.post('http://122.51.39.219:8000/query', json={'ts': '2020-01-20 13:14:15'}).json()print(resp)httpx 的同步模式與 requests 代碼重合度99%,只需要把requests改成httpx即可正常運行。如下圖所示:
使用 httpx 發送異步請求:
import httpximport asyncioasyncdefmain():asyncwith httpx.AsyncClient() as client: resp = await client.post('http://122.51.39.219:8000/query', json={'ts': '2020-01-20 13:14:15'}) result = resp.json() print(result)asyncio.run(main())運行效果如下圖所示:
aiohttp
import aiohttpimport asyncioasyncdefmain():asyncwith aiohttp.ClientSession() as client: resp = await client.post('http://122.51.39.219:8000/query', json={'ts': '2020-01-20 13:14:15'}) result = await resp.json() print(result)asyncio.run(main())運行效果如下圖所示:
aiohttp 的代碼與 httpx 異步模式的代碼重合度90%,只不過把AsyncClient換成了ClientSession,另外,在使用 httpx 時,當你await client.post時就已經發送了請求。但是當使用aiohttp時,只有在awiat resp.json() 時才會真正發送請求。
發送100次請求
我們現在隨機生成一個距離今天在5-15天的日期,發送到 HTTP接口中。如果日期距離今天超過10天,那麼返回的數據的 False,如果小於等於10天,那麼返回的數據是 True。
我們發送100次請求,計算總共耗時。
requests
在前幾天的文章中,我們提到,使用requests.post每次都會創建新的連接,速度較慢。而如果首先初始化一個 Session,那麼 requests 會保持連接,從而大大提高請求速度。所以在這次測評中,我們分別對兩種情況進行測試。
不保持連接
import randomimport timeimport datetimeimport requestsdefmake_request(body): resp = requests.post('http://122.51.39.219:8000/query', json=body) result = resp.json() print(result)defmain(): start = time.time()for _ in range(100): now = datetime.datetime.now() delta = random.randint(5, 15) ts = (now - datetime.timedelta(days=delta)).strftime('%Y-%m-%d %H:%M:%S') make_request({'ts': ts}) end = time.time() print(f'發送100次請求,耗時:{end - start}')if __name__ == '__main__': main()運行效果如下圖所示:
發送100次請求,requests 不保持連接時耗時2.7秒
保持連接
對代碼稍作修改,使用同一個 Session 發送請求:
import randomimport timeimport datetimeimport requestsdefmake_request(session, body): resp = session.post('http://122.51.39.219:8000/query', json=body) result = resp.json() print(result)defmain(): session = requests.Session() start = time.time()for _ in range(100): now = datetime.datetime.now() delta = random.randint(5, 15) ts = (now - datetime.timedelta(days=delta)).strftime('%Y-%m-%d %H:%M:%S') make_request(session, {'ts': ts}) end = time.time() print(f'發送100次請求,耗時:{end - start}')if __name__ == '__main__': main()運行效果如下圖所示:
發送100次請求,requests 保持連接耗時1.4秒
httpx
同步模式
代碼如下:
import randomimport timeimport datetimeimport httpxdefmake_request(client, body): resp = client.post('http://122.51.39.219:8000/query', json=body) result = resp.json() print(result)defmain(): session = httpx.Client() start = time.time()for _ in range(100): now = datetime.datetime.now() delta = random.randint(5, 15) ts = (now - datetime.timedelta(days=delta)).strftime('%Y-%m-%d %H:%M:%S') make_request(session, {'ts': ts}) end = time.time() print(f'發送100次請求,耗時:{end - start}')if __name__ == '__main__': main()運行效果如下圖所示:
發送100次請求,httpx 同步模式耗時1.5秒左右。
異步模式
代碼如下:
import httpximport randomimport datetimeimport asyncioimport timeasyncdefrequest(client, body): resp = await client.post('http://122.51.39.219:8000/query', json=body) result = resp.json() print(result)asyncdefmain():asyncwith httpx.AsyncClient() as client: start = time.time() task_list = []for _ in range(100): now = datetime.datetime.now() delta = random.randint(5, 15) ts = (now - datetime.timedelta(days=delta)).strftime('%Y-%m-%d %H:%M:%S') req = request(client, {'ts': ts}) task = asyncio.create_task(req) task_list.append(task)await asyncio.gather(*task_list) end = time.time() print(f'發送100次請求,耗時:{end - start}')asyncio.run(main())運行效果如下圖所示:
發送100次請求,httpx 異步模式耗時0.6秒左右。
aiohttp
測試代碼如下:
import aiohttpimport randomimport datetimeimport asyncioimport timeasyncdefrequest(client, body): resp = await client.post('http://122.51.39.219:8000/query', json=body) result = await resp.json() print(result)asyncdefmain():asyncwith aiohttp.ClientSession() as client: start = time.time() task_list = []for _ in range(100): now = datetime.datetime.now() delta = random.randint(5, 15) ts = (now - datetime.timedelta(days=delta)).strftime('%Y-%m-%d %H:%M:%S') req = request(client, {'ts': ts}) task = asyncio.create_task(req) task_list.append(task)await asyncio.gather(*task_list) end = time.time() print(f'發送100次請求,耗時:{end - start}')asyncio.run(main())運行效果如下圖所示:
發送100次請求,使用 aiohttp 耗時0.3秒左右
發送1000次請求
由於 request 保持連接的速度比不保持連接快,所以我們這裡只用保持連接的方式來測試。並且不列印返回的結果。
requests
運行效果如下圖所示:
發送1000次請求,requests 耗時16秒左右
httpx
同步模式
運行效果如下圖所示:
發送1000次請求,httpx 同步模式耗時18秒左右
異步模式
運行效果如下圖所示:
發送1000次請求,httpx 異步模式耗時5秒左右
aiohttp
運行效果如下圖所示:
發送1000次請求,aiohttp 耗時4秒左右
總結
如果你只發幾條請求。那麼使用 requests 或者 httpx 的同步模式,代碼最簡單。
如果你要發送很多請求,但是有些地方要發送同步請求,有些地方要發送異步請求,那麼使用 httpx 最省事。
如果你要發送很多請求,並且越快越好,那麼使用 aiohttp 最快。
這篇測評文章只是一個非常淺度的評測,只考慮了請求速度這一個角度。如果你要在生產環境使用,那麼你可以做更多實驗來看是不是符合你的實際使用情況。