1. 程式人生 > >aiohttp基本及進階使用

aiohttp基本及進階使用

section 應用程序 持久 參數 serialize 同時 執行 sync 有一個

客戶端使用

發起請求

讓我們從導入aiohttp模塊開始:

import aiohttp

好啦,我們來嘗試獲取一個web頁面。比如我們來獲取下GitHub的時間軸。

async with aiohttp.ClientSession() as session:
    async with session.get(https://api.github.com/events) as resp:
        print(resp.status)
        print(await resp.text())

我們現在有了一個會話(session)對象,由ClientSession對象賦值而來,還有一個變量resp

,它其實是ClientResponse對象。我們可以從這個響應對象中獲取我們任何想要的信息。協程方法ClientSession.get()的主要參數接受一個HTTP URL。

發起HTTP POST請求我們可以使用協程方法ClientSession.post():

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

其他的HTTP方法也同樣支持:

session.put(http://httpbin.org/put, data=bdata)
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=bdata)

註意:

不要為每個請求都創建一個會話。大多數情況下每個應用程序只需要一個會話就可以執行所有的請求。 每個會話對象都包含一個連接池,可復用的連接和持久連接狀態(keep-alives,這兩個是默認的)可提升總體的執行效率。

發起JSON請求:

每個會話的請求方法都可接受json參數。

async with aiohttp.ClientSession() as session:
    async with session.post(json
={test: object})

默認情況下會話(session)使用Python標準庫裏的json模塊解析json信息。但還可使用其他的json解析器。可以給ClientSession指定json_serialize參數來實現:

import ujson

async with aiohttp.ClientSession(json_serialize=ujson.dumps) as session:
    async with session.post(json={test: object})

傳遞URL中的參數:

你可能經常想在URL中發送一系列的查詢信息。如果你手動構建他們,這些信息會以鍵值對的形式出現在?後面,比如: httpbin.org/get?key=val。請求對象允許你使用dict(字典,python中的數據類型)發送它們,使用params參數即可。例如: 如果你要把 key1=value1,key2=value2放到httpbin.org/get後面,你可以用下面的方式:

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

看,URL已經被正確的編碼啦。 同鍵不同值的並聯字典(MultiDict) 也同樣支持。 可使用帶有兩個tuples(元組,python中的數據類型)的list(列表,python中的數據類型)來構建:

params = [(key, value1), (key, value2)]
async with session.get(http://httpbin.org/get,
                       params=params) as r:
    assert str(r.url) == http://httpbin.org/get?key=value2&key=value1

同樣也允許你傳遞str(字符串)給params,但要小心一些不能被編碼的字符。就是一個不能被編碼的字符:

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

註意:

aiohttp會在發送請求前標準化URL。 域名部分會用IDNA 編碼,路徑和查詢條件會重新編譯(requoting)。 比如:URL(‘http://example.com/путь%30?a=%31‘)會被轉化為URL(‘http://example.com/%D0%BF%D1%83%D1%82%D1%8C/0?a=1‘) 如果服務器需要接受準確的表示並不要求編譯URL,那標準化過程應是禁止的。 禁止標準化可以使用encoded=True:

await session.get(URL(http://example.com/%30, encoded=True))

警告:

傳遞params時不要用encode=True,這倆參數不能同時使用。

獲取響應內容

我們可以讀取服務器的響應內容。想想我們獲取GitHub時間軸的例子:

async with session.get(https://api.github.com/events) as resp:
    print(await resp.text())

這樣會打印出類似於下面的信息:

[{"created_at":"2015-06-12T14:06:22Z","public":true,"actor":{...

aiohttp將會自動解碼內容。你可以為text()方法指定編碼(使用encoding參數):

await resp.text(encoding=windows-1251)

獲取二進制響應內容

你也可以以字節形式獲取響應,這樣得到的就不是文本了:

print(await resp.read())
b[{"created_at":"2015-06-12T14:06:22Z","public":true,"actor":{...

gzipdefalte傳輸編碼會自動解碼。 你也可以使其支持brotli傳輸編碼的解碼,只需安裝brotlipy即可。

獲取JSON響應內容

以防你需要處理JSON數據,內置了一個JSON解碼器:

async with session.get(https://api.github.com/events) as resp:
    print(await resp.json())

如果JSON解碼失敗,json()方法將會拋出一個異常。你還可以在調用json()時指定編碼器和解碼器函數。

註意:

這些方法會讀出內存中所有響應的內容。如果你要讀非常多的數據,考慮使用流式響應方法進行讀取。請看之後的文檔。

獲取流式響應內容

read(), json(), text()等方法使用起來很方便,但也要註意謹慎地使用。上述方法會將所有的響應內容加載到內存。舉個例子,如果你要下載幾個G的文件,這些方法還是會將所有內容都加載到內存,內存會表示"臣妾做不到啊~"(如果內存不夠的話)。作為代替你可以用content屬性。content其實是 aiohttp.StreamReader類的實例。gzipdeflate傳輸編碼同樣會自動解碼。

async with session.get(https://api.github.com/events) as resp:
    await resp.content.read(10)

一般情況下你可以使用下列模式將內容保存在一個文件中:

with open(filename, wb) as fd:
    while True:
        chunk = await resp.content.read(chunk_size)
        if not chunk:
            break
        fd.write(chunk)

在使用content讀了數據後,就不要在用read(), json(), text()了。

獲取請求信息

ClientResponse(客戶端響應)對象含有request_info(請求信息),主要是urlheaders信息。 raise_for_status結構體上的信息會被復制給ClientResponseError實例。

自定義Headers

如果你需要給某個請求添加HTTP頭,可以使用headers參數,傳遞一個dict對象即可。 比如,如果你想給之前的例子指定 content-type可以這樣:

import json
url = https://api.github.com/some/endpoint
payload = {some: data}
headers = {content-type: application/json}

await session.post(url,
                   data=json.dumps(payload),
                   headers=headers)

自定義Cookies

發送你自己的cookies給服務器,你可以為ClientSession對象指定cookies參數:

url = http://httpbin.org/cookies
cookies = {cookies_are: working}
async with ClientSession(cookies=cookies) as session:
    async with session.get(url) as resp:
        assert await resp.json() == {
           "cookies": {"cookies_are": "working"}}

註意:

訪問httpbin.org/cookies 會看到以JSON形式返回的cookies。查閱會話中的cookies請看ClientSession.cookie_jar。

發起更復雜的POST請求

一般來說,如果你想以表單形式發送一些數據 - 就像HTML表單。那麽只需要簡單的將一個dict通過data參數傳遞就可以。傳遞的dict數據會自動編碼:

payload = {key1: value1, key2: value2}
async with session.post(http://httpbin.org/post,
                        data=payload) as resp:
    print(await resp.text())
{
  ...
  "form": {
    "key2": "value2",
    "key1": "value1"
  },
  ...
}

如果你想發送非表單形式的數據你可用str(字符串)代替dict(字典)。這些數據會直接發送出去。 例如,GitHub API v3 接受JSON編碼POST/PATCH數據:

import json
url = https://api.github.com/some/endpoint
payload = {some: data}

async with session.post(url, data=json.dumps(payload)) as resp:
    ...

發送多部分編碼文件(Multipart-Encoded)

上傳多部分編碼文件:

url = http://httpbin.org/post
files = {file: open(report.xls, rb)}

await session.post(url, data=files)

你也可以顯式地設置文件名,文件類型:

url = http://httpbin.org/post
data = FormData()
data.add_field(file,
               open(report.xls, rb),
               filename=report.xls,
               content_type=application/vnd.ms-excel)

await session.post(url, data=data)

如果你把一個文件對象傳遞給data參數,aiohttp會自動將其以流的形式上傳。查看StreamReader以獲取支持的格式信息。

流式上傳

aiohttp 支持多種形式的流式上傳,允許你直接發送大文件而不必讀到內存。

下面是個簡單的例子,提供類文件對象即可:

with open(massive-body, rb) as f:
   await session.post(http://httpbin.org/post, data=f)

或者你也可以使用aiohttp.streamer對象:

@aiohttp.streamer
def file_sender(writer, file_name=None):
    with open(file_name, rb) as f:
        chunk = f.read(2**16)
        while chunk:
            yield from writer.write(chunk)
            chunk = f.read(2**16)

# 之後你可以使用’file_sender‘傳遞給data:

async with session.post(http://httpbin.org/post,
                        data=file_sender(file_name=huge_file)) as resp:
    print(await resp.text())

同樣可以使用StreamReader對象.

我們來看下如何把來自於另一個請求的內容作為文件上傳並計算其SHA1值:

async def feed_stream(resp, stream):
    h = hashlib.sha256()

    while True:
        chunk = await resp.content.readany()
        if not chunk:
            break
        h.update(chunk)
        stream.feed_data(chunk)

    return h.hexdigest()

resp = session.get(http://httpbin.org/post)
stream = StreamReader()
loop.create_task(session.post(http://httpbin.org/post, data=stream))

file_hash = await feed_stream(resp, stream)

因為響應對象的content屬性是一個StreamReader實例,所以你可以將get和post請求連在一起用:

r = await session.get(http://python.org)
await session.post(http://httpbin.org/post,
                   data=r.content)

aiohttp基本及進階使用