python grequests极限_关于python:限制/限制GRequests中HTTP请求的速率

我正在用python 2.7.3编写一个小脚本,其中包含grequests和lxml,它允许我从各种网站收集一些可收集的卡片价格,并对它们进行比较。问题是其中一个网站限制了请求的数量,如果超过了这个数量,就会返回HTTP错误429。

是否有一种方法可以添加限制grequest中的请求数,以使我不超过每秒指定的请求数?另外-如果发生HTTP 429,如何在一段时间后使grequestes重试?

旁注——他们的限额低得离谱。大约每15秒有8个请求。我多次使用浏览器破坏它,只是刷新等待价格变化的页面。

您是否使用grequest在他们的站点注册多个URL?如果是这样,最好同步请求他们的站点,这样您就可以轻松地控制重试。

我同意@dm03514-你不需要grequest。我最近读到一个算法,它可以针对这类问题进行优化。

我试图同时访问同一站点的多个URL,因为我希望能够同时监控多张ATR卡的价格。

@这听起来很有趣。您介意分享一下同步方法吗?

我要自己回答我自己的问题,因为我必须自己解决这个问题,而且这方面的信息似乎很少。

想法如下。与grequest一起使用的每个请求对象在创建时都可以将会话对象作为参数。另一方面,会话对象可以安装HTTP适配器,在发出请求时使用这些适配器。通过创建我们自己的适配器,我们可以截获请求,并以最适合我们应用程序的方式对请求进行速率限制。在我的例子中,我最后得到了下面的代码。

用于限制的对象:

DEFAULT_BURST_WINDOW = datetime.timedelta(seconds=5)

DEFAULT_WAIT_WINDOW = datetime.timedelta(seconds=15)

class BurstThrottle(object):

max_hits = None

hits = None

burst_window = None

total_window = None

timestamp = None

def __init__(self, max_hits, burst_window, wait_window):

self.max_hits = max_hits

self.hits = 0

self.burst_window = burst_window

self.total_window = burst_window + wait_window

self.timestamp = datetime.datetime.min

def throttle(self):

now = datetime.datetime.utcnow()

if now < self.timestamp + self.total_window:

if (now < self.timestamp + self.burst_window) and (self.hits < self.max_hits):

self.hits += 1

return datetime.timedelta(0)

else:

return self.timestamp + self.total_window - now

else:

self.timestamp = now

self.hits = 1

return datetime.timedelta(0)

HTTP适配器:

class MyHttpAdapter(requests.adapters.HTTPAdapter):

throttle = None

def __init__(self, pool_connections=requests.adapters.DEFAULT_POOLSIZE,

pool_maxsize=requests.adapters.DEFAULT_POOLSIZE, max_retries=requests.adapters.DEFAULT_RETRIES,

pool_block=requests.adapters.DEFAULT_POOLBLOCK, burst_window=DEFAULT_BURST_WINDOW,

wait_window=DEFAULT_WAIT_WINDOW):

self.throttle = BurstThrottle(pool_maxsize, burst_window, wait_window)

super(MyHttpAdapter, self).__init__(pool_connections=pool_connections, pool_maxsize=pool_maxsize,

max_retries=max_retries, pool_block=pool_block)

def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):

request_successful = False

response = None

while not request_successful:

wait_time = self.throttle.throttle()

while wait_time > datetime.timedelta(0):

gevent.sleep(wait_time.total_seconds(), ref=True)

wait_time = self.throttle.throttle()

response = super(MyHttpAdapter, self).send(request, stream=stream, timeout=timeout,

verify=verify, cert=cert, proxies=proxies)

if response.status_code != 429:

request_successful = True

return response

设置:

requests_adapter = adapter.MyHttpAdapter(

pool_connections=__CONCURRENT_LIMIT__,

pool_maxsize=__CONCURRENT_LIMIT__,

max_retries=0,

pool_block=False,

burst_window=datetime.timedelta(seconds=5),

wait_window=datetime.timedelta(seconds=20))

requests_session = requests.session()

requests_session.mount('http://', requests_adapter)

requests_session.mount('https://', requests_adapter)

unsent_requests = (grequests.get(url,

hooks={'response': handle_response},

session=requests_session) for url in urls)

grequests.map(unsent_requests, size=__CONCURRENT_LIMIT__)

请看一下自动请求限制:https://pypi.python.org/pypi/requeststhrottler/0.2.2

您可以在每个请求之间设置固定的延迟量,也可以在固定的秒数内设置发送的请求数(基本上相同):

import requests

from requests_throttler import BaseThrottler

request = requests.Request(method='GET', url='http://www.google.com')

reqs = [request for i in range(0, 5)]  # An example list of requests

with BaseThrottler(name='base-throttler', delay=1.5) as bt:

throttled_requests = bt.multi_submit(reqs)

其中函数multi_submit返回ThrottledRequest的列表(见末尾的doc:link)。

然后您可以访问响应:

for tr in throttled_requests:

print tr.response

或者,您可以通过指定在固定时间内发送的一个或多个请求(例如,每60秒发送15个请求)来实现这一点:

import requests

from requests_throttler import BaseThrottler

request = requests.Request(method='GET', url='http://www.google.com')

reqs = [request for i in range(0, 5)]  # An example list of requests

with BaseThrottler(name='base-throttler', reqs_over_time=(15, 60)) as bt:

throttled_requests = bt.multi_submit(reqs)

两种解决方案都可以在不使用with语句的情况下实现:

import requests

from requests_throttler import BaseThrottler

request = requests.Request(method='GET', url='http://www.google.com')

reqs = [request for i in range(0, 5)]  # An example list of requests

bt = BaseThrottler(name='base-throttler', delay=1.5)

bt.start()

throttled_requests = bt.multi_submit(reqs)

bt.shutdown()

有关详细信息,请访问:http://pythonhosted.org/requeststhrottler/index.html

它能和grequests一起工作吗?

我也有类似的问题。这是我的解决方案。在你的情况下,我会:

def worker():

with rate_limit('slow.domain.com', 2):

response = requests.get('https://slow.domain.com/path')

text = response.text

# Use `text`

假设你有多个域名,我会设置一个字典映射(domain, delay),这样你就不会达到你的速率限制。

此代码假定您将使用gevent和monkey补丁。

from contextlib import contextmanager

from gevent.event import Event

from gevent.queue import Queue

from time import time

def rate_limit(resource, delay, _queues={}):

"""Delay use of `resource` until after `delay` seconds have passed.

Example usage:

def worker():

with rate_limit('foo.bar.com', 1):

response = requests.get('https://foo.bar.com/path')

text = response.text

# use `text`

This will serialize and delay requests from multiple workers for resource

'foo.bar.com' by 1 second.

"""

if resource not in _queues:

queue = Queue()

gevent.spawn(_watch, queue)

_queues[resource] = queue

return _resource_manager(_queues[resource], delay)

def _watch(queue):

"Watch `queue` and wake event listeners after delay."

last = 0

while True:

event, delay = queue.get()

now = time()

if (now - last) < delay:

gevent.sleep(delay - (now - last))

event.set()   # Wake worker but keep control.

event.clear()

event.wait()  # Yield control until woken.

last = time()

@contextmanager

def _resource_manager(queue, delay):

"`with` statement support for `rate_limit`."

event = Event()

queue.put((event, delay))

event.wait() # Wait for queue watcher to wake us.

yield

event.set()  # Wake queue watcher.

看起来没有任何简单的机制来处理请求或grequests代码中的这个内置项。唯一的钩子似乎是周围的反应。

这里有一个超级黑客的工作,至少证明了这是可能的-我修改了grequest,以保留发出请求的时间列表,并休眠异步请求的创建,直到每秒请求数低于最大值。

class AsyncRequest(object):

def __init__(self, method, url, **kwargs):

print self,'init'

waiting=True

while waiting:

if len([x for x in q if x > time.time()-15]) < 8:

q.append(time.time())

waiting=False

else:

print self,'snoozing'

gevent.sleep(1)

您可以使用grequests.imap()以交互方式观看

import time

import rg

urls = [

'http://www.heroku.com',

'http://python-tablib.org',

'http://httpbin.org',

'http://python-requests.org',

'http://kennethreitz.com',

'http://www.cnn.com',

]

def print_url(r, *args, **kwargs):

print(r.url),time.time()

hook_dict=dict(response=print_url)

rs = (rg.get(u, hooks=hook_dict) for u in urls)

for r in rg.imap(rs):

print r

我希望有一个更优雅的解决方案,但到目前为止我找不到。在会话和适配器中四处查看。也许游泳池管理员可以增加?

此外,我不会将此代码投入生产中,'q'列表永远不会被修剪,最终会变得相当大。另外,我不知道它是否真的像广告上说的那样工作。当我查看控制台输出时,它看起来就是这样。

呃。只要看看这段代码,我就知道是凌晨3点。该睡觉了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值