Làm cách nào tôi có thể sử dụng các yêu cầu trong asyncio?


126

Tôi muốn thực hiện các tác vụ yêu cầu http song song asyncio, nhưng tôi thấy điều đó python-requestssẽ chặn vòng lặp sự kiện asyncio. Tôi đã tìm thấy aiohttp nhưng nó không thể cung cấp dịch vụ yêu cầu http bằng proxy http.

Vì vậy, tôi muốn biết liệu có cách nào để thực hiện các yêu cầu http không đồng bộ với sự trợ giúp của không asyncio.


1
Nếu bạn chỉ gửi yêu cầu, bạn có thể sử dụng subprocessđể song song mã của mình.
WeaselFox

Phương pháp này có vẻ không thanh lịch
flyer

Hiện tại có một cổng asyncio của các yêu cầu. github.com/rdbhost/yieldfromRequests
Rdbhost

Câu trả lời:


181

Để sử dụng các yêu cầu (hoặc bất kỳ thư viện chặn nào khác) với asyncio, bạn có thể sử dụng BaseEventLoop.run_in_executor để chạy một hàm trong một luồng khác và lấy kết quả từ nó để lấy kết quả. Ví dụ:

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

Điều này sẽ nhận được cả hai phản ứng song song.

Với python 3.5, bạn có thể sử dụng cú pháp await/ mới 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())

Xem PEP0492 để biết thêm.


5
Bạn có thể giải thích chính xác làm thế nào điều này hoạt động? Tôi không hiểu làm thế nào điều này không chặn.
Scott Coates

32
@christian nhưng nếu nó chạy đồng thời trong một luồng khác, thì đó có phải là đánh bại điểm của asyncio không?
Scott Coates

21
@scoarescoare Đó là phần 'nếu bạn làm đúng' xuất hiện - phương thức bạn chạy trong trình thực thi phải được khép kín ((hầu hết) như request.get trong ví dụ trên). Bằng cách đó, bạn không phải đối phó với bộ nhớ dùng chung, khóa, v.v. và các phần phức tạp trong chương trình của bạn vẫn được phân luồng đơn nhờ vào asyncio.
christian

5
@scoarescoare Trường hợp sử dụng chính là để tích hợp với các thư viện IO không hỗ trợ asyncio. Chẳng hạn, tôi đang thực hiện một số công việc với giao diện SOAP thực sự cổ xưa và tôi đang sử dụng thư viện suds-Jurko làm giải pháp "ít tệ nhất". Tôi đang cố gắng tích hợp nó với một máy chủ asyncio, vì vậy tôi đang sử dụng run_in_executor để thực hiện các cuộc gọi suds chặn theo cách có vẻ không đồng bộ.
Lucretiel

10
Thực sự mát mẻ mà công trình này và như vậy là dễ dàng như vậy cho các công cụ di sản, nhưng cần phải nhấn mạnh sử dụng này một threadpool OS và do đó không quy mô như một lib hướng asyncio đúng như aiohttp làm
jsalter

78

aiohttp có thể được sử dụng với HTTP proxy:

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

Trình kết nối làm gì ở đây?
Markus Meskanen 7/10/2015

Nó cung cấp kết nối thông qua máy chủ proxy
mindmaster

16
Đây là một giải pháp tốt hơn nhiều sau đó để sử dụng các yêu cầu trong một luồng riêng biệt. Vì nó thực sự không đồng bộ nên nó có chi phí sử dụng thấp hơn và sử dụng mem thấp hơn.
Thơm

14
cho python> = 3.5 thay thế @ asyncio.coroutine bằng "async" và "suất từ" bằng "await"
James

40

Các câu trả lời ở trên vẫn đang sử dụng các coroutines kiểu Python 3.4 cũ. Đây là những gì bạn sẽ viết nếu bạn có Python 3.5+.

aiohttp hỗ trợ http proxy ngay bây giờ

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
bạn có thể xây dựng với nhiều url hơn? Không có nghĩa là chỉ có một url khi câu hỏi về yêu cầu http song song.
ẩn danh

Huyền thoại. Cảm ơn bạn! Hoạt động tuyệt vời
Adam

@ospider Làm thế nào mã này có thể được sửa đổi để phân phối các URL nói 10k bằng cách sử dụng song song 100 yêu cầu? Ý tưởng là sử dụng đồng thời tất cả 100 vị trí, không phải đợi 100 được giao để bắt đầu 100.
Antoan Milkov

@AntoanMilkov Đó là một câu hỏi khác nhau không thể trả lời trong khu vực bình luận.
ospider

@ospider Bạn nói đúng, đây là câu hỏi: stackoverflow.com/questions/56523043/NH
Antoan Milkov

11

Yêu cầu hiện không hỗ trợ asynciovà không có kế hoạch cung cấp hỗ trợ như vậy. Có khả năng là bạn có thể triển khai "Bộ điều hợp vận chuyển" tùy chỉnh (như được thảo luận ở đây ) biết cách sử dụng asyncio.

Nếu tôi thấy mình có lúc đó là điều gì đó tôi thực sự có thể xem xét, nhưng tôi không thể hứa bất cứ điều gì.


Liên kết dẫn đến 404.
CodeBiker

8

Có một trường hợp tốt về async / đang chờ các vòng lặp và xâu chuỗi trong một bài viết của Pimin Konstantin Kefaloukos Các yêu cầu HTTP song song dễ dàng với Python và asyncio :

Để giảm thiểu tổng thời gian hoàn thành, chúng tôi có thể tăng kích thước của nhóm luồng để phù hợp với số lượng yêu cầu chúng tôi phải thực hiện. May mắn thay, điều này là dễ dàng để làm như chúng ta sẽ thấy tiếp theo. Danh sách mã dưới đây là một ví dụ về cách thực hiện hai mươi yêu cầu HTTP không đồng bộ với nhóm luồng gồm hai mươi luồng công việc:

# 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
Vấn đề với điều này là nếu tôi cần chạy 10000 yêu cầu với số lượng 20 người thực thi, tôi phải đợi tất cả 20 người thực thi kết thúc để bắt đầu với 20 yêu cầu tiếp theo, phải không? Tôi không thể làm for i in range(10000)vì một yêu cầu có thể thất bại hoặc hết thời gian, phải không?
Sanandrea

1
Bạn có thể giải thích tại sao bạn cần asyncio khi bạn có thể làm tương tự chỉ bằng cách sử dụng ThreadPoolExecutor?
Asaf Pinhassi

@lya Rusin Dựa vào cái gì, chúng ta có đặt số lượng max_workers không? Nó có phải làm với số lượng CPU và luồng không?
alt-f4
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.