Jak mogę używać żądań w asyncio?

126

Chcę wykonywać równoległe zadania żądań http w programie asyncio, ale uważam, python-requestsże zablokowałoby to pętlę zdarzeń asyncio. Znalazłem aiohttp, ale nie mógł on obsłużyć żądania HTTP przez proxy http.

Dlatego chcę wiedzieć, czy istnieje sposób na wykonywanie asynchronicznych żądań http za pomocą asyncio.

ulotka
źródło
1
Jeśli wysyłasz tylko żądania, możesz użyć subprocessrównoległego kodu.
WeaselFox
Ta metoda nie wydaje się elegancka ……
ulotka
Istnieje teraz port asyncio żądań. github.com/rdbhost/yieldfromRequests
Rdbhost

Odpowiedzi:

181

Aby użyć żądań (lub innych bibliotek blokujących) z asyncio, możesz użyć BaseEventLoop.run_in_executor do uruchomienia funkcji w innym wątku i uzyskania wyniku z niej. Na przykład:

import asyncio
import requests

@asyncio.coroutine
def main():
    loop = asyncio.get_event_loop()
    future1 = loop.run_in_executor(None, requests.get, 'http://www.google.com')
    future2 = loop.run_in_executor(None, requests.get, 'http://www.google.co.uk')
    response1 = yield from future1
    response2 = yield from future2
    print(response1.text)
    print(response2.text)

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

Spowoduje to równoległe uzyskanie obu odpowiedzi.

W Pythonie 3.5 możesz użyć nowej await/ asyncskładni:

import asyncio
import requests

async def main():
    loop = asyncio.get_event_loop()
    future1 = loop.run_in_executor(None, requests.get, 'http://www.google.com')
    future2 = loop.run_in_executor(None, requests.get, 'http://www.google.co.uk')
    response1 = await future1
    response2 = await future2
    print(response1.text)
    print(response2.text)

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

Zobacz PEP0492 po więcej.

chrześcijanin
źródło
5
Czy możesz wyjaśnić, jak dokładnie to działa? Nie rozumiem, jak to nie blokuje.
Scott Coates,
32
@christian, ale jeśli działa równolegle w innym wątku, czy to nie pokonuje sensu asyncio?
Scott Coates,
21
@scoarescoare W tym miejscu pojawia się część „jeśli zrobisz to dobrze” - metoda, którą uruchamiasz w executorze, powinna być samodzielna ((głównie) jak request.get w powyższym przykładzie). W ten sposób nie musisz zajmować się pamięcią współdzieloną, blokowaniem itp., A złożone części twojego programu są nadal jednowątkowe dzięki asyncio.
chrześcijańska
5
@scoarescoare Głównym przypadkiem użycia jest integracja z bibliotekami IO, które nie obsługują asyncio. Na przykład, pracuję z naprawdę starym interfejsem SOAP i używam biblioteki suds-jurko jako „najmniej złego” rozwiązania. Próbuję zintegrować go z serwerem asyncio, więc używam run_in_executor, aby blokować wywołania suds w sposób, który wygląda asynchronicznie.
Lucretiel
10
Naprawdę fajne, że to działa i jest tak łatwe w przypadku starszych rzeczy, ale należy podkreślić, że używa on puli wątków systemu operacyjnego, więc nie skaluje się jako prawdziwa biblioteka zorientowana na asyncio, jak robi to
aiohttp
78

aiohttp może być już używany z proxy HTTP:

import asyncio
import aiohttp


@asyncio.coroutine
def do_request():
    proxy_url = 'http://localhost:8118'  # your proxy address
    response = yield from aiohttp.request(
        'GET', 'http://google.com',
        proxy=proxy_url,
    )
    return response

loop = asyncio.get_event_loop()
loop.run_until_complete(do_request())
Mistrz umysłu
źródło
Co tu robi złącze?
Markus Meskanen
Zapewnia połączenie przez serwer proxy
mindmaster
16
Jest to znacznie lepsze rozwiązanie niż używanie żądań w osobnym wątku. Ponieważ jest naprawdę asynchroniczny, ma mniejsze obciążenie i mniejsze zużycie pamięci.
Thom,
14
dla pythona> = 3.5 zamień @ asyncio.coroutine na „async”, a „yield from” na „await”
James
40

Powyższe odpowiedzi nadal wykorzystują stare procedury w stylu Pythona 3.4. Oto, co byś napisał, gdybyś miał Pythona 3.5+.

aiohttp obsługuje teraz proxy http

import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    urls = [
            'http://python.org',
            'https://google.com',
            'http://yifei.me'
        ]
    tasks = []
    async with aiohttp.ClientSession() as session:
        for url in urls:
            tasks.append(fetch(session, url))
        htmls = await asyncio.gather(*tasks)
        for html in htmls:
            print(html[:100])

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
ospider
źródło
1
czy mógłbyś rozwinąć więcej adresów URL? Nie ma sensu mieć tylko jednego adresu URL, gdy pytanie dotyczy równoległego żądania http.
anonimowy
Legenda. Dziękuję Ci! Działa świetnie
Adam
@ospider Jak można zmodyfikować ten kod, aby dostarczał, powiedzmy, 10 tys. adresów URL przy użyciu 100 żądań równolegle? Chodzi o to, aby wykorzystać wszystkie 100 miejsc jednocześnie, a nie czekać na dostarczenie 100, aby rozpocząć następne 100.
Antoan Milkov
@AntoanMilkov To inne pytanie, na które nie można odpowiedzieć w obszarze komentarzy.
ospider
@ospider Masz rację, oto pytanie: stackoverflow.com/questions/56523043/…
Antoan Milkov
11

Żądania obecnie nie są obsługiwane asyncioi nie ma planów zapewnienia takiego wsparcia. Prawdopodobnie można zaimplementować niestandardowy „adapter transportowy” (omawiany tutaj ), który wie, jak go używać asyncio.

Jeśli znajdę się przez jakiś czas, to może rzeczywiście zajrzę, ale nie mogę nic obiecać.

Lukasa
źródło
Link prowadzi do 404.
CodeBiker
8

Istnieje dobry przypadek pętli async / await i wątków w artykule Pimin Konstantin Kefaloukos Easy równoległe żądania HTTP z Pythonem i asyncio :

Aby zminimalizować całkowity czas ukończenia, możemy zwiększyć rozmiar puli wątków, aby dopasować ją do liczby żądań, które musimy wykonać. Na szczęście jest to łatwe, jak zobaczymy dalej. Poniższy kod jest przykładem tego, jak wykonać dwadzieścia asynchronicznych żądań HTTP z pulą wątków złożoną z dwudziestu wątków roboczych:

# Example 3: asynchronous requests with larger thread pool
import asyncio
import concurrent.futures
import requests

async def main():

    with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:

        loop = asyncio.get_event_loop()
        futures = [
            loop.run_in_executor(
                executor, 
                requests.get, 
                'http://example.org/'
            )
            for i in range(20)
        ]
        for response in await asyncio.gather(*futures):
            pass


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Ilya Rusin
źródło
2
Problem polega na tym, że jeśli muszę uruchomić 10000 żądań z fragmentami po 20 executorów, muszę poczekać, aż wszystkie 20 executorów zakończy pracę, aby rozpocząć od następnych 20, prawda? Nie mogę tego zrobić, for i in range(10000)ponieważ jedno żądanie może się nie powieść lub przekroczyć limit czasu, prawda?
Sanandrea
1
Czy możesz wyjaśnić, dlaczego potrzebujesz asyncio, skoro możesz zrobić to samo, używając tylko ThreadPoolExecutor?
Asaf Pinhassi
@lya Rusin Na podstawie czego ustalamy liczbę max_workers? Czy ma to związek z liczbą procesorów i wątków?
alt-f4