Żądania asynchroniczne z żądaniami Pythona

Wypróbowałem próbkę dostarczoną w dokumentacji requests library dla Pythona.

Z 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
Author: gmds, 2012-02-02

13 answers

Uwaga

Poniższa odpowiedź to Nie dotyczy zapytań v0.13.0+. Funkcja asynchroniczna została przeniesiona do grequests po napisaniu tego pytania. Możesz jednak zastąpić requests grequests poniżej i powinno działać.

Zostawiłam tę odpowiedź, aby odzwierciedlić pierwotne pytanie, które dotyczyło korzystania z requests


Do wykonywania wielu zadań z async.map asynchronicznie musisz:

  1. Define a funkcja dla tego, co chcesz zrobić z każdym obiektem (Twoim zadaniem)
  2. Dodaj tę funkcję jako hook zdarzeń w żądaniu
  3. wywołanie async.map na liście wszystkich żądań / akcji

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)
 163
Author: Jeff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-02 17:14:26

async jest teraz niezależnym modułem: grequests.

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

I tam: idealna metoda do wysyłania wielu żądań HTTP przez Pythona?

Instalacja:

$ pip install grequests

Użycie:

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

Grequesty nie wydają się ustawiać ograniczenia dla jednoczesnych żądań, tj. gdy wiele żądań jest wysyłanych do ten sam serwer.

 86
Author: outforawhile,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:44

Przetestowałem zarówno requests-futures jak igrequests . Grequests jest szybszy, ale przynosi łatanie małp i dodatkowe problemy z zależnościami. requests-futures jest kilka razy wolniejszy niż grequests. Postanowiłem napisać własne i po prostu zawinięte żądania do ThreadPoolExecutor i było prawie tak szybkie jak grequesty, 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
 52
Author: Hodza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-24 18:57:42

Może requests-futures 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)

Jest również zalecany w dokumencie biurowym . Jeśli nie chcesz angażować geventa, to dobrze.

 31
Author: Dreampuf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-03-22 06:54:58

Mam wiele problemów z większością opublikowanych odpowiedzi - albo używają przestarzałych bibliotek, które zostały przeniesione z ograniczonymi funkcjami, albo dostarczają rozwiązanie z zbyt dużą ilością magii na wykonanie żądania, co utrudnia obsługę błędów. Jeśli nie należą do jednej z powyższych kategorii, są bibliotekami innych firm lub są przestarzałe.

Niektóre rozwiązania działają w porządku wyłącznie w żądaniach http, ale rozwiązania nie pasują do każdego innego rodzaju żądania, które to niedorzeczne. Wysoce spersonalizowane rozwiązanie nie jest tutaj konieczne.

Samo użycie wbudowanej biblioteki Pythona asyncio jest wystarczające do wykonywania asynchronicznych żądań dowolnego typu, a także zapewnia wystarczającą płynność dla skomplikowanej i specyficznej obsługi 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 wykonać asynchronicznie, a następnie prosisz pętlę o wykonanie tych zadań i zakończenie po zakończeniu. Brak dodatkowych bibliotek konserwacji, nie wymaga braku funkcjonalności.
 13
Author: arshbot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-20 16:37:31

Wiem, że to było zamknięte przez jakiś czas, ale pomyślałem, że może być przydatne promowanie innego rozwiązania asynchronicznego zbudowanego na bibliotece requests.

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/

 8
Author: Monkey Boson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-21 14:57:30

Jeśli chcesz użyć asyncio, to requests-async udostępnia funkcjonalność async / wait dla requests - https://github.com/encode/requests-async

 6
Author: Tom Christie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-27 10:48:44

Możesz użyć httpx do 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 chcesz mieć składnię funkcjonalną, gamla lib zawija to w get_async.

Wtedy możesz zrobić


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

10 to limit czasu w sekundach.

(disclaimer: I am its author)

 6
Author: Uri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-15 14:34:09

Niestety, z tego co wiem, biblioteka requests nie jest przystosowana do wykonywania żądań asynchronicznych. Możesz zawinąć składnię async/await wokół requests, ale to sprawi, że podstawowe żądania nie będą mniej synchroniczne. Jeśli chcesz uzyskać prawdziwe żądania asynchroniczne, musisz użyć innych narzędzi, które je zapewniają. Jednym z takich rozwiązań jest aiohttp (Python 3.5.3+). Z mojego doświadczenia wynika, że używa się go ze składnią Pythona 3.7 async/await. Poniżej piszę trzy implementacje wykonywania N żądań internetowych using

  1. czysto synchroniczne żądania (sync_requests_get_all) za pomocą biblioteki Pythona requests
  2. synchroniczne żądania (async_requests_get_all) za pomocą biblioteki Pythona requests owiniętej w składnię Pythona 3.7 async/await i asyncio
  3. prawdziwie asynchroniczna implementacja (async_aiohttp_get_all) z biblioteką Pythona aiohttp owiniętą w składnię Pythona 3.7 async/await i asyncio
import time
import asyncio
import requests
import aiohttp

from types import SimpleNamespace

durations = []


def timed(func):
    """
    records approximate durations of function calls
    """
    def wrapper(*args, **kwargs):
        start = time.time()
        print(f'{func.__name__:<30} started')
        result = func(*args, **kwargs)
        duration = f'{func.__name__:<30} finished in {time.time() - start:.2f} seconds'
        print(duration)
        durations.append(duration)
        return result
    return wrapper


async def fetch(url, session):
    """
    asynchronous get request
    """
    async with session.get(url) as response:
        response_json = await response.json()
        return SimpleNamespace(**response_json)


async def fetch_many(loop, urls):
    """
    many asynchronous get requests, gathered
    """
    async with aiohttp.ClientSession() as session:
        tasks = [loop.create_task(fetch(url, session)) for url in urls]
        return await asyncio.gather(*tasks)

@timed
def sync_requests_get_all(urls):
    """
    performs synchronous get requests
    """
    # use session to reduce network overhead
    session = requests.Session()
    return [SimpleNamespace(**session.get(url).json()) for url in urls]


@timed
def async_requests_get_all(urls):
    """
    asynchronous wrapper around synchronous requests
    """
    loop = asyncio.get_event_loop()
    # use session to reduce network overhead
    session = requests.Session()

    async def async_get(url):
        return session.get(url)

    async_tasks = [loop.create_task(async_get(url)) for url in urls]
    return loop.run_until_complete(asyncio.gather(*async_tasks))


@timed
def asnyc_aiohttp_get_all(urls):
    """
    performs asynchronous get requests
    """
    loop = asyncio.get_event_loop()
    return loop.run_until_complete(fetch_many(loop, urls))


if __name__ == '__main__':
    # this endpoint takes ~3 seconds to respond,
    # so a purely synchronous implementation should take
    # little more than 30 seconds and a purely asynchronous
    # implementation should take little more than 3 seconds.
    urls = ['https://postman-echo.com/delay/3']*10

    sync_requests_get_all(urls)
    async_requests_get_all(urls)
    asnyc_aiohttp_get_all(urls)
    print('----------------------')
    [print(duration) for duration in durations]

Na mojej maszynie jest to wyjście:

sync_requests_get_all          started
sync_requests_get_all          finished in 30.92 seconds
async_requests_get_all         started
async_requests_get_all         finished in 30.87 seconds
asnyc_aiohttp_get_all          started
asnyc_aiohttp_get_all          finished in 3.22 seconds
----------------------
sync_requests_get_all          finished in 30.92 seconds
async_requests_get_all         finished in 30.87 seconds
asnyc_aiohttp_get_all          finished in 3.22 seconds
 6
Author: DragonBobZ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-09 11:04:49
from threading import Thread

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
Author: Jason Pump,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-11 16:57:17

Od jakiegoś czasu używam zapytań Pythona do asynchronicznych wywołań w GIST API Githuba.

Aby zobaczyć przykład, zobacz kod tutaj:

Https://github.com/davidthewatson/flasgist/blob/master/views.py#L60-72

Ten styl Pythona może nie jest najlepszym przykładem, ale mogę cię zapewnić, że kod działa. Daj mi znać, jeśli to będzie dla Ciebie mylące, a ja to udokumentuję.

 2
Author: David Watson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-23 05:35:36

Following code creates different threads for each function.

Może to być przydatne w niektórych przypadkach, ponieważ jest prostsze w użyciu. Ale wiedz, że nie jest to asynchroniczne, ale daje iluzję asynchronicznego korzystania z wielu wątków, mimo że dekorator sugeruje, że.

Możesz użyć poniższego dekoratora, aby wywołać wywołanie zwrotne po zakończeniu wykonywania funkcji, wywołanie zwrotne musi obsługiwać przetwarzanie danych zwracanych przez funkcję.

Należy pamiętać, że po udekorowaniu funkcji zwróci ona Future obiekt.

import asyncio

## Decorator implementation of async runner !!
def run_async(callback, loop=None):
    if loop is None:
        loop = asyncio.get_event_loop()

    def inner(func):
        def wrapper(*args, **kwargs):
            def __exec():
                out = func(*args, **kwargs)
                callback(out)
                return out

            return loop.run_in_executor(None, __exec)

        return wrapper

    return inner

Przykład realizacji:

urls = ["https://google.com", "https://facebook.com", "https://apple.com", "https://netflix.com"]
loaded_urls = []  # OPTIONAL, used for showing realtime, which urls are loaded !!


def _callback(resp):
    print(resp.url)
    print(resp)
    loaded_urls.append((resp.url, resp))  # OPTIONAL, used for showing realtime, which urls are loaded !!


# Must provide a callback function, callback func will be executed after the func completes execution
# Callback function will accept the value returned by the function.
@run_async(_callback)
def get(url):
    return requests.get(url)


for url in urls:
    get(url)

Jeśli chcesz zobaczyć, który adres URL jest ładowany w czasie rzeczywistym, możesz dodać następujący kod na końcu, jak również:

while True:
    print(loaded_urls)
    if len(loaded_urls) == len(urls):
        break
 2
Author: vaskrneup,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-31 15:49:16

Próbowałem też kilku rzeczy używając metod asynchronicznych w Pythonie, jak kiedykolwiek 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 tego co próbujesz w twisted.

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

 1
Author: Sam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-02 17:06:14