簡體   English   中英

Python API 速率限制 - 如何在全局范圍內限制 API 調用

[英]Python API Rate Limiting - How to Limit API Calls Globally

我正在嘗試限制代碼中的 API 調用。 我已經找到了一個不錯的 python 庫ratelimiter==1.0.2.post0 https://pypi.python.org/pypi/ratelimiter

但是,這個庫只能限制本地范圍內的速率。 即)在函數和循環中

# Decorator
@RateLimiter(max_calls=10, period=1)
def do_something():
    pass


# Context Manager
rate_limiter = RateLimiter(max_calls=10, period=1)

for i in range(100):
    with rate_limiter:
        do_something()

因為我有幾個函數在不同的地方進行 API 調用,所以我想將 API 調用限制在全局范圍內。

例如,假設我想將 API 調用限制為每秒一次 而且,假設我有函數xy ,其中進行了兩個 API 調用。

@rate(...)
def x():
   ...

@rate(...)
def y():
   ...

通過使用limiter裝飾函數,我可以限制這兩個函數的速率。

但是,如果我順序執行上述兩個函數,它會失去對全局范圍內 API 調用數量的跟蹤,因為它們彼此不知道。 因此, y將在x執行后立即調用,而無需再等待一秒鍾。 並且,這將違反每秒一次的限制。

有什么方法或庫可以用來在 python 中全局限制速率?

畢竟,我實現了自己的Throttler類。 通過將每個 API 請求代理到request方法,我們可以跟蹤所有 API 請求。 利用傳遞函數作為request方法參數,它還緩存結果以減少API調用。

class TooManyRequestsError(Exception):
    def __str__(self):
        return "More than 30 requests have been made in the last five seconds."


class Throttler(object):
    cache = {}

    def __init__(self, max_rate, window, throttle_stop=False, cache_age=1800):
        # Dict of max number of requests of the API rate limit for each source
        self.max_rate = max_rate
        # Dict of duration of the API rate limit for each source
        self.window = window
        # Whether to throw an error (when True) if the limit is reached, or wait until another request
        self.throttle_stop = throttle_stop
        # The time, in seconds, for which to cache a response
        self.cache_age = cache_age
        # Initialization
        self.next_reset_at = dict()
        self.num_requests = dict()

        now = datetime.datetime.now()
        for source in self.max_rate:
            self.next_reset_at[source] = now + datetime.timedelta(seconds=self.window.get(source))
            self.num_requests[source] = 0

    def request(self, source, method, do_cache=False):
        now = datetime.datetime.now()

        # if cache exists, no need to make api call
        key = source + method.func_name
        if do_cache and key in self.cache:
            timestamp, data = self.cache.get(key)
            logging.info('{} exists in cached @ {}'.format(key, timestamp))

            if (now - timestamp).seconds < self.cache_age:
                logging.info('retrieved cache for {}'.format(key))
                return data

        # <--- MAKE API CALLS ---> #

        # reset the count if the period passed
        if now > self.next_reset_at.get(source):
            self.num_requests[source] = 0
            self.next_reset_at[source] = now + datetime.timedelta(seconds=self.window.get(source))

        # throttle request
        def halt(wait_time):
            if self.throttle_stop:
                raise TooManyRequestsError()
            else:
                # Wait the required time, plus a bit of extra padding time.
                time.sleep(wait_time + 0.1)

        # if exceed max rate, need to wait
        if self.num_requests.get(source) >= self.max_rate.get(source):
            logging.info('back off: {} until {}'.format(source, self.next_reset_at.get(source)))
            halt((self.next_reset_at.get(source) - now).seconds)

        self.num_requests[source] += 1
        response = method()  # potential exception raise

        # cache the response
        if do_cache:
            self.cache[key] = (now, response)
            logging.info('cached instance for {}, {}'.format(source, method))

        return response

我遇到了同樣的問題,我有一堆不同的函數調用相同的 API,我想讓速率限制在全球范圍內工作。 我最終做的是創建一個啟用了速率限制的空函數。

PS:我使用這里找到的不同速率限制庫: https : //pypi.org/project/ratelimit/

from ratelimit import limits, sleep_and_retry

# 30 calls per minute
CALLS = 30
RATE_LIMIT = 60

@sleep_and_retry
@limits(calls=CALLS, period=RATE_LIMIT)
def check_limit():
''' Empty function just to check for calls to API '''
return

然后我只是在調用 API 的每個函數的開頭調用該函數:

def get_something_from_api(http_session, url):
    check_limit()
    response = http_session.get(url)
    return response

如果達到限制,程序將休眠直到(在我的情況下)60 秒過去,然后恢復正常。

有很多花哨的庫可以提供漂亮的裝飾器和特殊的安全功能,但以下應該與django.core.cache或任何其他帶有getset方法的緩存一起使用:

def hit_rate_limit(key, max_hits, max_hits_interval):
    '''Implement a basic rate throttler. Prevent more than max_hits occurring
    within max_hits_interval time period (seconds).'''
    # Use the django cache, but can be any object with get/set
    from django.core.cache import cache
    hit_count = cache.get(key) or 0
    logging.info("Rate Limit: %s --> %s", key, hit_count)
    if hit_count > max_hits:
        return True
    cache.set(key, hit_count + 1, max_hits_interval)
    return False

許多 API 提供者限制開發人員進行過多的 API 調用。

Python ratelimit包引入了一個函數裝飾器,可防止函數被調用的頻率超過 API 提供者所允許的頻率。

從 ratelimit 導入限制

import requests
TIME_PERIOD = 900   # time period in seconds

@limits(calls=15, period=TIME_PERIOD)
def call_api(url):
    response = requests.get(url)

    if response.status_code != 200:
        raise Exception('API response: {}'.format(response.status_code))
    return response

注意:此函數將無法在 15 分鍾內進行超過 15 個 API 調用。

添加到 Sunil 答案中,您需要添加 @sleep_and_retry 裝飾器,否則您的代碼將在達到速率限制時中斷:

@sleep_and_retry
@limits(calls=0.05, period=1)
def api_call(url, api_key):
    r = requests.get(
        url,
        headers={'X-Riot-Token': api_key}
        )
    if r.status_code != 200:
        raise Exception('API Response: {}'.format(r.status_code))
    return r

使用 Python 標准庫:

import threading
from time import time, sleep

b = threading.Barrier(2)

def belay(s=1):
    """Block the main thread for `s` seconds."""
    while True:
        b.wait()
        sleep(s)

def request_something():
    b.wait()
    print(f'something at {time()}')

def request_other():
    b.wait()
    print(f'or other at {time()}')
    

if __name__ == '__main__':

    thread = threading.Thread(target=belay)
    thread.daemon = True
    thread.start()

    # request a lot of things
    i = 0
    while (i := i+1) < 5:
        request_something()
        request_other()

打印的每個時間戳之間大約有s秒。 因為主線程等待而不是休眠,它響應請求所花費的時間與請求之間的(最小)時間無關。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM