aiohttp 異步http請求-1.快速入門 get 請求示例


前言

在 python 的眾多 http 請求庫中,大家最熟悉的就是 requests 庫了,requests 庫上手非常容易,適合入門學習。
如果平常工作中對發請求不追求效率和並發的情況下,requests 完全可以滿足大部分需求。但是想發大量的請求,比如幾萬個請求的時候,可能需要等待幾個小時,requests 庫就不能滿足需求了。
初學者建議先學習requests 庫,熟練掌握requests 庫才能更好的學習 aiohttp 異步框架。

同步與異步

requests只能發送同步請求,aiohttp只能發送異步請求。
所謂的同步請求,是指在單進程單線程的代碼中,發起一次請求后,在收到返回結果之前,不能發起下一次請求。
所謂異步請求,是指在單進程單線程的代碼中,發起一次請求后,在等待網站返回結果的時間里,可以繼續發送更多請求。

在前面一篇中有講到python asyncio 異步 I/O - 實現並發http請求(asyncio + aiohttp)
如果使用requests 庫,發10個請求訪問我的博客,那么這10個請求是串行的。

import requests
import time

url = "https://www.cnblogs.com/yoyoketang/"

start_time = time.time()
for i in range(10):
    r = requests.get(url)
    print(r)
print('總耗時:', time.time()-start_time)

我們想實現並發請求需用到 異步http 庫 aiohttp。

環境准備

首先,確保 aiohttp 已安裝,為了更好的學習 aiohttp 的功能,建議大家使用python3.7+版本, 我用的是python3.8版本

pip install aiohttp==3.8.1

或者在 pycharm 中安裝

簡單get 請求實現

首先導入 aiohttp 模塊和 asyncio

import aiohttp
import asyncio

現在,讓我們嘗試獲取一個網頁。例如讓我們查詢 http://httpbin.org/get

import aiohttp
import asyncio


async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get('http://httpbin.org/get') as resp:
            print(resp.status)
            print(await resp.text())


# asyncio.run(main()) # 會報錯,改成下面2句
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

運行結果

200
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Host": "httpbin.org", 
    "User-Agent": "Python/3.8 aiohttp/3.8.1", 
    "X-Amzn-Trace-Id": "Root=1-625ed4ea-5f44f6163dc3521845687df1"
  }, 
  "origin": "183.193.27.228", 
  "url": "http://httpbin.org/get"
}

上面代碼解釋:

  • aiohttp.ClientSession() 是創建客戶端session會話對象
  • resp 是返回的response對象
  • session.get 發get請求
  • resp.status 獲取返回對象狀態碼
  • resp.text() 返回文本對象

前面是get請求示例,發一個 post 請求示例如下

session.post('http://httpbin.org/post', data=b'data')

其他 HTTP 方法也可用:

session.put('http://httpbin.org/put', data=b'data')
session.delete('http://httpbin.org/delete')
session.head('http://httpbin.org/get')
session.options('http://httpbin.org/get')
session.patch('http://httpbin.org/patch', data=b'data')

為了使對同一個站點的多個請求更簡單,可以使用構造函數的參數base_url ,例如請求不同的端點 http://httpbin.org 可以使用以下代碼:

import aiohttp
import asyncio


async def main():
    async with aiohttp.ClientSession('http://httpbin.org') as session:
        async with session.get('/get') as resp1:
            print(resp1.status)
        async with session.post('/post', data=b'data') as resp2:
            print(resp2.status)
        async with session.put('/put', data=b'data') as resp3:
            print(resp3.status)

# asyncio.run(main()) # 會報錯,改成下面2句
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

筆記

不要為每個請求創建會話。盡可能在每個應用程序中使用一個會話執行所有的請求。
更復雜的情況可能需要每個站點一個會話,例如一個用於 Github,另一個用於 Facebook API。無論如何,為每個請求創建一個會話是一個非常糟糕的主意。
會話內部包含一個連接池。連接重用和保持活動(默認情況下都打開)可以提高整體性能。

會話上下文管理器的使用不是強制性的,但在這種情況下應該調用方法,例如:await session.close()

session = aiohttp.ClientSession()
async with session.get('...'):
    # ...
await session.close()

在 URL 中傳遞參數

當url中帶請求參數時,如http://httpbin.org/get?key1=value1&key2=value2,在url中問號后面的參數可以單獨拿出來用鍵值對保存,使用 params關鍵字參數將這些參數作為 提供

import aiohttp
import asyncio


async def main():
    async with aiohttp.ClientSession() as session:
        params = {'key1': 'value1', 'key2': 'value2'}
        async with session.get('http://httpbin.org/get', params=params) as resp:
            expect = 'http://httpbin.org/get?key1=value1&key2=value2'
            print(resp.url)
            assert str(resp.url) == expect


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

運行打印結果:http://httpbin.org/get?key1=value1&key2=value2,通過打印 URL,您可以看到 URL 已正確編碼。

如果同一個key有多個值的時候,如:http://httpbin.org/get?key1=value1&key2=value2&key2=value3,其中key2對應的值有2個value2和value3。
那么在傳值的時候,可以把多個值放到一個list,如

params = {
            'key1': 'value1',
            'key2': ['value2', 'value3']
        }

也可以使用列表嵌套元祖

params = [
            ('key1', 'value1'),
            ('key2', 'value2'),
            ('key2', 'value3')
        ]

也可以把字符串內容作為參數傳遞, 如params='key=value+1&key2=value2'.

        async with session.get('http://httpbin.org/get',
                               params='key=value+1&key2=value2') as r:
            print(r.url)
            assert str(r.url) == 'http://httpbin.org/get?key=value+1&key2=value2'

但是這樣會有弊端,+號這種特殊字符沒轉碼成urlencoded格式%2B,所以這種方式盡量不用,以下是官方文檔把字符串內容作為參數傳遞的說明。

也可以把參數直接傳到url上,如http://httpbin.org/get?key1=value1&key2=value2

import aiohttp
import asyncio


async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get('http://httpbin.org/get?key1=value1&key2=value2') as resp:
            print(resp.url)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

當請求參數帶有中文的時候

params = {
            'key1': 'value1',
            'key2': '上海-悠悠'
        }

它會自動幫我們轉成urlencode編碼http://httpbin.org/get?key1=value1&key2=%E4%B8%8A%E6%B5%B7-%E6%82%A0%E6%82%A0

async def main():
    async with aiohttp.ClientSession() as session:
        params = {
            'key1': 'value1',
            'key2': '上海-悠悠'
        }
        async with session.get('http://httpbin.org/get', params=params) as resp:
            print(resp.url)

響應內容和狀態碼

我們可以讀取服務器響應的內容及其狀態碼

import aiohttp
import asyncio


async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://www.cnblogs.com/yoyoketang/') as resp:
            print(resp.url)
            print(await resp.text(encoding='utf-8'))
   
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

打印出內容:

https://www.cnblogs.com/yoyoketang/
<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="referrer" content="origin-when-crossorigin" />
    
    
    <meta http-equiv="Cache-Control" content="no-transform" />
    <meta http-equiv="Cache-Control" content="no-siteapp" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>上海-悠悠 - 博客園</title>

aiohttp自動解碼來自服務器的內容。您可以為該text()方法指定自定義編碼:

resp.text(encoding='utf-8')

也可以獲取byte響應內容

print(await resp.read())

打印結果

b'<!DOCTYPE html>\n<html lang="zh-cn">\n<head>\n    <meta charset="utf-8" />\n  ...'


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM