Wie kann ich Anfragen in Asyncio verwenden?

126

Ich möchte parallele http-Anforderungsaufgaben ausführen asyncio, aber ich finde, dass python-requestsdies die Ereignisschleife von blockieren würde asyncio. Ich habe aiohttp gefunden, aber es konnte den Dienst der http-Anfrage nicht über einen http-Proxy bereitstellen.

Ich möchte also wissen, ob es eine Möglichkeit gibt, asynchrone http-Anfragen mit Hilfe von zu erledigen asyncio.

Flyer
quelle
1
Wenn Sie nur Anfragen senden, können Sie subprocessIhren Code parallel schalten.
WeaselFox
Diese Methode scheint nicht elegant ......
Flyer
Es gibt jetzt einen asynchronen Port von Anfragen. github.com/rdbhost/yieldfromRequests
Rdbhost

Antworten:

181

Um Anforderungen (oder andere blockierende Bibliotheken) mit asyncio zu verwenden, können Sie BaseEventLoop.run_in_executor verwenden , um eine Funktion in einem anderen Thread auszuführen und daraus zu ergeben, um das Ergebnis zu erhalten. Beispielsweise:

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())

Dadurch werden beide Antworten parallel erhalten.

Mit Python 3.5 können Sie die neue await/ asyncSyntax verwenden:

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())

Weitere Informationen finden Sie unter PEP0492 .

Christian
quelle
5
Können Sie erklären, wie genau das funktioniert? Ich verstehe nicht, wie das nicht blockiert.
Scott Coates
32
@christian, aber wenn es gleichzeitig in einem anderen Thread läuft, besiegt das nicht den Punkt von Asyncio?
Scott Coates
21
@scoarescoare Hier kommt der Teil "Wenn Sie es richtig machen" ins Spiel - die Methode, die Sie im Executor ausführen, sollte in sich geschlossen sein ((meistens) wie request.get im obigen Beispiel). Auf diese Weise müssen Sie sich nicht mit gemeinsam genutztem Speicher, Sperren usw. befassen, und die komplexen Teile Ihres Programms sind dank asyncio immer noch Single-Threaded.
Christian
5
@scoarescoare Der Hauptanwendungsfall ist die Integration in E / A-Bibliotheken, die Asyncio nicht unterstützen. Zum Beispiel arbeite ich mit einer wirklich alten SOAP-Schnittstelle und verwende die suds-jurko-Bibliothek als "am wenigsten schlechte" Lösung. Ich versuche es mit einem asyncio Server zu integrieren, so dass ich run_in_executor bin mit den blockierenden Schaum Anrufe in eine Art und Weise zu machen, sieht asynchron.
Lucretiel
10
Wirklich cool, dass dies funktioniert und daher für Legacy-Inhalte so einfach ist, aber es sollte betont werden, dass dies einen Betriebssystem-Threadpool verwendet und daher nicht wie aiohttp als echte asyncio-orientierte Bibliothek skaliert
jsalter
78

aiohttp kann bereits mit HTTP-Proxy verwendet werden:

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())
Gedächtnis Meister
quelle
Was macht der Stecker hier?
Markus Meskanen
Es bietet eine Verbindung über Proxy-Server
Mindmaster
16
Dies ist eine viel bessere Lösung als die Verwendung von Anforderungen in einem separaten Thread. Da es wirklich asynchron ist, hat es einen geringeren Overhead und eine geringere Mem-Nutzung.
Thom
14
für Python> = 3.5 ersetzen Sie @ asyncio.coroutine durch "async" und "Yield from" durch "await"
James
40

Die obigen Antworten verwenden immer noch die alten Coroutinen im Python 3.4-Stil. Folgendes würden Sie schreiben, wenn Sie Python 3.5+ hätten.

aiohttp unterstützt jetzt http-Proxy

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
quelle
1
Könntest du mit mehr URLs arbeiten? Es ist nicht sinnvoll, nur eine URL zu haben, wenn es um parallele http-Anfragen geht.
anonym
Legende. Danke dir! Funktioniert großartig
Adam
@ospider Wie kann dieser Code geändert werden, um beispielsweise 10.000 URLs mit 100 parallelen Anforderungen zu liefern? Die Idee ist, alle 100 Slots gleichzeitig zu nutzen und nicht darauf zu warten, dass 100 geliefert werden, um mit den nächsten 100 zu beginnen.
Antoan Milkov
@AntoanMilkov Das ist eine andere Frage, die im Kommentarbereich nicht beantwortet werden kann.
ospider
@ospider Sie haben Recht, hier ist die Frage: stackoverflow.com/questions/56523043/…
Antoan Milkov
11

Anfragen werden derzeit nicht unterstützt, asynciound es gibt keine Pläne, solche Unterstützung bereitzustellen. Es ist wahrscheinlich, dass Sie einen benutzerdefinierten "Transportadapter" (wie hier beschrieben ) implementieren , der weiß, wie man ihn verwendet asyncio.

Wenn ich mich mit etwas Zeit befinde, ist es etwas, das ich vielleicht tatsächlich untersuche, aber ich kann nichts versprechen.

Lukasa
quelle
Der Link führt zu einem 404.
CodeBiker
8

In einem Artikel von Pimin Konstantin Kefaloukos gibt es einen guten Fall von Async / Wait-Schleifen und Threading. Einfache parallele HTTP-Anforderungen mit Python und Asyncio :

Um die Gesamtabschlusszeit zu minimieren, können wir den Thread-Pool vergrößern, um ihn an die Anzahl der Anforderungen anzupassen, die wir stellen müssen. Zum Glück ist dies einfach, wie wir als nächstes sehen werden. Die folgende Codeliste ist ein Beispiel für das Erstellen von zwanzig asynchronen HTTP-Anforderungen mit einem Thread-Pool von zwanzig Arbeitsthreads:

# 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
quelle
2
Das Problem dabei ist, dass ich, wenn ich 10000 Anfragen mit 20 Executoren ausführen muss, warten muss, bis alle 20 Executoren fertig sind, um mit den nächsten 20 zu beginnen, oder? Ich kann es nicht tun, for i in range(10000)weil eine Anfrage fehlschlagen oder eine Zeitüberschreitung auftreten kann, oder?
Sanandrea
1
Können Sie bitte erklären, warum Sie Asyncio benötigen, wenn Sie dasselbe nur mit ThreadPoolExecutor tun können?
Asaf Pinhassi
@lya Rusin Basierend auf was setzen wir die Anzahl der max_workers? Hat es mit der Anzahl der CPUs und Threads zu tun?
Alt-F4