aiohttp基本及進階使用
客戶端使用
發起請求
讓我們從導入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
ClientSession.get()
的主要參數接受一個HTTP URL。
發起HTTP POST請求我們可以使用協程方法ClientSession.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‘)
註意:
不要為每個請求都創建一個會話。大多數情況下每個應用程序只需要一個會話就可以執行所有的請求。 每個會話對象都包含一個連接池,可復用的連接和持久連接狀態(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":{...
gzip
和defalte
傳輸編碼會自動解碼。 你也可以使其支持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類的實例。gzip
和deflate
傳輸編碼同樣會自動解碼。
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(請求信息),主要是url和headers信息。 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基本及進階使用