Як я можу використовувати запити в асинціо?


128

Я хочу робити паралельні завдання запиту http asyncio, але я вважаю, що python-requestsце перекриє цикл подій asyncio. Я знайшов aiohttp, але він не міг надати http-запит за допомогою http-проксі.

Тому я хочу знати, чи є спосіб зробити асинхронні запити http за допомогою asyncio.


1
Якщо ви просто надсилаєте запити, ви можете використати subprocessпаралельний код.
WeaselFox

Цей метод здається не елегантним ……
листівка

Зараз існує порт запитів на асинціо. github.com/rdbhost/yieldfromRequests
Rdbhost

Відповіді:


181

Для використання запитів (або будь-яких інших блокуючих бібліотек) з asyncio, ви можете використовувати BaseEventLoop.run_in_executor, щоб запустити функцію в інший потік і отримати з неї результат, щоб отримати результат. Наприклад:

import asyncio
import requests

@asyncio.coroutine
def main():
    loop = asyncio.get_event_loop()
    future1 = loop.run_in_executor(None, requests.get, 'http://www.google.com')
    future2 = loop.run_in_executor(None, requests.get, 'http://www.google.co.uk')
    response1 = yield from future1
    response2 = yield from future2
    print(response1.text)
    print(response2.text)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Це отримає обидві відповіді паралельно.

З python 3.5 ви можете використовувати новий await/ asyncсинтаксис:

import asyncio
import requests

async def main():
    loop = asyncio.get_event_loop()
    future1 = loop.run_in_executor(None, requests.get, 'http://www.google.com')
    future2 = loop.run_in_executor(None, requests.get, 'http://www.google.co.uk')
    response1 = await future1
    response2 = await future2
    print(response1.text)
    print(response2.text)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Див. PEP0492 для отримання додаткової інформації.


5
Чи можете ви пояснити, як саме це працює? Я не розумію, як це не блокується.
Скотт Коутс

32
@christian, але якщо його паралельно працює в іншій нитці, чи не переможе ця точка асинсіо?
Скотт Коутс

21
@scoarescoare Ось тут і приходить частина "якщо ти зробиш правильно" - метод, який ти запускаєш у виконавцю, повинен бути самостійним ((в основному), як request.get у наведеному вище прикладі). Таким чином, вам не доведеться мати справу зі спільною пам'яттю, блокуванням тощо, а складні частини вашої програми все ще є однопотоковою завдяки асинціо.
Крістіан

5
@scoarescoare Основний випадок використання - це інтеграція з бібліотеками IO, у яких немає підтримки для asyncio. Наприклад, я займаюся деякою роботою з справді старовинним інтерфейсом SOAP, і я використовую бібліотеку suds-jurko як "найменш погане" рішення. Я намагаюся інтегрувати його з сервером asyncio, тому я використовую run_in_executor для здійснення блокування дзвінків suds таким чином, що виглядає асинхронним.
Лукретьєль

10
Дійсно здорово, що це працює і так легко для застарілих речей, але слід підкреслити, що він використовує нитку ОС, і тому не збільшує масштаб як справжню лінійку, орієнтовану на асинціо, як це робить
aiohttp

78

aiohttp вже можна використовувати з HTTP-проксі:

import asyncio
import aiohttp


@asyncio.coroutine
def do_request():
    proxy_url = 'http://localhost:8118'  # your proxy address
    response = yield from aiohttp.request(
        'GET', 'http://google.com',
        proxy=proxy_url,
    )
    return response

loop = asyncio.get_event_loop()
loop.run_until_complete(do_request())

Що тут робить роз'єм?
Маркус Месканен

Він забезпечує з'єднання через проксі-сервер
mindmaster

16
Це набагато краще рішення, ніж використовувати запити в окремому потоці. Оскільки він справді асинхронний, він має менші накладні витрати та менше споживання пам’яті.
Том,

14
для python> = 3.5 замініть @ asyncio.coroutine на "async", а "вийти з" на "чекайте"
Джеймс

41

У наведених вище відповідях все ще використовуються старовинні підпрограми стилю Python 3.4. Ось що б ви написали, якби отримали Python 3.5+.

aiohttp підтримує http-проксі зараз

import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    urls = [
            'http://python.org',
            'https://google.com',
            'http://yifei.me'
        ]
    tasks = []
    async with aiohttp.ClientSession() as session:
        for url in urls:
            tasks.append(fetch(session, url))
        htmls = await asyncio.gather(*tasks)
        for html in htmls:
            print(html[:100])

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

1
Ви можете розробити більше URL-адрес? Немає сенсу мати лише одну URL-адресу, коли питання стосується паралельного запиту http.
анонімний

Легенда. Дякую! Чудово працює
Адам

@ospider Як цей код можна модифікувати, щоб надати 10-казові URL-адреси, використовуючи паралельно 100 запитів? Ідея полягає у використанні всіх 100 слотів одночасно, а не в очікуванні доставки 100, щоб розпочати наступні 100.
Антоан Мілков

@AntoanMilkov Це вже інше питання, на яке не можна відповісти в області коментарів.
ospider

@ospider Ви праві, ось питання: stackoverflow.com/questions/56523043/…
Антоан Мілков

11

Наразі запити не підтримуються, asyncioі не планується надавати таку підтримку. Цілком ймовірно, що ви могли б реалізувати спеціальний "Транспортний адаптер" (про що йдеться тут ), який вміє використовувати asyncio.

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


Посилання веде до 404.
CodeBiker

8

У статті Pimin Konstantin Kefaloukos Легкі паралельні запити HTTP з Python та asyncio :

Щоб мінімізувати загальний час завершення, ми могли б збільшити розмір пулу потоків, щоб відповідати кількості запитів, які ми повинні зробити. На щастя, це легко зробити, як ми побачимо далі. Список наведених нижче кодів є прикладом того, як зробити двадцять асинхронних запитів HTTP з пулом потоків з двадцяти робочих ниток:

# Example 3: asynchronous requests with larger thread pool
import asyncio
import concurrent.futures
import requests

async def main():

    with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:

        loop = asyncio.get_event_loop()
        futures = [
            loop.run_in_executor(
                executor, 
                requests.get, 
                'http://example.org/'
            )
            for i in range(20)
        ]
        for response in await asyncio.gather(*futures):
            pass


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

2
Проблема в цьому полягає в тому, що якщо мені потрібно запустити 10000 запитів із шматками 20 виконавців, мені доведеться чекати, коли всі 20 виконавців закінчать, щоб почати з наступних 20, правда? Я не можу цього зробити, for i in range(10000)тому що один запит може вийти з ладу або закінчитися, чи не так?
Sanandrea

1
Чи можете ви пояснити, чому вам потрібен асинціо, коли ви можете зробити те саме, використовуючи ThreadPoolExecutor?
Асаф Пінхассі

@lya Rusin Виходячи з того, що ми встановлюємо кількість max_workers? Це стосується кількості процесорів та потоків?
alt-f4
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.