Асинхронные запросы с запросами Python


149

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

С помощью async.map(rs)я получаю коды ответа, но хочу получить содержимое каждой запрошенной страницы. Это, например, не работает:

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

Может быть, ответы, которые вы получаете, имеют пустое тело?
Мариуш Ямро 02

Работает для меня. Пожалуйста, опубликуйте полную информацию об ошибке.
Chewie

ошибки нет. он просто запускается вечно по предоставленным тестовым URL.
trbck 02

это, очевидно, появляется, когда я использую URL-адреса поверх https. http работает нормально
trbck 02

Похоже, requests-threadsсейчас существует.
OrangeDog

Ответы:


159

Заметка

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

1
Тупой вопрос: каково увеличение скорости использования grequests по сравнению с простыми запросами? Какие существуют ограничения относительно запросов? например, можно ли поместить 3500 запросов в async.map?
Droope 03

11
from grequests import asyncне работают .. и это определение что-то работает для меня def do_something(response, **kwargs):, я нахожу его из stackoverflow.com/questions/15594015/…
Аллан Руин

3
если вызов async.map все еще блокируется, тогда как это асинхронно? Помимо того, что сами запросы отправляются асинхронно, получение по-прежнему синхронно?
bryanph

4
Замена from requests import asyncна import grequests as asyncсработала у меня.
Мартин Тома

82

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

Похоже, что grequests не устанавливает ограничения для одновременных запросов, т.е. когда несколько запросов отправляются на один и тот же сервер.


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

1
На данный момент это не поддерживает python3 (gevent не может собрать v2.6 на py3.4).
saarp 04

1
Я не совсем понимаю асинхронную часть. если я позволю results = grequests.map(rs)коду после этой строки заблокироваться, я смогу увидеть асинхронный эффект?
Allan Ruin

50

Я тестировал как запросы-фьючерсы, так и grequests . Grequests работает быстрее, но приносит исправления обезьяны и дополнительные проблемы с зависимостями. request-futures в несколько раз медленнее, чем grequests. Я решил написать свои собственные и просто обернул запросы в ThreadPoolExecutor, и это было почти так же быстро, как grequests, но без внешних зависимостей.

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-атаки))
Hodza

1
Я не понимаю, почему этот ответ получил столько голосов. Вопрос OP касался асинхронных запросов. ThreadPoolExecutor запускает потоки. Да, вы можете делать запросы в нескольких потоках, но это никогда не будет асинхронной программой, поэтому я могу ответить на исходный вопрос?
nagylzs

1
Собственно вопрос был о том, как загружать URL параллельно. И да, исполнитель пула потоков - не лучший вариант, лучше использовать async io, но он хорошо работает на Python. И я не понимаю, почему потоки нельзя использовать для async? Что делать, если вам нужно запустить задачу, связанную с процессором, асинхронно?
Hodza

30

возможно, запросы-фьючерсы - другой выбор.

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)

Также рекомендуется в офисном документе . Если вы не хотите задействовать gevent, это хороший вариант.


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

1
Было бы неплохо увидеть пример такого масштабирования, чтобы мы не использовали одно имя переменной для каждого элемента в цикле.
user1717828

иметь один поток на запрос - адская трата ресурсов! невозможно выполнить, например, 500 запросов одновременно, это убьет ваш процессор. это никогда не следует рассматривать как хорошее решение.
Corneliu Maftuleac

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

Пул обработки @Dreampuf, я считаю, еще хуже?
Corneliu Maftuleac

13

У меня много проблем с большинством опубликованных ответов - они либо используют устаревшие библиотеки, которые были перенесены с ограниченными функциями, либо предоставляют решение со слишком большим количеством магии при выполнении запроса, что затрудняет обработку ошибок. Если они не попадают в одну из вышеперечисленных категорий, они являются сторонними библиотеками или являются устаревшими.

Некоторые решения работают нормально только в 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). Нет?
Кодер № 23,

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

@DragonBobZ В моем случае время сократилось примерно на 40%. Основным преимуществом была возможность выполнять необходимые работы в ожидании следующего звонка. В моем наборе данных я делал сотни вызовов, поэтому масштаб также мог быть фактором.
arshbot

@ CoderNr23 Кто-нибудь может поправить меня в этом, но iirc даже с таким синтаксисом выполнение задач в основном синхронно - порядок выполнения задач является асинхронным. В python вы просто упаковываете дерево синхронного выполнения, которое будет выполняться с помощью некоторого стартера, например, run_until_complete- это если вы не используете модуль потоковой передачи, который делегирует асинхронность на уровень ОС. Прочтите о проблеме GIL в python для получения дополнительной информации
arshbot

@arshbot Да, если у вас асинхронная работа по дому, вы увидите ускорение, несмотря на ожидание синхронных вызовов requests.get. Но вопрос в том, как выполнять асинхронные запросы с requestsбиблиотекой python . Этот ответ этого не делает, поэтому моя критика остается в силе.
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/issues ; Я буквально использую эту библиотеку тысячи раз в день.
Monkey Boson

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

@YSY По умолчанию ошибки 404/500 вызывают исключение. Это поведение можно изменить (см. Pythonhosted.org/simple-requests/… ). URL-адреса HTTPS являются сложными из-за зависимости от gevent, в котором в настоящее время есть выдающаяся ошибка ( github.com/gevent/gevent/issues/477 ). В билете есть прокладка, которую вы можете запустить, но она все равно будет выдавать предупреждения для серверов SNI (но она будет работать). Что касается ножниц, я боюсь, что все мои обычаи в моей компании и закрыты. Но уверяю вас, мы выполняем тысячи запросов по десяткам работ.
Monkey Boson

1
Библиотека выглядит гладкой с точки зрения взаимодействия. Можно ли использовать Python3 +? Извините, не удалось увидеть ни одного упоминания.
Исаак Филипп

@Jethro абсолютно прав, библиотеку нужно будет полностью переписать, поскольку базовые технологии в Python 3 совсем другие. На данный момент библиотека «завершена», но работает только для Python 2.
Monkey Boson,


4

Вы можете использовать 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))

если вам нужен функциональный синтаксис, библиотека gamla помещает его вget_async .

Тогда ты можешь сделать


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

В 10 ожидания в секундах.

(отказ от ответственности: я являюсь его автором)


И respxдля насмешек / тестирования :)
rlat

Привет, @Uri, я получаю ошибку ниже при попытке использовать код, который вы упомянули в этом ответе. await asyncio.gather(*map(get_async, urls)) ^ SyntaxError: invalid syntaxПожалуйста, проведите
AJ.

Обратите внимание, что вам нужен асинхронный контекст await.
Ури,

4
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
это «нормальные» запросы в потоках. неплохой пример покупка не по теме.
Ник

2

Я уже некоторое время использую запросы python для асинхронных вызовов API github gist.

Для примера см. Код здесь:

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

Этот стиль Python может быть не самым ярким примером, но я могу заверить вас, что код работает. Сообщите мне, если вас это смущает, и я задокументирую это.


2

К сожалению, насколько мне известно, библиотека запросов не приспособлена для выполнения асинхронных запросов. Вы можете обернуть async/awaitсинтаксис вокруг себя requests, но это сделает базовые запросы не менее синхронными. Если вам нужны истинные асинхронные запросы, вы должны использовать другие инструменты, которые их предоставляют. Одно из таких решений aiohttp(Python 3.5.3+). По моему опыту, он хорошо работает с async/awaitсинтаксисом Python 3.7 . Ниже я напишу три реализации выполнения n веб-запросов с использованием

  1. Чисто синхронные запросы ( sync_requests_get_all) с использованием Pythonrequests библиотеки
  2. Синхронные запросы ( async_requests_get_all) с использованием requestsбиблиотеки Python с async/awaitсинтаксисом Python 3.7 иasyncio
  3. Поистине асинхронная реализация ( async_aiohttp_get_all) с aiohttpбиблиотекой Python, заключенной в async/awaitсинтаксис Python 3.7, и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} 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]

На моей машине это результат:

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

1

Я также пробовал некоторые вещи, используя асинхронные методы в python, но мне гораздо больше повезло с использованием twisted для асинхронного программирования. Он имеет меньше проблем и хорошо задокументирован. Вот ссылка на что-то похожее на то, что вы пытаетесь скрутить.

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


Twisted - это старомодно. Вместо этого используйте HTTPX.
AmirHossein
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.