Python調用aiohttp

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 將不進行超時檢查,也就是不限時長。

相關文章
相關標籤/搜索