Asynchrone Anforderungen mit Python-Anforderungen


142

Ich habe das Beispiel in der Dokumentation der Anforderungsbibliothek für Python ausprobiert .

Mit async.map(rs)erhalte ich die Antwortcodes, möchte aber den Inhalt jeder angeforderten Seite abrufen. Dies funktioniert beispielsweise nicht:

out = async.map(rs)
print out[0].content

Vielleicht haben die Antworten, die Sie erhalten, einen leeren Körper?
Mariusz Jamro

Funktioniert bei mir. Bitte posten Sie den vollständigen Fehler, den Sie erhalten.
Chewie

Es liegt kein Fehler vor. Es läuft nur für immer durch die bereitgestellten Test-URLs.
Trbck

Es erscheint offensichtlich, wenn ich URLs über https verwende. http funktioniert gut
trbck

Sieht so aus, als ob requests-threadses jetzt existiert.
OrangeDog

Antworten:


154

Hinweis

Die folgende Antwort gilt nicht für Anfragen v0.13.0 +. Die asynchrone Funktionalität wurde nach dem Schreiben dieser Frage in Grequests verschoben . Allerdings konnte man nur ersetzen requestsmit grequestsunten und es sollte funktionieren.

Ich habe diese Antwort so belassen, wie sie ist, um die ursprüngliche Frage zu reflektieren, bei der es um die Verwendung von Anforderungen <v0.13.0 ging.


Um mehrere Aufgaben async.map asynchron auszuführen , müssen Sie:

  1. Definieren Sie eine Funktion für das, was Sie mit jedem Objekt (Ihrer Aufgabe) tun möchten.
  2. Fügen Sie diese Funktion als Ereignis-Hook in Ihre Anfrage ein
  3. Rufen Sie async.mapeine Liste aller Anfragen / Aktionen auf

Beispiel:

from requests import async
# If using requests > v0.13.0, use
# from grequests import async

urls = [
    'http://python-requests.org',
    'http://httpbin.org',
    'http://python-guide.org',
    'http://kennethreitz.com'
]

# A simple task to do to each response object
def do_something(response):
    print response.url

# A list to hold our things to do via async
async_list = []

for u in urls:
    # The "hooks = {..." part is where you define what you want to do
    # 
    # Note the lack of parentheses following do_something, this is
    # because the response will be used as the first argument automatically
    action_item = async.get(u, hooks = {'response' : do_something})

    # Add the task to our list of things to do via async
    async_list.append(action_item)

# Do our list of things to do via async
async.map(async_list)

2
Schöne Idee, Ihren Kommentar hinterlassen zu haben: Aufgrund von Kompatibilitätsproblemen zwischen den neuesten Anforderungen und Grequests (fehlende Option max_retries in Anforderungen 1.1.0) musste ich Anforderungen herunterstufen, um Async abzurufen, und ich habe festgestellt, dass die asynchrone Funktionalität mit Versionen 0.13+ verschoben wurde ( pypi.python.org/pypi/requests )
outforawhile

1
Dumme Frage: Was ist die Geschwindigkeitssteigerung bei der Verwendung von Grequests im Gegensatz zu einfachen Anfragen? Welche Grenzen gibt es für Anfragen? zB wäre es in Ordnung, 3500 Anfragen in async.map zu setzen?
Droope

10
from grequests import asyncfunktioniert nicht .. und diese Definition von etwas funktioniert für mich def do_something(response, **kwargs):, ich finde es von stackoverflow.com/questions/15594015/…
Allan Ruin

3
Wenn der Aufruf von async.map immer noch blockiert, wie ist dieser asynchrone Aufruf? Abgesehen davon, dass die Anforderungen selbst asynchron gesendet werden, ist der Abruf immer noch synchron?
Bryanph

3
Ersetzen from requests import asyncdurch import grequests as asyncarbeitete für mich.
Martin Thoma

79

asyncist jetzt ein eigenständiges Modul : grequests.

Siehe hier: https://github.com/kennethreitz/grequests

Und da: Ideale Methode zum Senden mehrerer HTTP-Anfragen über Python?

Installation:

$ pip install grequests

Verwendung:

Bauen Sie einen Stapel:

import grequests

urls = [
    'http://www.heroku.com',
    'http://tablib.org',
    'http://httpbin.org',
    'http://python-requests.org',
    'http://kennethreitz.com'
]

rs = (grequests.get(u) for u in urls)

Senden Sie den Stapel

grequests.map(rs)

Ergebnis sieht aus wie

[<Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>]

Grequests scheinen keine Einschränkung für gleichzeitige Anforderungen festzulegen, dh wenn mehrere Anforderungen an denselben Server gesendet werden.


11
In Bezug auf die Beschränkung gleichzeitiger Anforderungen können Sie beim Ausführen von map () / imap () eine Poolgröße angeben. dh grequests.map (rs, size = 20) für 20 gleichzeitige Grabs.
Synthesizerpatel

1
Derzeit ist dies nicht python3-fähig (gevent kann v2.6 nicht auf py3.4 erstellen).
Saarp

1
Ich verstehe den asynchronen Teil nicht ganz. Wenn ich results = grequests.map(rs)den Code nach dieser Zeile blockieren lasse , kann ich den asynchronen Effekt sehen?
Allan Ruin

47

Ich habe sowohl Anfragen-Futures als auch Grequests getestet . Grequests sind schneller, bringen aber Affen-Patches und zusätzliche Probleme mit Abhängigkeiten mit sich. Anfragen-Futures sind um ein Vielfaches langsamer als Grequests. Ich habe beschlossen, meine eigenen und einfach verpackten Anfragen in ThreadPoolExecutor zu schreiben, und es war fast so schnell wie Grequests, aber ohne externe Abhängigkeiten.

import requests
import concurrent.futures

def get_urls():
    return ["url1","url2"]

def load_url(url, timeout):
    return requests.get(url, timeout = timeout)

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

    future_to_url = {executor.submit(load_url, url, 10): url for url in     get_urls()}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            resp_err = resp_err + 1
        else:
            resp_ok = resp_ok + 1

Welche Art von Ausnahme ist hier möglich?
Langsamer Harry

Anfragen.Ausnahmen.Timeout
Hodza

2
Entschuldigung, ich verstehe Ihre Frage nicht. Nur eine URL in mehreren Threads verwenden? Nur ein Fall DDoS-Angriffe))
Hodza

1
Ich verstehe nicht, warum diese Antwort so viele positive Stimmen bekommen hat. Die OP-Frage betraf asynchrone Anforderungen. ThreadPoolExecutor führt Threads aus. Ja, Sie können Anfragen in mehreren Threads stellen, aber das wird niemals ein asynchrones Programm sein. Wie könnte es eine Antwort auf die ursprüngliche Frage sein?
Nagylzs

1
Eigentlich war die Frage, wie man URLs parallel lädt. Und ja, Thread Pool Executor ist nicht die beste Option, es ist besser, async io zu verwenden, aber es funktioniert gut in Python. Und ich verstehe nicht, warum Threads nicht für Async verwendet werden konnten? Was ist, wenn Sie eine CPU-gebundene Aufgabe asynchron ausführen müssen?
Hodza

29

Vielleicht ist Anfragen-Futures eine andere Wahl.

from requests_futures.sessions import FuturesSession

session = FuturesSession()
# first request is started in background
future_one = session.get('http://httpbin.org/get')
# second requests is started immediately
future_two = session.get('http://httpbin.org/get?foo=bar')
# wait for the first request to complete, if it hasn't already
response_one = future_one.result()
print('response one status: {0}'.format(response_one.status_code))
print(response_one.content)
# wait for the second request to complete, if it hasn't already
response_two = future_two.result()
print('response two status: {0}'.format(response_two.status_code))
print(response_two.content)

Es wird auch in empfohlen Office - Dokument . Wenn Sie nicht gevent einbeziehen möchten, ist es eine gute.


1
Eine der einfachsten Lösungen. Die Anzahl der gleichzeitigen Anforderungen kann erhöht werden, indem der Parameter max_workers definiert wird
Jose Cherian

1
Es wäre schön, ein Beispiel für diese Skalierung zu sehen, damit wir nicht einen Variablennamen pro Element zum Durchlaufen verwenden.
user1717828

Ein Thread pro Anfrage ist eine Verschwendung von Ressourcen! Es ist nicht möglich, zum Beispiel 500 Anfragen gleichzeitig zu erledigen, es wird Ihre CPU töten. Dies sollte niemals als gute Lösung angesehen werden.
Corneliu Maftuleac

@ CorneliuMaftuleac guter Punkt. In Bezug auf die Thread-Verwendung müssen Sie sich unbedingt darum kümmern, und die Bibliothek bietet eine Option zum Aktivieren des Threading-Pools oder des Verarbeitungspools. ThreadPoolExecutor(max_workers=10)
Dreampuf

@ Dreampuf Verarbeitungspool Ich glaube, ist noch schlimmer?
Corneliu Maftuleac

11

Ich habe viele Probleme mit den meisten Antworten - sie verwenden entweder veraltete Bibliotheken, die mit eingeschränkten Funktionen portiert wurden, oder bieten eine Lösung mit zu viel Magie bei der Ausführung der Anforderung, was die Fehlerbehandlung erschwert. Wenn sie nicht in eine der oben genannten Kategorien fallen, sind sie Bibliotheken von Drittanbietern oder veraltet.

Einige der Lösungen funktionieren nur in http-Anfragen, aber die Lösungen sind für jede andere Art von Anfrage nicht geeignet, was lächerlich ist. Eine hochgradig kundenspezifische Lösung ist hier nicht erforderlich.

Die einfache Verwendung der in Python integrierten Bibliothek asyncioreicht aus, um asynchrone Anforderungen jeglicher Art auszuführen und eine ausreichende Fluidität für die komplexe und benutzerspezifische Fehlerbehandlung bereitzustellen.

import asyncio

loop = asyncio.get_event_loop()

def do_thing(params):
    async def get_rpc_info_and_do_chores(id):
        # do things
        response = perform_grpc_call(id)
        do_chores(response)

    async def get_httpapi_info_and_do_chores(id):
        # do things
        response = requests.get(URL)
        do_chores(response)

    async_tasks = []
    for element in list(params.list_of_things):
       async_tasks.append(loop.create_task(get_chan_info_and_do_chores(id)))
       async_tasks.append(loop.create_task(get_httpapi_info_and_do_chores(ch_id)))

    loop.run_until_complete(asyncio.gather(*async_tasks))

Wie es funktioniert ist einfach. Sie erstellen eine Reihe von Aufgaben, die asynchron ausgeführt werden sollen, und fordern dann eine Schleife auf, diese Aufgaben auszuführen und nach Abschluss zu beenden. Keine zusätzlichen Bibliotheken, die nicht gewartet werden müssen, keine mangelnde Funktionalität erforderlich.


2
Wenn ich das richtig verstehe, blockiert dies die Ereignisschleife während des GRPC- und HTTP-Aufrufs. Wenn diese Aufrufe Sekunden dauern, wird Ihre gesamte Ereignisschleife für Sekunden blockiert. Um dies zu vermeiden, müssen Sie GRPC- oder HTTP-Bibliotheken verwenden async. Dann können Sie zum Beispiel tun await response = requests.get(URL). Nein?
Coder Nr. 23,

Leider stellte ich beim Ausprobieren fest, dass das Erstellen eines Wrappers requestskaum schneller (und in einigen Fällen langsamer) ist als das synchrone Aufrufen einer Liste von URLs. Das Anfordern eines Endpunkts, der 3 Sekunden benötigt, um 10 Mal mit der oben beschriebenen Strategie zu antworten, dauert etwa 30 Sekunden. Wenn Sie echte asyncLeistung wünschen , müssen Sie so etwas wie verwenden aiohttp.
DragonBobZ

8

Ich weiß, dass dies für eine Weile geschlossen wurde, aber ich dachte, es könnte nützlich sein, eine andere asynchrone Lösung zu bewerben, die auf der Anforderungsbibliothek basiert.

list_of_requests = ['http://moop.com', 'http://doop.com', ...]

from simple_requests import Requests
for response in Requests().swarm(list_of_requests):
    print response.content

Die Dokumente finden Sie hier: http://pythonhosted.org/simple-requests/


@YSY Fühlen Sie sich frei, ein Problem zu posten: github.com/ctheiss/simple-requests/issues ; Ich benutze diese Bibliothek buchstäblich tausende Male am Tag.
Monkey Boson

Boston, wie gehen Sie mit 404/500 Fehlern um? Was ist mit https-URLs? Ich werde ein Snipping zu schätzen wissen, das Tausende von URLs unterstützt. Können Sie bitte ein Beispiel einfügen? danke
YSY

@YSY Standardmäßig lösen 404/500 Fehler eine Ausnahme aus. Dieses Verhalten kann überschrieben werden (siehe pythonhosted.org/simple-requests/… ). HTTPS-URLs sind schwierig, da sie sich auf gevent verlassen, das derzeit einen herausragenden Fehler aufweist ( github.com/gevent/gevent/issues/477 ). Es ist eine Beilage im Ticket Sie ausführen können, aber es wird immer noch Warnungen für SNI - Server werfen (aber es wird funktionieren). Ich fürchte, alle meine Verwendungen sind in meiner Firma und geschlossen. Aber ich versichere Ihnen, dass wir Tausende von Anfragen über Dutzende von Jobs ausführen.
Monkey Boson

Die Bibliothek sieht in Bezug auf die Interaktion elegant aus. Ist Python3 + verwendbar? Entschuldigung konnte keine Erwähnung sehen.
Isaac Philip

@Jethro absolut richtig, die Bibliothek müsste komplett neu geschrieben werden, da die zugrunde liegenden Technologien in Python 3 sehr unterschiedlich sind. Derzeit ist die Bibliothek "vollständig", funktioniert aber nur für Python 2.
Monkey Boson

4
threads=list()

for requestURI in requests:
    t = Thread(target=self.openURL, args=(requestURI,))
    t.start()
    threads.append(t)

for thread in threads:
    thread.join()

...

def openURL(self, requestURI):
    o = urllib2.urlopen(requestURI, timeout = 600)
    o...

4
Dies sind "normale" Anforderungen in Threads. ist kein schlechtes Beispiel kaufen ist nicht zum Thema.
Nick



2

Es ist ein weiteres Paket mit dem Namen fgrequests verfügbar .

Es ist eine Alternative zu Grequests. Es ist schneller als grequests. Der Grund dafür fgrequestsist, dass Threading für gleichzeitige Anforderungen verwendet wird. Sie können Ihr eigenes Parallelitätslimit entsprechend der Leistung Ihres Systems festlegen. Es hat auch eine gute Dokumentation. Bietet auch fast alle Funktionen der Python- requestsBibliothek.

Ich hoffe, Ihnen wird das in beiden (features & speed)Szenarien gefallen .


1
Bitte geben Sie in Zukunft an, dass Sie der Autor des Pakets sind. Andernfalls verstoßen Sie gegen die Regeln für die Eigenwerbung von Stack Overflow. Weitere Informationen finden Sie unter So werden Sie kein Spammer .
Valentin Podkamennyi

2

Sie können dafür verwenden httpx.

import httpx

async def get_async(url):
    async with httpx.AsyncClient() as client:
        return await client.get(url)

urls = ["http://google.com", "http://wikipedia.org"]

# Note that you need an async context to use `await`.
await asyncio.gather(*map(get_async, urls))

Wenn Sie eine funktionale Syntax wünschen, wird diese von der Gamla Lib umschlossen get_async.

Dann können Sie tun


await gamla.map(gamla.get_async(10), ["http://google.com", "http://wikipedia.org"])

Das 10ist das Timeout in Sekunden.

(Haftungsausschluss: Ich bin sein Autor)


Und respxzum Verspotten / Testen :)
am

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.