Żą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
trbck
źródło
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)
Jeff
źródło
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.

outforawhile
źródło
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
Hodza
źródło
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.

Dreampuf
źródło
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.

arszbot
źródło
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/

Monkey Boson
źródło
@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...
Jason Pump
źródło
4
są to „normalne” żądania w wątkach. nie jest zły przykład kupowania jest nie na temat.
Nick
4

Jeśli chcesz używać asyncio, requests-asynczapewnia funkcjonalność async / await dla requests- https://github.com/encode/requests-async

Tom Christie
źródło
2
potwierdzone, działa świetnie. Na stronie projektu jest napisane, że ta praca została przejęta przez następujący projekt github.com/encode/httpx
nurettin
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)

Uri
źródło
I respxza kpiny / testy :)
rlat
0

Próbowałem też kilku rzeczy przy użyciu metod asynchronicznych w Pythonie, ale miałem dużo więcej szczęścia, używając twisted do programowania asynchronicznego. Ma mniej problemów i jest dobrze udokumentowany. Oto link do czegoś podobnego do tego, co próbujesz w przekręconej.

http://pythonquirks.blogspot.com/2011/04/twisted-asynchronous-http-request.html

Sam
źródło
Twisted jest staromodne. Zamiast tego użyj protokołu HTTPX.
AmirHossein