Yêu cầu không đồng bộ với yêu cầu Python


142

Tôi đã thử mẫu được cung cấp trong tài liệu của thư viện yêu cầu cho python.

Với async.map(rs), tôi nhận được mã phản hồi, nhưng tôi muốn nhận nội dung của từng trang được yêu cầu. Điều này, ví dụ, không hoạt động:

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

Có lẽ các phản ứng bạn nhận được có cơ thể trống rỗng?
Mariusz Jamro 2/212

Làm việc cho tôi. Vui lòng gửi toàn bộ lỗi bạn nhận được.
Chewie

không có lỗi nó chỉ chạy mãi mãi bởi các url thử nghiệm được cung cấp.
trbck

nó rõ ràng xuất hiện khi tôi sử dụng các url trên https. http đang hoạt động tốt
trbck 2/212

Hình như requests-threadstồn tại bây giờ.
OrangeDog

Câu trả lời:


154

Ghi chú

Câu trả lời dưới đây không áp dụng cho các yêu cầu v0.13.0 +. Các chức năng không đồng bộ đã được chuyển đến grequests sau khi câu hỏi này được viết. Tuy nhiên, bạn chỉ có thể thay thế requestsbằng grequestsbên dưới và nó sẽ hoạt động.

Tôi đã để lại câu trả lời này như là để phản ánh câu hỏi ban đầu về việc sử dụng các yêu cầu <v0.13.0.


Để thực hiện nhiều tác vụ async.map không đồng bộ, bạn phải:

  1. Xác định hàm cho những gì bạn muốn làm với từng đối tượng (nhiệm vụ của bạn)
  2. Thêm chức năng đó như một cái móc sự kiện trong yêu cầu của bạn
  3. Gọi async.mapvào danh sách tất cả các yêu cầu / hành động

Thí dụ:

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
Ý tưởng tuyệt vời đã để lại nhận xét của bạn: do sự cố tương thích giữa các yêu cầu mới nhất và các yêu cầu (thiếu tùy chọn max_retries trong các yêu cầu 1.1.0) tôi đã phải hạ cấp các yêu cầu để truy xuất async và tôi thấy rằng chức năng không đồng bộ đã được di chuyển với các phiên bản 0.13+ ( pypi.python.org/pypi/requests )
outforawhile

1
Câu hỏi ngớ ngẩn: sự gia tăng tốc độ của việc sử dụng các grequest trái ngược với các yêu cầu đơn giản là gì? Những giới hạn nào liên quan đến yêu cầu? ví dụ: đặt 3500 yêu cầu trong async.map có ổn không?
droope

10
from grequests import asynckhông hoạt động .. và định nghĩa này về một cái gì đó phù hợp với tôi def do_something(response, **kwargs):, tôi tìm thấy nó từ stackoverflow.com/questions/15594015/iêu
Allan Ruin

3
nếu cuộc gọi async.map vẫn chặn, thì làm thế nào là không đồng bộ này? Bên cạnh các yêu cầu được gửi không đồng bộ, việc truy xuất vẫn còn đồng bộ?
bryanph

3
Thay thế from requests import asyncbằng cách import grequests as asynclàm việc cho tôi.
Martin Thoma

79

asyncbây giờ là một mô-đun độc lập : grequests.

Xem tại đây: https://github.com/kennethreitz/grequests

Và ở đó: Phương pháp lý tưởng để gửi nhiều yêu cầu HTTP qua Python?

cài đặt:

$ pip install grequests

sử dụng:

xây dựng một ngăn xếp:

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)

gửi ngăn xếp

grequests.map(rs)

kết quả trông như

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

grequests dường như không đặt giới hạn cho các yêu cầu đồng thời, nghĩa là khi nhiều yêu cầu được gửi đến cùng một máy chủ.


11
Liên quan đến giới hạn đối với các yêu cầu đồng thời - bạn có thể chỉ định kích thước nhóm khi chạy bản đồ () / imap (). tức là grequests.map (rs, size = 20) để có 20 lần lấy đồng thời.
tổng

1
Cho đến bây giờ, điều này không có khả năng python3 (gevent không thể xây dựng v2.6 trên py3.4).
saarp

1
Tôi không hoàn toàn hiểu phần async. Nếu tôi để results = grequests.map(rs)mã sau khi dòng này bị chặn, tôi có thể thấy hiệu ứng async không?
Allan Ruin

47

Tôi đã thử nghiệm cả hai yêu cầu - tương laigrequest . Grequests nhanh hơn nhưng mang lại bản vá khỉ và các vấn đề bổ sung với sự phụ thuộc. yêu cầu-tương lai chậm hơn nhiều lần so với grequest. Tôi đã quyết định viết các yêu cầu của riêng mình và đơn giản được gói vào ThreadPoolExecutor và nó gần như nhanh như các grequest, nhưng không có sự phụ thuộc bên ngoài.

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

Loại ngoại lệ nào có thể ở đây?
Chậm Harry

requests.exceptions.Timeout
Hodza

2
Xin lỗi tôi không hiểu câu hỏi của bạn. Chỉ sử dụng một url trong nhiều chủ đề? Chỉ có một trường hợp tấn công DDoS))
Hodza

1
Tôi không hiểu tại sao câu trả lời này lại có quá nhiều sự ủng hộ. Câu hỏi OP là về các yêu cầu không đồng bộ. ThreadPoolExecutor chạy chủ đề. Có, bạn có thể thực hiện các yêu cầu trong nhiều luồng, nhưng đó sẽ không bao giờ là một chương trình không đồng bộ, vì vậy tôi làm thế nào nó có thể là một câu trả lời cho câu hỏi ban đầu?
nagylzs

1
Trên thực tế, câu hỏi là về cách tải URL song song. Và có, người thực thi nhóm luồng không phải là lựa chọn tốt nhất, tốt hơn là sử dụng async io, nhưng nó hoạt động tốt trong Python. Và tôi không hiểu tại sao các chủ đề không thể được sử dụng cho async? Điều gì nếu bạn cần chạy CPU bị ràng buộc không đồng bộ?
Hodza

29

có thể yêu cầu tương lai là một lựa chọn khác.

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)

Nó cũng được đề nghị trong tài liệu văn phòng . Nếu bạn không muốn liên quan đến gevent, thì đó là một điều tốt.


1
Một trong những giải pháp đơn giản nhất. Có thể tăng số lượng yêu cầu đồng thời bằng cách xác định tham số max_workers
Jose Cherian

1
Thật tuyệt khi thấy một ví dụ về tỷ lệ này vì vậy chúng tôi không sử dụng một tên biến cho mỗi mục để lặp lại.
dùng1717828

có một luồng cho mỗi yêu cầu là một sự lãng phí tài nguyên! không thể thực hiện đồng thời 500 yêu cầu, nó sẽ giết cpu của bạn. điều này không bao giờ nên được coi là một giải pháp tốt.
Corneliu Maftuleac

@CorneliuMaftuleac điểm tốt. Về việc sử dụng luồng, bạn chắc chắn cần quan tâm đến nó và thư viện cung cấp một tùy chọn để kích hoạt nhóm luồng hoặc nhóm xử lý. ThreadPoolExecutor(max_workers=10)
Dreampuf

@Dreampuf xử lý hồ bơi tôi tin là còn tồi tệ hơn?
Corneliu Maftuleac

11

Tôi có rất nhiều vấn đề với hầu hết các câu trả lời được đăng - họ sử dụng các thư viện không dùng nữa với các tính năng hạn chế hoặc cung cấp giải pháp với quá nhiều phép thuật trong việc thực hiện yêu cầu, gây khó khăn cho việc xử lý lỗi. Nếu chúng không thuộc một trong các loại trên, chúng là thư viện của bên thứ 3 hoặc không dùng nữa.

Một số giải pháp hoạt động hoàn toàn trong các yêu cầu http, nhưng các giải pháp không phù hợp với bất kỳ loại yêu cầu nào khác, đó là lố bịch. Một giải pháp tùy biến cao là không cần thiết ở đây.

Chỉ cần sử dụng thư viện tích hợp python asynciolà đủ để thực hiện các yêu cầu không đồng bộ thuộc bất kỳ loại nào, cũng như cung cấp đủ tính trôi chảy để xử lý lỗi cụ thể và phức tạp.

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

Cách thức hoạt động rất đơn giản. Bạn đang tạo một loạt các tác vụ bạn muốn xảy ra không đồng bộ, sau đó yêu cầu một vòng lặp để thực thi các tác vụ đó và thoát khi hoàn thành. Không có thư viện bổ sung bị thiếu bảo trì, không thiếu chức năng cần thiết.


2
Nếu tôi hiểu chính xác, điều này sẽ chặn vòng lặp sự kiện trong khi thực hiện cuộc gọi GRPC và HTTP? Vậy nếu những cuộc gọi này mất vài giây để hoàn thành, toàn bộ vòng lặp sự kiện của bạn sẽ chặn trong vài giây? Để tránh điều này, bạn cần sử dụng các thư viện GRPC hoặc HTTP async. Sau đó, bạn có thể làm ví dụ await response = requests.get(URL). Không?
ngày 23

Thật không may, khi thử điều này, tôi thấy rằng việc tạo một trình bao bọc xung quanh requestshầu như không nhanh hơn (và trong một số trường hợp chậm hơn) so với việc chỉ gọi một danh sách các URL một cách đồng bộ. Ví dụ: yêu cầu điểm cuối mất 3 giây để phản hồi 10 lần bằng chiến lược trên mất khoảng 30 giây. Nếu bạn muốn asynchiệu suất thực sự , bạn cần sử dụng một cái gì đó như aiohttp.
DragonBobZ

8

Tôi biết điều này đã bị đóng trong một thời gian, nhưng tôi nghĩ rằng nó có thể hữu ích để quảng bá một giải pháp async khác được xây dựng trên thư viện yêu cầu.

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

Các tài liệu ở đây: http://pythonhosted.org/simple-requests/


@YSY Vui lòng gửi một vấn đề: github.com/ctheiss/simple-requests/issues ; Tôi thực sự sử dụng thư viện này hàng ngàn lần một ngày.
Khỉ Boson

Boston, làm thế nào để bạn xử lý lỗi 404/500? Còn url https thì sao? sẽ đánh giá cao một snipping hỗ trợ hàng ngàn url. bạn có thể vui lòng dán một ví dụ? cảm ơn
YSY

@YSY Theo mặc định, lỗi 404/500 đưa ra một ngoại lệ. Hành vi này có thể được ghi đè (xem pythonhosted.org/simple-requests/iêu ). Các url HTTPS rất khó khăn do sự phụ thuộc vào gevent, hiện đang có một lỗi nổi bật về vấn đề này ( github.com/gevent/gevent/issues/477 ). Có một shim trong vé bạn có thể chạy, nhưng nó vẫn sẽ đưa ra cảnh báo cho các máy chủ SNI (nhưng nó sẽ hoạt động). Đối với snipping, tôi sợ tất cả các tập quán của tôi ở công ty của tôi và đóng cửa. Nhưng tôi đảm bảo với bạn rằng chúng tôi thực hiện hàng ngàn yêu cầu qua hàng chục công việc.
Khỉ Boson

Thư viện trông bóng bẩy đối với sự tương tác. Python3 + có sử dụng được không? Xin lỗi không thể thấy bất kỳ đề cập.
Isaac Philip

@Jethro hoàn toàn đúng, thư viện sẽ cần phải viết lại toàn bộ vì các công nghệ cơ bản khá khác nhau trong Python 3. Hiện tại, thư viện đã "hoàn tất" nhưng chỉ hoạt động cho 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
đây là yêu cầu "bình thường" trong các chủ đề. ví dụ mua không tệ là lạc đề.
Nick


2

Tôi đã sử dụng các yêu cầu python cho các cuộc gọi không đồng bộ với API chính của github trong một thời gian.

Ví dụ, xem mã ở đây:

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

Kiểu trăn này có thể không phải là ví dụ rõ ràng nhất, nhưng tôi có thể đảm bảo với bạn rằng mã hoạt động. Hãy cho tôi biết nếu điều này gây nhầm lẫn với bạn và tôi sẽ ghi lại nó.


2

Bạn có thể sử dụng httpxcho điều đó.

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

nếu bạn muốn một cú pháp chức năng, gamla lib kết thúc điều này get_async.

Sau đó bạn có thể làm


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

Các 10là thời gian chờ trong vài giây.

(từ chối trách nhiệm: Tôi là tác giả của nó)


respxcho mocking / thử nghiệm :)
rlat

0

Tôi cũng đã thử một số thứ bằng cách sử dụng các phương thức không đồng bộ trong python, bao giờ tôi đã gặp may mắn hơn nhiều khi sử dụng xoắn cho lập trình không đồng bộ. Nó có ít vấn đề hơn và được ghi chép lại. Đây là một liên kết của một cái gì đó tương tự với những gì bạn đang cố gắng trong xoắn.

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


Twisted là lỗi thời. Sử dụng HTTPX thay thế.
AmirHossein
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.