背景
调用第三方接口时,常见的问题就是调用频率过快,从而导致一系列的问题:可能会被封IP;可能会被封号;也可能会被限流等问题。
解决思路
最简单的方法:限制一个时间段内的调用频率
ratelimit实现控制调用API的频率
例如以下的测试代码,使用多线程模拟请求调用test2()
函数
import time
import threading
def test2():
time.sleep(3)
print('调用了函数')
def run_func():
print('模拟普通请求...')
test2()
if __name__ == '__main__':
all_t = []
for i in range(10):
t = threading.Thread(target=run_func)
all_t.append(t)
for n in all_t:
n.start()
for k in all_t:
k.join()
上面测试代码中,启动的10个线程中在同一个时刻调用了test2()
函数,可以理解为1s内请求API10次。下面需要引入ratelimit
库控制调用的频率。
import time
from ratelimit import limits
@limits(calls=1, period=1)
def test2():
time.sleep(3)
print('调用了函数')
默认情况下,在15min内允许15次请求,超出的请求会被丢弃。在这里设置calls=1,period=1
,表示在1s内只允许请求1次。
但是,运行上面代码时,发生了其中一部分线程发生了ratelimit.exception.RateLimitException: too many calls
错误,这是为什么呢?
增加阻塞等待
刚刚上面提到,在设置的时间内,请求次数超出设置的值时,其他请求请求会被丢弃。因此,需要处理返回RateLimitException
错误的情况。
增加一个装饰器,使得超出的请求阻塞
import time
from functools import wraps
from ratelimit import RateLimitException
def sleep_and_retry(func):
@wraps(func)
def wrapper(*args, **kargs):
while True:
try:
return func(*args, **kargs)
except RateLimitException as exception:
time.sleep(exception.period_remaining)
return wrapper
结果
增加上面的装饰器之后,超出的请求也实现堵塞,并能正常相应请求。
完整代码
import threading
import time
from functools import wraps
from ratelimit import limits, RateLimitException
def sleep_and_retry(func):
@wraps(func)
def wrapper(*args, **kargs):
while True:
try:
return func(*args, **kargs)
except RateLimitException as exception:
time.sleep(exception.period_remaining)
return wrapper
@sleep_and_retry
@limits(calls=1, period=1)
def test2():
time.sleep(3)
print('调用了函数')
def run_func():
print('模拟普通请求...')
test2()
if __name__ == '__main__':
all_t = []
for i in range(10):
t = threading.Thread(target=run_func)
all_t.append(t)
for n in all_t:
n.start()
for k in all_t:
k.join()