Асинхронні запити із запитами Python


142

Я спробував зразок, наданий в документації бібліотеки запитів на python.

З async.map(rs), я отримую коди відповідей, але хочу отримати вміст кожної запитуваної сторінки. Наприклад, це не працює:

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

Можливо, відповіді, які ви отримуєте, мають порожнє тіло?
Маріуш Джамро

Працює для мене. Будь ласка, опублікуйте повну помилку, яку ви отримуєте.
Chewie

помилки немає. він просто працює назавжди наданими тестовими URL-адресами.
trbck

це, очевидно, з’являється, коли я використовую URL через https. http працює чудово
trbck

Схоже, requests-threadsіснує зараз.
OrangeDog

Відповіді:


154

Примітка

Відповідь нижче не стосується запитів v0.13.0 +. Після написання цього питання асинхронна функціональність була перенесена на найсильніші запити . Тим НЕ менше, ви могли б просто замінити requestsз grequestsнижче , і він повинен працювати.

Цю відповідь я залишив так, щоб відображати оригінальне запитання, яке стосувалося використання запитів <v0.13.0.


Для виконання декількох завдань з async.map асинхронним зв'язком потрібно:

  1. Визначте функцію для того, що ви хочете робити з кожним об'єктом (ваше завдання)
  2. Додайте цю функцію як гачок події у своєму запиті
  3. Телефонуйте async.mapу списку всіх запитів / дій

Приклад:

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
Хороша ідея, що ви залишили ваш коментар: через проблеми сумісності між останніми запитами та запрошеннями (відсутність параметра max_retries у запитах 1.1.0) мені довелося знизити запити, щоб відновити асинхронізацію, і я виявив, що асинхронна функціональність була переміщена з версіями 0.13+ ( pypi.python.org/pypi/requests )
поза тим

1
Німе запитання: Яке збільшення швидкості використання грекетів на відміну від просто запитів? Які обмеження щодо запитів? наприклад, чи буде розміщення 3500 запитів у async.map добре?
droope

10
from grequests import asyncне працюють .. і це визначення досоми щось працює для мене def do_something(response, **kwargs):, я знаходжу це з stackoverflow.com/questions/15594015/…
Allan Ruin

3
якщо виклик async.map все ще блокується, то як це асинхронно? Крім того, що самі запити надсилаються асинхронно, пошук є ще синхронним?
Брайанф

3
Заміна from requests import asyncна import grequests as asyncпрацював для мене.
Мартін Тома

80

asyncв даний час є незалежним модуль: grequests.

Дивіться тут: https://github.com/kennethreitz/grequests

І там: Ідеальний метод для надсилання декількох HTTP-запитів через Python?

установка:

$ pip install grequests

використання:

побудувати стек:

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)

відправити стек

grequests.map(rs)

результат виглядає так

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

здається, що грекести не встановлюють обмеження для одночасних запитів, тобто коли кілька запитів надсилаються на один і той же сервер.


11
Що стосується обмеження на паралельні запити - ви можете вказати розмір пулу під час запуску map () / imap (). тобто grequests.map (rs, size = 20), щоб мати 20 одночасних захоплень.
синтезаторпател

1
Наразі це не підтримує python3 (gevent не в змозі створити v2.6 на py3.4).
saarp

1
Я не зовсім розумію асинхронну частину. якщо я дозволю results = grequests.map(rs)коду після блокування цього рядка, я можу побачити ефект асинхронізації?
Аллан Руїн

47

Я перевіряв і запити, і ф'ючерси, і виграші . Grequests швидше, але приносить виправлення мавп та додаткові проблеми із залежностями. запити-ф'ючерси в кілька разів повільніше, ніж найсильніші. Я вирішив написати свої власні і просто загорнуті запити в ThreadPoolExecutor, і це було майже так само швидко, як гракети, але без зовнішніх залежностей.

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

Який тип винятку тут можливий?
Повільний Гаррі

requests.exceptions.Timeout
Hodza

2
Вибачте, я не розумію вашого запитання. Використовувати лише одну URL-адресу в декількох потоках? Лише один випадок DDoS-атак))
Ходжа

1
Я не розумію, чому ця відповідь отримала так багато відгуків. Питання ОП стосувалося запитів асинхронізації. ThreadPoolExecutor запускає потоки. Так, ви можете робити запити в декількох потоках, але це ніколи не буде асинхронною програмою, тож як я могла відповісти на початкове запитання?
nagylzs

1
Власне, питання полягало в тому, як паралельно завантажувати URL-адреси. І так, виконавець пулових потоків - не найкращий варіант, краще використовувати async io, але він добре працює в Python. І я не розумію, чому теми не можна використовувати для асинхронізації? Що робити, якщо вам потрібно запустити завдання, пов'язане з процесором, асинхронно?
Hodza

29

можливо, запити-ф'ючерси - це інший вибір.

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)

Також рекомендується в офісному документі . Якщо ви не хочете залучати гевент, це добре.


1
Одне з найпростіших рішень. Кількість одночасних запитів можна збільшити, визначивши параметр max_workers
Хосе Черіан

1
Було б добре побачити приклад цього масштабу, тому ми не використовуємо одне ім’я змінної для елемента, щоб переоцінити його.
користувач1717828

мати одну нитку на запит - це марна трата ресурсів! не можна робити, наприклад, 500 запитів одночасно, це вб'є ваш процесор. це ніколи не слід вважати хорошим рішенням.
Корнеліу Мафтулеак

@CorneliuMaftuleac хороший момент. Що стосується використання потоку, то вам обов'язково потрібно подбати про нього, і бібліотека надає можливість ввімкнути пул потоків чи пул обробки. ThreadPoolExecutor(max_workers=10)
Dreampuf

@Dreampuf обробний пул, я вважаю, ще гірше?
Корнеліу Мафтулеак

12

У мене дуже багато проблем з більшістю опублікованих відповідей - вони або використовують застарілі бібліотеки, перенесені з обмеженими можливостями, або надають рішення з надто великою магією при виконанні запиту, що ускладнює обробку помилок. Якщо вони не належать до однієї з перерахованих вище категорій, вони є сторонніми бібліотеками або застарілими.

Деякі з рішень спрацьовують виключно в http-запитах, але рішення не відповідають будь-яким іншим запитам, що смішно. Тут не потрібні високомобільні рішення.

Просто використання вбудованої бібліотеки python asyncioє достатньою для виконання асинхронних запитів будь-якого типу, а також забезпечує достатню плинність для складних та корисних випадків обробки помилок.

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

Як це працює просто. Ви створюєте низку завдань, які хочете виконувати асинхронно, а потім просите цикл виконати ці завдання та вийти після завершення. Немає зайвих бібліотек, які не піддаються технічному обслуговуванню, відсутність функціональності.


2
Якщо я правильно розумію, це блокує цикл подій під час виконання дзвінків GRPC та HTTP? Отже, якщо ці дзвінки займуть кілька секунд, весь цикл подій блокується на секунди? Щоб цього уникнути, вам потрібно використовувати бібліотеки GRPC або HTTP, які є async. Тоді ви можете, наприклад, зробити await response = requests.get(URL). Немає?
Coder Nr 23

На жаль, спробувавши це, я виявив, що робити обгортку requestsнавряд чи швидше (а в деяких випадках і повільніше), ніж просто синхронно викликати список URL-адрес. Наприклад, запит на кінцеву точку, яка потребує 3 секунд, щоб відповісти 10 разів, використовуючи вищезгадану стратегію, займає приблизно 30 секунд. Якщо ви хочете справжньої asyncпродуктивності, вам потрібно використовувати щось подібне aiohttp.
DragonBobZ

8

Я знаю, що це було закрито на деякий час, але я подумав, що може бути корисним просувати ще одне рішення асинхронізації, побудоване на бібліотеці запитів.

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

Документи тут: http://pythonhosted.org/simple-requests/


@YSY Не соромтеся публікувати випуск: github.com/ctheiss/simple-requests/isissue ; Я буквально користуюся цією бібліотекою тисячі разів на день.
Мавпа Босон

Бостоне, як ви обробляєте 404/500 помилок? як щодо URL-адрес https? оцінить знімок, який підтримує тисячі URL-адрес. ви можете, будь ласка, вставити приклад? дякую
YSY

@YSY За замовчуванням 404/500 помилок викликає виняток. Таку поведінку можна змінити (див. Pythonhosted.org/simple-requests/… ). URL-адреси HTTPS є складними через залежність від gevent, який наразі має неабияку помилку ( github.com/gevent/gevent/isissue/477 ). У квитку є зашморг, який ви можете запустити, але він все одно кине попередження для серверів SNI (але він буде працювати). Що стосується снайпера, я боюся, що всі мої звичаї в моїй компанії та закриті. Але запевняю, ми виконуємо тисячі запитів на десятки завдань.
Мавпа Босон

Бібліотека виглядає гладкою щодо взаємодії. Чи Python3 + корисний? На жаль, не вдалося побачити жодної згадки.
Ісаак Філіп

@Jethro абсолютно прав, бібліотеці знадобиться тотальне переписування, оскільки основні технології в Python 3. зовсім інші. Зараз бібліотека є "повною", але працює лише для 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
це "звичайні" запити в потоках. не поганий приклад покупки поза темою.
Нік


2

Я вже деякий час використовую запити python для викликів асинхронізації проти API суті github.

Для прикладу дивіться код тут:

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

Цей стиль пітона може бути не найяскравішим прикладом, але я можу запевнити, що код працює. Повідомте мене, якщо це вас бентежить, і я задокументую це.


2

Ви можете використовувати 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))

якщо ви хочете функціональний синтаксис, гамла- ліб обертає це get_async.

Тоді ви можете зробити


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

Час 10очікування в секундах.

(відмова від відповідальності: я її автор)


А respxдля глузування / тестування :)
rlat

0

Я також спробував деякі речі, використовуючи асинхронні методи в python, як ніколи мені пощастило використовувати скручені для асинхронного програмування. У нього менше проблем і добре документовано. Ось посилання чогось схожого на те, що ви намагаєтесь крутити.

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


Скручений - старомодний. Замість цього використовуйте HTTPX.
AmirHossein
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.