Python istekleriyle Eşzamansız İstekler


142

Python için istek kitaplığının belgelerinde sağlanan örneği denedim .

İle async.map(rs), yanıt kodlarını alıyorum, ancak istenen her sayfanın içeriğini almak istiyorum. Örneğin, bu çalışmaz:

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

Belki aldığınız cevaplar boş bir bedene sahiptir?
Mariusz Jamro

Benim için çalışıyor. Lütfen aldığınız hatanın tamamını gönderin.
Chewie

hata yok. sadece sağlanan test URL'leri tarafından sonsuza kadar çalışır.
trbck

https üzerinden url kullandığım açıkça görülüyor. http gayet iyi çalışıyor
trbck

requests-threadsŞimdi var gibi görünüyor .
OrangeDog

Yanıtlar:


154

Not

Aşağıdaki cevap v0.13.0 + talepleri için geçerli değildir . Bu soru yazıldıktan sonra eşzamansız işlevsellik mağaralara taşındı . Ancak, sadece aşağıdaki requestsile değiştirebilirsiniz grequestsve çalışması gerekir.

Bu cevabı, <v0.13.0 isteklerini kullanmakla ilgili orijinal soruyu yansıtacak şekilde bıraktım.


async.map Eşzamansız olarak birden fazla görev yapmak için şunları yapmanız gerekir:

  1. Her bir nesneyle (göreviniz) ne yapmak istediğinize ilişkin bir işlev tanımlayın
  2. Bu işlevi isteğinize bir olay kancası olarak ekleyin
  3. async.mapTüm isteklerin / işlemlerin bir listesini arayın

Misal:

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
Yorumunuzu bırakmış olmak için güzel bir fikir: son istekler ve yunanlar arasındaki uyumluluk sorunları nedeniyle (istek 1.1.0'da max_retries seçeneğinin olmaması) zaman uyumsuzluğu almak için istekleri düşürmek zorunda kaldım ve asenkron işlevlerin 0.13+ sürümleriyle taşındığını gördüm ( pypi.python.org/pypi/requests )
outforawhile

1
Aptalca soru: Sadece isteklerin tersine, mağlubeleri kullanmanın hız artışı nedir? Talepler için ne gibi limitler var? Örneğin, async.map dosyasına 3500 istek koymak iyi olur mu?
droope

10
from grequests import asyncçalışmıyor .. ve bu saçma şey tanımı benim için işe yarıyor def do_something(response, **kwargs):, bunu stackoverflow.com/questions/15594015/…
Allan Ruin

3
async.map çağrısı hala engelliyorsa, bu nasıl zaman uyumsuzdur? İsteklerin eşzamansız olarak gönderilmesinin yanı sıra, geri alma hala eşzamanlı mıdır?
bryanph

3
Değiştirme from requests import asynctarafından import grequests as asyncbenim için çalıştı.
Martin Thoma

80

asyncBağımsız bir modül artık şudur: grequests.

Buraya bakın: https://github.com/kennethreitz/grequests

Ve orada: Python üzerinden birden fazla HTTP isteği göndermek için ideal yöntem?

Kurulum:

$ pip install grequests

kullanımı:

bir yığın oluşturun:

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)

yığını gönder

grequests.map(rs)

sonuç benziyor

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

grequest'ler, eşzamanlı istekler için, yani aynı sunucuya birden fazla istek gönderildiğinde, bir sınırlama getirmiş gibi görünmemektedir.


11
Eşzamanlı istekler üzerindeki sınırlama ile ilgili olarak - map () / imap () öğesini çalıştırırken bir havuz boyutu belirleyebilirsiniz. yani eşzamanlı kapmak için grequests.map (rs, size = 20).
synthesizerpatel

1
Şu an itibariyle bu python3 özellikli değildir (gevent py3.4 üzerinde v2.6 oluşturmaz).
saarp

1
Asenkron bölümünü tam olarak anlamıyorum. results = grequests.map(rs)Bu satır bloğundan sonra kod izin verirseniz , zaman uyumsuz etkisi görebilirsiniz?
Allan Ruin

47

Hem test istekleri-gelecekleri ve grequests . Grequests daha hızlıdır, ancak maymun yama ve bağımlılıklarla ilgili ek sorunlar getirir. istek-vadeli işlemleri, mağlubiyetlerden birkaç kat daha yavaştır. Kendi ve sadece isteklerini ThreadPoolExecutor içine yazmaya karar verdim ve neredeyse depquest kadar hızlı, ama dış bağımlılıkları olmadan.

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

Burada ne tür bir istisna mümkündür?
Yavaş Harry

requests.exceptions.Timeout
Hoca,

2
Üzgünüm sorunuzu anlamıyorum. Birden fazla iş parçacığında yalnızca tek bir url mi kullanıyorsunuz? Sadece bir vaka DDoS saldırıları))
Hodza

1
Bu cevabın neden bu kadar çok oy aldığını anlamıyorum. OP sorusu asenkron isteklerle ilgili idi. ThreadPoolExecutor evreleri çalıştırır. Evet, birden fazla iş parçacığında istekte bulunabilirsiniz, ancak bu asla bir zaman uyumsuz program olmayacaktır, bu yüzden orijinal sorunun cevabı nasıl olabilir?
nagylzs

1
Aslında soru, URL'lerin paralel olarak nasıl yükleneceği ile ilgiliydi. Ve evet iş parçacığı havuzu yürütücü en iyi seçenek değil, async io kullanmak daha iyidir, ancak Python iyi çalışır. Ve iş parçacıklarının neden zaman uyumsuz olarak kullanılamadığını anlamıyorum? CPU bağlantılı görevi eşzamansız olarak çalıştırmanız gerekirse ne olur?
Hodza

29

belki istek-futures başka bir seçimdir.

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)

Ofis dokümanında da tavsiye edilir . Gevent'i dahil etmek istemiyorsanız, bu iyi bir şeydir.


1
En kolay çözümlerden biri. Eşzamanlı istek sayısı, max_workers parametresi tanımlanarak artırılabilir
Jose Cherian

1
Bu ölçeklendirilmiş bir örneği görmek güzel olurdu, bu yüzden döngü için öğe başına bir değişken adı kullanmıyoruz.
user1717828

istek başına bir iş parçacığı olması kaynak cehennem israfıdır! aynı anda örneğin 500 istek yapmak mümkün değildir, cpu'nuzu öldürür. bu asla iyi bir çözüm olarak görülmemelidir.
Corneliu Maftuleac

@CorneliuMaftuleac iyi bir nokta. İş parçacığı kullanımı ile ilgili olarak, kesinlikle buna dikkat etmeniz gerekir ve kütüphane, iş parçacığı havuzunu veya işleme havuzunu etkinleştirmek için bir seçenek sunar. ThreadPoolExecutor(max_workers=10)
Dreampuf

@Dreampuf işleme havuzu daha kötü olduğuna inanıyorum?
Corneliu Maftuleac

12

Gönderilen cevapların çoğuyla ilgili çok fazla sorunum var - ya sınırlı özelliklerle taşınan kullanımdan kaldırılmış kütüphaneleri kullanıyorlar ya da isteğin yürütülmesinde çok fazla sihir içeren bir çözüm sağlayarak hata işlemeyi zorlaştırıyorlar. Yukarıdaki kategorilerden birine girmezlerse, üçüncü taraf kütüphaneleridir veya kullanımdan kaldırılmıştır.

Çözümlerin bazıları tamamen http isteklerinde işe yarıyor, ancak çözümler diğer her türlü istek için yetersiz kalıyor, ki bu gülünç. Burada son derece özelleştirilmiş bir çözüm gerekli değildir.

Basitçe, python yerleşik kütüphanesini kullanmak, asyncioherhangi bir türdeki eşzamansız istekleri gerçekleştirmek için yeterlidir, ayrıca karmaşık ve kullanıcıya özgü hata işleme için yeterli akışkanlık sağlar.

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

Nasıl çalıştığı basit. Eşzamansız olarak gerçekleşmesini istediğiniz bir dizi görev oluşturuyorsunuz ve ardından bir döngüden bu görevleri gerçekleştirmesini ve tamamlandıktan sonra çıkmasını istiyorsunuz. Bakım gerektirmeyen ekstra kütüphane, işlevsellik eksikliği gerekmez.


2
Doğru anlarsam, bu GRPC ve HTTP çağrısı yaparken olay döngüsünü engelleyecektir? Bu çağrıların tamamlanması saniyeler alırsa, tüm etkinlik döngünüz saniyeler boyunca engellenir mi? Bundan kaçınmak için olan GRPC veya HTTP kitaplıklarını kullanmanız gerekir async. Sonra örneğin yapabilirsiniz await response = requests.get(URL). Hayır?
Kodlayıcı Nr 23

Ne yazık ki, bunu denerken, bir sarmalayıcı yapmanın requests, URL'lerin bir listesini senkronize olarak çağırmaktan çok daha hızlı (ve bazı durumlarda daha yavaş) olduğunu buldum . Örneğin, yukarıdaki stratejiyi kullanarak 10 kez yanıt vermek için 3 saniye süren bir bitiş noktası istemek yaklaşık 30 saniye sürer. Gerçek asyncperformans istiyorsanız , böyle bir şey kullanmanız gerekir aiohttp.
DragonBobZ

@DragonBobZ Benim durumumda ~% 40 oranında bir azalma gördüm. En büyük yararı, bir sonraki çağrıyı beklerken gerekli işleri yapabilmektir. Veri setimde yüzlerce arama yapıyordum, bu yüzden ölçek de bir faktör olabilir.
arshbot

@ CoderNr23 Birisi beni bu konuda düzeltebilir, ancak bu sözdizimiyle bile, görevleri yürütmek temelde eşzamanlıdır - zaman uyumsuz hale getirildiği zaman hangi görevlerin sırası. Python'da, sadece bir başlangıç ​​ile çalıştırılacak olan senkronize bir yürütme ağacını paketliyorsunuz run_until_complete- bu, OS katmanına zaman uyumsuzluk sağlayan iplik geçirme modülünü kullanmamanızdır. Daha fazla bilgi için
python'daki

@arshbot Evet, eğer ev işleriniz eşzamansızsa, eşzamanlı çağrıları beklemenize rağmen hızlanmaları göreceksiniz requests.get. Ancak soru, python requestskütüphanesi ile asenkron isteklerin nasıl yapılacağıdır . Bu cevap bunu yapmıyor, bu yüzden eleştirim duruyor.
DragonBobZ

8

Bunun bir süredir kapalı olduğunu biliyorum, ancak istek kitaplığına dayanan başka bir zaman uyumsuz çözümü teşvik etmenin yararlı olabileceğini düşündüm.

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

Dokümanlar burada: http://pythonhosted.org/simple-requests/


@YSY Bir sorun göndermekten çekinmeyin: github.com/ctheiss/simple-requests/issues ; Kelimenin tam anlamıyla bu kütüphaneyi günde binlerce kez kullanıyorum.
Monkey Boson

Boston, 404/500 hatalarını nasıl ele alırsınız? https URL'leri ne olacak? binlerce URL'yi destekleyen bir ekran alıntısını takdir edecektir. lütfen bir örnek yapıştırabilir misiniz? teşekkürler
YSY

@YSY Varsayılan olarak 404/500 hataları bir istisna oluşturur. Bu davranış geçersiz kılınabilir (bkz. Pythonhosted.org/simple-requests/… ). HTTPS URL'leri, şu anda bu konuda olağanüstü bir hataya sahip olan gevent'e güvenmesi nedeniyle zor ( github.com/gevent/gevent/issues/477 ). Bilet üzerinde çalıştırabileceğiniz bir şim var, ancak yine de SNI sunucuları için uyarı gönderecek (ancak çalışacak). Snipping gelince, korkarım tüm kullanımlarım şirketimde ve kapalı. Ama temin ederim ki onlarca iş için binlerce talep gerçekleştiriyoruz.
Maymun Boson

Kütüphane etkileşim konusunda şık görünüyor. Python3 + kullanılabilir mi? Üzgünüm herhangi bir söz göremedik.
Isaac Philip

@Jethro kesinlikle doğru, temel teknolojiler Python 3'te oldukça farklı olduğu için kütüphanenin yeniden yazılması gerekiyor. Şimdilik, kütüphane "tamamlandı" ama sadece Python 2 için çalışıyor
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
iş parçacıklarındaki "normal" isteklerdir. kötü değil örnek satın alma konu dışı.
Nick



2

Bunun için kullanabilirsiniz 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))

işlevsel bir sözdizimi istiyorsanız gamla lib bunuget_async .

Sonra yapabilirsin


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

10Saniye cinsinden zaman aşımı olduğunu.

(sorumluluk reddi: Ben onun yazarım)


Ve respxalay etmek / test etmek için :)
rlat


0

Ne yazık ki, bildiğim kadarıyla, istek kitaplığı asenkron istekleri gerçekleştirmek için donanımlı değil. async/awaitSözdizimini etrafına sarabilirsiniz requests, ancak temel istekleri daha az senkronize hale getirmez. Gerçek zaman uyumsuz istekleri istiyorsanız, bunu sağlayan diğer araçları kullanmanız gerekir. Böyle bir çözüm aiohttp(Python 3.5.3+). Python 3.7 async/awaitsözdizimi ile kullanma deneyimimde iyi çalışıyor . Aşağıda aşağıdakileri kullanarak üç web isteği gerçekleştirmenin üç uygulamasını yazıyorum:

  1. sync_requests_get_allPython kullanarak tamamen senkronize istekler ( )requests kitaplığını
  2. Python 3.7 ile sarılmış async_requests_get_allPython requestskütüphanesini kullanarak senkronize istekler ( )async/await sözdiziminde veasyncio
  3. Python 3.7 sözdizimine sarılmış async_aiohttp_get_allPython aiohttpkütüphanesi ile gerçek zamanlı olmayan bir uygulama ( ) async/awaitveasyncio
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} finsished 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 asnyc_aiohttp_get_all(urls):
    """
    performs asynchronous get requests
    """
    loop = asyncio.get_event_loop()
    return loop.run_until_complete(fetch_many(loop, urls))


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


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]

Makinemde çıktı bu:

sync_requests_get_all          started
sync_requests_get_all          finsished in 30.92 seconds
async_requests_get_all         started
async_requests_get_all         finsished in 30.87 seconds
asnyc_aiohttp_get_all          started
asnyc_aiohttp_get_all          finsished in 3.22 seconds
----------------------
sync_requests_get_all          finsished in 30.92 seconds
async_requests_get_all         finsished in 30.87 seconds
asnyc_aiohttp_get_all          finsished in 3.22 seconds
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.