限制/限制grequest中HTTP请求的速率

2024-06-30 16:15:20 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在用Python 2.7.3编写一个带有GRequests和lxml的小脚本,它允许我从不同的网站收集一些可收藏的卡片价格并进行比较。问题是其中一个网站限制了请求的数量,如果我超过了它,就会发送回HTTP错误429。

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

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


Tags: 方法脚本http数量网站错误浏览器价格
3条回答

看起来没有任何简单的机制来处理请求或grequests代码中的这个构建。唯一的陷阱似乎是回应。

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

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点了。该上床睡觉了。

查看此以了解自动请求限制: 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一起使用的每个请求对象在创建时都可以将会话对象作为参数。另一方面,会话对象可以安装在发出请求时使用的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__)

相关问题 更多 >