1. aiohttp安裝html
pip install aiohttp
1.1. 基本請求用法python
async with aiohttp.get('https://github.com') as r: await r.text()
其中r.text(), 能夠在括號中指定解碼方式,編碼方式,例如nginx
await resp.text(encoding='windows-1251')
或者也能夠選擇不編碼,適合讀取圖像等,是沒法編碼的git
await resp.read()
2.發起一個session請求github
首先是導入aiohttp模塊:web
import aiohttp
而後咱們試着獲取一個web源碼,這裏以GitHub的公共Time-line頁面爲例:json
async with aiohttp.ClientSession() as session: async with session.get('https://api.github.com/events') as resp: print(resp.status) print(await resp.text())
上面的代碼中,咱們建立了一個 ClientSession 對象命名爲session,而後經過session的get方法獲得一個 ClientResponse 對象,命名爲resp,get方法中傳入了一個必須的參數url,就是要得到源碼的http url。至此便經過協程完成了一個異步IO的get請求。windows
有get請求固然有post請求,而且post請求也是一個協程:api
session.post('http://httpbin.org/post', data=b'data')
用法和get是同樣的,區別是post須要一個額外的參數data,便是須要post的數據。安全
除了get和post請求外,其餘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')
不要爲每次的鏈接都建立一次session,通常狀況下只須要建立一個session,而後使用這個session執行全部的請求。
每一個session對象,內部包含了一個鏈接池,而且將會保持鏈接和鏈接複用(默認開啓)能夠加快總體的性能。
3.在URL中傳遞參數
咱們常常須要經過 get 在url中傳遞一些參數,參數將會做爲url問號後面的一部分發給服務器。在aiohttp的請求中,容許以dict的形式來表示問號後的參數。舉個例子,若是你想傳遞 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 resp.url == 'http://httpbin.org/get?key2=value2&key1=value1'
能夠看到,代碼正確的執行了,說明參數被正確的傳遞了進去。不論是一個參數兩個參數,仍是更多的參數,均可以經過這種方式來傳遞。除了這種方式以外,還有另一個,使用一個 list 來傳遞(這種方式能夠傳遞一些特殊的參數,例以下面兩個key是相等的也能夠正確傳遞):
params = [('key', 'value1'), ('key', 'value2')] async with session.get('http://httpbin.org/get', params=params) as r: assert r.url == 'http://httpbin.org/get?key=value2&key=value1'
除了上面兩種,咱們也能夠直接經過傳遞字符串做爲參數來傳遞,可是須要注意,經過字符串傳遞的特殊字符不會被編碼:
async with session.get('http://httpbin.org/get', params='key=value+1') as r: assert r.url == 'http://httpbin.org/get?key=value+1'
4.響應的內容
仍是以GitHub的公共Time-line頁面爲例,咱們能夠得到頁面響應的內容:
async with session.get('https://api.github.com/events') as resp: print(await resp.text())
運行以後,會打印出相似於以下的內容
'[{"created_at":"2019-03-12T14:06:22Z","public":true,"actor":{...
resp的text方法,會自動將服務器端返回的內容進行解碼--decode,固然咱們也能夠自定義編碼方式:
await resp.text(encoding='gb2312'
除了text方法能夠返回解碼後的內容外,咱們也能夠獲得類型是字節的內容:
print(await resp.read())
運行的結果是:
b'[{"created_at":"2015-06-12T14:06:22Z","public":true,"actor":{...
gzip和deflate轉換編碼已經爲你自動解碼。
小記:
text(),read()方法是把整個響應體讀入內存,若是你是獲取大量的數據,請考慮使用」字節流「(streaming response)
5.特殊響應內容:json
若是咱們獲取的頁面的響應內容是json,aiohttp內置了更好的方法來處理json:
async with session.get('https://api.github.com/events') as resp: print(await resp.json())
若是由於某種緣由而致使resp.json()解析json失敗,例如返回不是json字符串等等,那麼resp.json()將拋出一個錯誤,也能夠給json()方法指定一個解碼方式:
print(await resp.json(encoding='gb2312'))
或者傳遞一個函數進去:
print(await resp.json( lambda(x:x.replace('a','b')) ))
6.以字節流的方式讀取響應內容
雖然json(),text(),read()很方便的能把響應的數據讀入到內存,可是咱們仍然應該謹慎的使用它們,由於它們是把整個的響應體所有讀入了內存。即便你只是想下載幾個字節大小的文件,但這些方法卻將在內存中加載全部的數據。因此咱們能夠經過控制字節數來控制讀入內存的響應內容:
async with session.get('https://api.github.com/events') as resp: await resp.content.read(10) #讀取前10個字節
通常地,咱們應該使用如下的模式來把讀取的字節流保存到文件中:
with open(filename, 'wb') as fd: while True: chunk = await resp.content.read(chunk_size) if not chunk: break fd.write(chunk)
7.自定義請求頭
若是你想添加請求頭,能夠像get添加參數那樣以dict的形式,做爲get或者post的參數進行請求:
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)
8.自定義Cookie
給服務器發送cookie,能夠經過給 ClientSession 傳遞一個cookie參數:
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」查看當前cookie,訪問session中的cookie請見第10節。
9.post數據的幾種方式
(1)模擬表單post數據
payload = {'key1': 'value1', 'key2': 'value2'} async with session.post('http://httpbin.org/post', data=payload) as resp: print(await resp.text())
注意:data=dict的方式post的數據將被轉碼,和form提交數據是同樣的做用,若是你不想被轉碼,能夠直接以字符串的形式 data=str 提交,這樣就不會被轉碼。
(2)post json
import json url = 'https://api.github.com/some/endpoint' payload = {'some': 'data'} async with session.post(url, data=json.dumps(payload)) as resp: ...
其實json.dumps(payload)返回的也是一個字符串,只不過這個字符串能夠被識別爲json格式
(3)post 小文件
url = 'http://httpbin.org/post' files = {'file': open('report.xls', 'rb')} await session.post(url, data=files)
能夠設置好文件名和content-type:
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)
若是將文件對象設置爲數據參數,aiohttp將自動以字節流的形式發送給服務器。
(4)post 大文件
aiohttp支持多種類型的文件以流媒體的形式上傳,因此咱們能夠在文件未讀入內存的狀況下發送大文件。
@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) # Then you can use `file_sender` as a data provider: async with session.post('http://httpbin.org/post', data=file_sender(file_name='huge_file')) as resp: print(await resp.text())
同時咱們能夠從一個url獲取文件後,直接post給另外一個url,並計算hash值:
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)
由於響應內容類型是StreamReader,因此能夠把get和post鏈接起來,同時進行post和get:
r = await session.get('http://python.org') await session.post('http://httpbin.org/post', data=r.content)
(5)post預壓縮數據
在經過aiohttp發送前就已經壓縮的數據, 調用壓縮函數的函數名(一般是deflate 或 zlib)做爲content-encoding的值:
async def my_coroutine(session, headers, my_data): data = zlib.compress(my_data) headers = {'Content-Encoding': 'deflate'} async with session.post('http://httpbin.org/post', data=data, headers=headers) pass
10.keep-alive, 鏈接池,共享cookie
ClientSession 用於在多個鏈接之間共享cookie:
sync with aiohttp.ClientSession() as session: await session.get( 'http://httpbin.org/cookies/set?my_cookie=my_value') filtered = session.cookie_jar.filter_cookies('http://httpbin.org') assert filtered['my_cookie'].value == 'my_value' async with session.get('http://httpbin.org/cookies') as r: json_body = await r.json() assert json_body['cookies']['my_cookie'] == 'my_value'
也能夠爲全部的鏈接設置共同的請求頭:
async with aiohttp.ClientSession( headers={"Authorization": "Basic bG9naW46cGFzcw=="}) as session: async with session.get("http://httpbin.org/headers") as r: json_body = await r.json() assert json_body['headers']['Authorization'] == \ 'Basic bG9naW46cGFzcw=='
ClientSession 還支持 keep-alive鏈接和鏈接池(connection pooling)
11.cookie安全性
默認ClientSession使用的是嚴格模式的 aiohttp.CookieJar. RFC 2109,明確的禁止接受url和ip地址產生的cookie,只能接受 DNS 解析IP產生的cookie。能夠經過設置aiohttp.CookieJar 的 unsafe=True 來配置:
jar = aiohttp.CookieJar(unsafe=True) session = aiohttp.ClientSession(cookie_jar=jar)
12.控制同時鏈接的數量(鏈接池)
也能夠理解爲同時請求的數量,爲了限制同時打開的鏈接數量,咱們能夠將限制參數傳遞給鏈接器:
conn = aiohttp.TCPConnector(limit=30)#同時最大進行鏈接的鏈接數爲30,默認是100,limit=0的時候是無限制
限制同時打開限制同時打開鏈接到同一端點的數量((host, port, is_ssl) 三的倍數),能夠經過設置 limit_per_host 參數:
conn = aiohttp.TCPConnector(limit_per_host=30)#默認是0
13.自定義域名解析
咱們能夠指定域名服務器的 IP 對咱們提供的get或post的url進行解析:
from aiohttp.resolver import AsyncResolver resolver = AsyncResolver(nameservers=["8.8.8.8", "8.8.4.4"]) conn = aiohttp.TCPConnector(resolver=resolver)
14.設置代理
aiohttp支持使用代理來訪問網頁:
async with aiohttp.ClientSession() as session: async with session.get("http://python.org", proxy="http://some.proxy.com") as resp: print(resp.status)
固然也支持須要受權的頁面:
async with aiohttp.ClientSession() as session: proxy_auth = aiohttp.BasicAuth('user', 'pass') async with session.get("http://python.org", proxy="http://some.proxy.com", proxy_auth=proxy_auth) as resp: print(resp.status)
或者經過這種方式來驗證受權:
session.get("http://python.org", proxy="http://user:pass@some.proxy.com")
15.響應狀態碼 response status code
能夠經過 resp.status來檢查狀態碼是否是200:
async with session.get('http://httpbin.org/get') as resp: assert resp.status == 200
16.響應頭
咱們能夠直接使用 resp.headers 來查看響應頭,獲得的值類型是一個dict:
>>> resp.headers {'ACCESS-CONTROL-ALLOW-ORIGIN': '*', 'CONTENT-TYPE': 'application/json', 'DATE': 'Tue, 15 Jul 2014 16:49:51 GMT', 'SERVER': 'gunicorn/18.0', 'CONTENT-LENGTH': '331', 'CONNECTION': 'keep-alive'}
或者咱們能夠查看原生的響應頭:
>>> resp.raw_headers ((b'SERVER', b'nginx'), (b'DATE', b'Sat, 09 Jan 2016 20:28:40 GMT'), (b'CONTENT-TYPE', b'text/html; charset=utf-8'), (b'CONTENT-LENGTH', b'12150'), (b'CONNECTION', b'keep-alive'))
17.查看cookie
url = 'http://example.com/some/cookie/setting/url' async with session.get(url) as resp: print(resp.cookies)
18.重定向的響應頭
若是一個請求被重定向了,咱們依然能夠查看被重定向以前的響應頭信息:
>>> resp = await session.get('http://example.com/some/redirect/') >>> resp <ClientResponse(http://example.com/some/other/url/) [200]> >>> resp.history (<ClientResponse(http://example.com/some/redirect/) [301]>,)
19.超時處理
默認的IO操做都有5分鐘的響應時間 咱們能夠經過 timeout 進行重寫:
async with session.get('https://github.com', timeout=60) as r: ...
若是 timeout=None 或者 timeout=0 將不進行超時檢查,也就是不限時長。