Żądania asynchroniczne z żądaniami Pythona


142

Wypróbowałem próbkę dostarczoną w dokumentacji biblioteki żądań dla Pythona.

W przypadku async.map(rs)otrzymuję kody odpowiedzi, ale chcę uzyskać zawartość każdej żądanej strony. To na przykład nie działa:

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

Może odpowiedzi, które otrzymujesz, mają puste ciało?
Mariusz Jamro

Pracuje dla mnie. Opublikuj pełny komunikat o błędzie.
Chewie

nie ma błędu. po prostu działa wiecznie przez dostarczone testowe adresy URL.
trbck

oczywiście pojawia się, gdy używam adresów URL przez https. http działa dobrze
trbck

Wygląda na to, że requests-threadsistnieje teraz.
OrangeDog

Odpowiedzi:


154

Uwaga

Poniższa odpowiedź nie dotyczy żądań w wersji 0.13.0 +. Funkcjonalność asynchroniczna została przeniesiona do grequestów po napisaniu tego pytania. Jednakże, można po prostu zastąpić requestszgrequests dołu i powinno działać.

Pozostawiłem tę odpowiedź tak, jak ma odzwierciedlać pierwotne pytanie, które dotyczyło korzystania z żądań <v0.13.0.


Aby wykonać wiele zadań async.map asynchronicznie , musisz:

  1. Zdefiniuj funkcję dla tego, co chcesz zrobić z każdym obiektem (Twoim zadaniem)
  2. Dodaj tę funkcję jako punkt zaczepienia zdarzenia w swoim żądaniu
  3. Wywołaj async.maplistę wszystkich żądań / działań

Przykład:

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
Niezły pomysł, aby zostawić komentarz: ze względu na problemy ze zgodnością między najnowszymi żądaniami a grequestami (brak opcji max_retries w żądaniach 1.1.0) musiałem obniżyć poziom żądań w celu pobrania asynchronicznego i odkryłem, że asynchroniczna funkcjonalność została przeniesiona z wersjami 0.13+ ( pypi.python.org/pypi/requests )
outforawhile

1
Głupie pytanie: jaki jest wzrost szybkości używania poleceń powitania w porównaniu z prostymi prośbami? Jakie są ograniczenia dotyczące wniosków? np. czy umieszczenie 3500 żądań w async.map byłoby OK?
opadanie

10
from grequests import asyncnie działa ... i ta definicja czegoś mi pasuje def do_something(response, **kwargs):, znajduję ją ze stackoverflow.com/questions/15594015/ ...
Allan Ruin.

3
jeśli wywołanie async.map nadal blokuje, to jak to jest asynchroniczne? Poza tym, że same żądania są wysyłane asynchronicznie, pobieranie jest nadal synchroniczne?
bryanph

3
Wymiana from requests import asyncna import grequests as asyncpracowała dla mnie.
Martin Thoma

80

asyncTeraz jest niezależnym modułem: grequests.

Zobacz tutaj: https://github.com/kennethreitz/grequests

I tam: Idealna metoda wysyłania wielu żądań HTTP przez Python?

instalacja:

$ pip install grequests

stosowanie:

zbuduj stos:

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)

wyślij stos

grequests.map(rs)

wynik wygląda jak

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

Wydaje się, że grequests nie ustawia ograniczenia dla jednoczesnych żądań, tj. gdy wiele żądań jest wysyłanych do tego samego serwera.


11
Jeśli chodzi o ograniczenie jednoczesnych żądań - możesz określić rozmiar puli podczas uruchamiania map () / imap (). tzn. grequests.map (rs, size = 20), aby mieć 20 jednoczesnych chwytów.
synthesizerpatel

1
Na razie nie obsługuje to Pythona3 (gevent nie może zbudować wersji 2.6 na py3.4).
saarp

1
Nie do końca rozumiem część asynchroniczną. jeśli pozwolę results = grequests.map(rs)kodowi po tym, jak ta linia jest zablokowana, widzę efekt asynchroniczny?
Allan Ruin

47

Przetestowałem zarówno prośby - przyszłość, jak i życzenia . Grequests jest szybszy, ale przynosi małpie łatanie i dodatkowe problemy z zależnościami. request-futures jest kilkakrotnie wolniejsze niż grequests. Postanowiłem napisać własne i po prostu opakować żądania w ThreadPoolExecutor i było to prawie tak szybkie jak grequests, ale bez zewnętrznych zależności.

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

Jaki rodzaj wyjątku jest tutaj możliwy?
Slow Harry

requests.exceptions.Timeout
Hodža

2
Przepraszam, nie rozumiem twojego pytania. Używać tylko jednego adresu URL w wielu wątkach? Tylko jeden przypadek ataków DDoS))
Hodza

1
Nie rozumiem, dlaczego ta odpowiedź spotkała się z tak wieloma pozytywnymi opiniami. Pytanie OP dotyczyło żądań asynchronicznych. ThreadPoolExecutor uruchamia wątki. Tak, możesz wysyłać żądania w wielu wątkach, ale to nigdy nie będzie program asynchroniczny, więc jak mogę to być odpowiedź na pierwotne pytanie?
nagylzs

1
Właściwie chodziło o to, jak równolegle ładować adresy URL. I tak, wykonawca puli wątków nie jest najlepszą opcją, lepiej jest użyć async io, ale działa dobrze w Pythonie. I nie rozumiem, dlaczego nie można używać wątków do asynchronizacji? Co się stanie, jeśli musisz asynchronicznie uruchamiać zadanie powiązane z procesorem?
Hodza

29

może prośby o przyszłość to inny wybór.

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)

Zaleca się również w dokumencie biurowym . Jeśli nie chcesz angażować się w gevent, to dobrze.


1
Jedno z najłatwiejszych rozwiązań. Liczba jednoczesnych żądań może zostać zwiększona poprzez zdefiniowanie parametru max_workers
Jose Cherian

1
Byłoby miło zobaczyć przykład skalowania, więc nie używamy jednej nazwy zmiennej na element do zapętlenia.
user1717828

posiadanie jednego wątku na żądanie to piekielna strata zasobów! nie jest możliwe jednoczesne wykonanie na przykład 500 żądań, zabije to twój procesor. nigdy nie należy tego uważać za dobre rozwiązanie.
Corneliu Maftuleac

@CorneliuMaftuleac dobra uwaga. Jeśli chodzi o użycie wątków, zdecydowanie musisz o to zadbać, a biblioteka udostępnia opcję włączenia puli wątków lub puli przetwarzania. ThreadPoolExecutor(max_workers=10)
Dreampuf

Pula przetwarzania @Dreampuf, jak sądzę, jest jeszcze gorsza?
Corneliu Maftuleac,

11

Mam wiele problemów z większością opublikowanych odpowiedzi - albo korzystają z przestarzałych bibliotek, które zostały przeniesione z ograniczonymi funkcjami, albo zapewniają rozwiązanie ze zbyt dużą magią podczas wykonywania żądania, co utrudnia obsługę błędów. Jeśli nie należą do żadnej z powyższych kategorii, są bibliotekami innych firm lub są przestarzałe.

Niektóre z rozwiązań działają prawidłowo wyłącznie w przypadku żądań http, ale rozwiązania nie są odpowiednie dla innych rodzajów żądań, co jest śmieszne. W tym przypadku nie jest konieczne wysoce spersonalizowane rozwiązanie.

Samo użycie wbudowanej biblioteki Pythona asynciojest wystarczające do wykonywania asynchronicznych żądań dowolnego typu, a także zapewnia wystarczającą płynność do obsługi złożonych i specyficznych dla użytkownika błędów.

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

Jak to działa, jest proste. Tworzysz serię zadań, które chcesz wykonywać asynchronicznie, a następnie żądasz pętli, aby wykonać te zadania i zakończyć po zakończeniu. Brak dodatkowych bibliotek podlegających brakowi konserwacji, brak wymaganych funkcji.


2
Jeśli dobrze rozumiem, spowoduje to zablokowanie pętli zdarzeń podczas wykonywania wywołań GRPC i HTTP? Jeśli więc wykonanie tych wywołań zajmie kilka sekund, cała pętla zdarzeń zostanie zablokowana na kilka sekund? Aby tego uniknąć, musisz użyć bibliotek GRPC lub HTTP, które są async. Wtedy możesz na przykład zrobić await response = requests.get(URL) . Nie?
Coder Nr 23

Niestety, podczas wypróbowywania tego stwierdziłem, że tworzenie opakowania requestsjest ledwo szybsze (aw niektórych przypadkach wolniejsze) niż tylko synchroniczne wywoływanie listy adresów URL. Na przykład żądanie punktu końcowego, którego odpowiedź zajmuje 3 sekundy, przy użyciu powyższej strategii, zajmuje około 30 sekund. Jeśli chcesz prawdziwej asyncwydajności, musisz użyć czegoś takiego aiohttp.
DragonBobZ

8

Wiem, że to było od jakiegoś czasu zamknięte, ale pomyślałem, że może być przydatne wypromowanie innego rozwiązania asynchronicznego opartego na bibliotece żądań.

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

Dokumenty są tutaj: http://pythonhosted.org/simple-requests/


@YSY Zapraszam do zgłaszania problemów: github.com/ctheiss/simple-requests/issues ; Dosłownie korzystam z tej biblioteki tysiące razy dziennie.
Monkey Boson,

Boston, jak radzisz sobie z błędami 404/500? a co z adresami URL https? docenią wycinanie, które obsługuje tysiące adresów URL. czy możesz wkleić przykład? dzięki
YSY

@YSY Domyślnie błędy 404/500 powodują wyjątek. To zachowanie można zmienić (patrz pythonhosted.org/simple-requests/ ... ). Adresy URL HTTPS są trudne ze względu na poleganie na gevent, który obecnie ma na to wyjątkowy błąd ( github.com/gevent/gevent/issues/477 ). Bilet zawiera podkładkę, którą możesz uruchomić, ale nadal będzie generować ostrzeżenia dla serwerów SNI (ale będzie działać). Jeśli chodzi o wycinanie, obawiam się, że wszystkie moje zwyczaje są w mojej firmie i są zamknięte. Ale zapewniam, że wykonujemy tysiące zleceń na dziesiątki zleceń.
Monkey Boson

Biblioteka wygląda elegancko pod względem interakcji. Czy Python3 + jest użyteczny? Przepraszam, nie widziałem żadnej wzmianki.
Izaak Filip

@Jethro, absolutnie słusznie, biblioteka wymagałaby całkowitego przepisania, ponieważ podstawowe technologie są zupełnie inne w Pythonie 3. Na razie biblioteka jest „kompletna”, ale działa tylko dla Pythona 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
są to „normalne” żądania w wątkach. nie jest zły przykład kupowania jest nie na temat.
Nick



2

Możesz użyć httpxdo tego.

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

jeśli potrzebujesz funkcjonalnej składni, biblioteka gamla otacza toget_async .

Wtedy możesz to zrobić


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

Plik 10 limit czasu w sekundach.

(zastrzeżenie: jestem jego autorem)


I respxza kpiny / testy :)
rlat

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.