aiohttp簡易使用チュートリアル

17114 ワード

0.はじめに
本文はaiohttpの公式ドキュメントから翻訳して、もし漏れがあれば、指摘を歓迎します.
aiohttpはサーバ側とクライアント側に分かれており,本稿ではクライアントのみを紹介する.
コンテキストのため、要求コードは非同期の関数で実行する必要があります.
async def fn():
pass
 
1.aiohttpインストール
 
pip install aiohttp
 
 
1.1. 基本リクエストの使用方法
 
 
async with aiohttp.request('GET','https://github.com') as r:
        await r.text()

其中r.text(), 可以在括号中指定解码方式,编码方式,例如

await resp.text(encoding='windows-1251')

 
あるいは符号化しない、画像の読み取りに適しているなど、符号化できないものを選択してもよい
await resp.read()

 
#    ,       

import aiohttp, asyncio

async def main():#aiohttp           
    async with aiohttp.request('GET', 'https://api.github.com/events') as resp:
        json = await resp.json()
        print(json)

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

------------------------------------------------------------------------------
#    ,      

import aiohttp, asyncio

async def main():#aiohttp           
    tasks = []
    [tasks.append(fetch('https://api.github.com/events?a={}'.format(i))) for i in range(10)]#    
    await asyncio.wait(tasks)

async def fetch(url):
    async with aiohttp.request('GET', url) as resp:
        json = await resp.json()
        print(json)    

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
------------------------------------------------------------------------------
#    ,      ,          

import aiohttp, asyncio

async def main(pool):#aiohttp           
    tasks = []
    sem = asyncio.Semaphore(pool)#         
    [tasks.append(control_sem(sem, 'https://api.github.com/events?a={}'.format(i))) for i in range(10)]#    
    await asyncio.wait(tasks)

async def control_sem(sem, url):#     
    async with sem:
        await fetch(url)

async def fetch(url):
    async with aiohttp.request('GET', url) as resp:
        json = await resp.json()
        print(json)    

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

上記の例では、コパスを正しく使用して要求することができますが、aiohttp自体の理由でUnclosed clientセッションの警告が表示されます.公式ではaiohttpの使用は推奨されていません.requestの方式は要求して、aiohttp.requestをaiohttpに変更します.ClientSession(**kw).requestの方法でいいです. 
具体的には2.セッションリクエストを開始
 
2.セッションリクエストの開始
 
まずaiohttpモジュールをインポートします.
 
import aiohttp

次に、GitHubのpublic Time-lineページを例に、Webソースを取得してみましょう.
 
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メソッドによってrespと命名されたClientResponseオブジェクトを取得します.getメソッドには、ソースコードを取得するhttp urlという必須パラメータurlが入力されます.これで、非同期IOのgetリクエストがコヒーレントに完了します.
 
getリクエストがあればもちろんpostリクエストがあり、postリクエストも協力です.
 
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')

メモ:
 
接続ごとにセッションを作成しないでください.一般的には、セッションを作成し、このセッションを使用してすべてのリクエストを実行するだけです.
各セッションオブジェクトには、内部に接続プールが含まれており、接続と接続の多重化(デフォルトでオン)を維持することで、全体的なパフォーマンスを向上させることができます.
#    

import aiohttp, asyncio

async def main(pool):#  
    sem = asyncio.Semaphore(pool)
    async with aiohttp.ClientSession() as session:#      ,     session
        tasks = []
        [tasks.append(control_sem(sem, 'https://api.github.com/events?a={}'.format(i), session)) for i in range(10)]#    
        await asyncio.wait(tasks)

async def control_sem(sem, url, session):#     
    async with sem:
        await fetch(url, session)

async def fetch(url, session):#      
    async with session.get(url) as resp:
        json = await resp.json()
        print(json)

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

スピードが速くて要らない
 
3.URLにパラメータを渡す
 
getを介してurlにいくつかのパラメータを渡す必要があります.パラメータはurl疑問符の後ろの一部としてサーバに送信されます.aiohttpのリクエストでは、疑問符後のパラメータをdict形式で表すことができます.例えば、key 1=value 1 key 2=value 2を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'

コードが正しく実行され、パラメータが正しく伝達されたことがわかります.1つのパラメータと2つのパラメータにかかわらず、より多くのパラメータにかかわらず、この方法で伝達することができます.この方法に加えて、listを使用して伝達するもう1つの方法があります(この方法は、次の2つの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'

上記の2つに加えて、パラメータとして文字列を直接渡すこともできますが、文字列を介して渡される特殊な文字は符号化されません.
 
 
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":"2015-06-12T14:06:22Z","public":true,"actor":{...

respのtextメソッドでは、サーバ側から返されたコンテンツを自動的に復号します.もちろん、符号化方法をカスタマイズすることもできます.
 
 
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.カスタムクッキー
 
サーバーにクッキーを送信し、ClientSessionにクッキーパラメータを渡すことができます.
 
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:
    ...

実は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からファイルを取得した後、別のurlに直接postし、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を介して送信される前に圧縮されたデータは、content-encodingの値として圧縮関数の関数名(通常はdeflateまたはzlib)を呼び出す.
 
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、接続プール、共有クッキー
 
ClientSessionは、複数の接続間でクッキーを共有するために使用されます.
 
async 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.クッキーのセキュリティ
 
デフォルトClientSessionは厳格なモードのaiohttpを使用しています.CookieJar. RFC 2109は、urlとipアドレスで生成されたクッキーの受信を明確に禁止し、DNS解析IPで生成されたクッキーのみを受信する.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)の3の倍数)を同時に開く制限を制限し、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は、プロキシを使用してWebページにアクセスすることをサポートします.
 
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:[email protected]")

 
 
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.クッキーの表示
 
 
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

>>> resp.history
(,)

 
 
19.タイムアウト処理
 
デフォルトのIO操作には5分間の応答時間があります.timeoutで書き換えることができます.
 
async with session.get('https://github.com', timeout=60) as r:
    ...

timeout=Noneまたはtimeout=0の場合、タイムアウトチェックは行われません.つまり、時間の長さは制限されません.