如何避免HTTP错误429(请求过多)python


91

我尝试使用Python登录网站并从多个网页收集信息,但出现以下错误:

Traceback (most recent call last):
  File "extract_test.py", line 43, in <module>
    response=br.open(v)
  File "/usr/local/lib/python2.7/dist-packages/mechanize/_mechanize.py", line 203, in open
    return self._mech_open(url, data, timeout=timeout)
  File "/usr/local/lib/python2.7/dist-packages/mechanize/_mechanize.py", line 255, in _mech_open
    raise response
mechanize._response.httperror_seek_wrapper: HTTP Error 429: Unknown Response Code

我曾经使用过time.sleep()并且可以正常工作,但是似乎并不智能且不可靠,还有其他方法可以避免此错误吗?

这是我的代码:

import mechanize
import cookielib
import re
first=("example.com/page1")
second=("example.com/page2")
third=("example.com/page3")
fourth=("example.com/page4")
## I have seven URL's I want to open

urls_list=[first,second,third,fourth]

br = mechanize.Browser()
# Cookie Jar
cj = cookielib.LWPCookieJar()
br.set_cookiejar(cj)

# Browser options 
br.set_handle_equiv(True)
br.set_handle_redirect(True)
br.set_handle_referer(True)
br.set_handle_robots(False)

# Log in credentials
br.open("example.com")
br.select_form(nr=0)
br["username"] = "username"
br["password"] = "password"
br.submit()

for url in urls_list:
        br.open(url)
        print re.findall("Some String")

6
没有办法解决,这是服务器端的一项强制措施,用于跟踪您发出的请求数/时间单位。如果您超出此单位,则将被暂时阻止。某些服务器在标头中发送此信息,但是这种情况很少见。检查从服务器收到的标头,使用可用的信息sleep
2014年

Answers:


157

接收状态429 并非错误,这是另一台服务器“友善地”要求您停止发送垃圾邮件请求。显然,您的请求率太高,服务器不愿意接受。

您不应该试图“躲避”这一点,甚至不应该尝试通过欺骗IP来绕过服务器安全设置,而应该仅仅通过不发送太多请求来尊重服务器的回答。

如果一切设置正确,您还将收到“ Retry-after”标头以及429响应。此标头指定在进行另一个呼叫之前应等待的秒数。处理此“问题”的正确方法是读取此标头,然后将您的进程休眠许多秒钟。

您可以在此处找到有关状态429的更多信息:http : //tools.ietf.org/html/rfc6585#page-3


23
好吧,没有人说所有的Web服务器都配置正确。另外,由于大多数速率限制器都是通过IP识别访问者的,因此在动态共享IP的情况下,这可能会导致问题。如果您确信自己根本没有发送太多请求,但仍保持状态429,则可以考虑与站点管理员联系。
MRA

2
感谢您提及“重试”标题。我喜欢一个代码示例,以了解如何获取该值(我使用urllib进行机械化,无论哪种情况,我都认为标头不包含在引发的异常中)
MacFreek,

@MacFreek我尚未准备任何特定的Python代码示例,但我假设可以从以下问题的答案中获取一些有关如何检索响应标头的示例:stackoverflow.com/q/843392
MRA

谢谢@MRA。我发现,标头在例外情况下也可用:捕获之后HTTPError as my_exception,它my_exception.headers在至少对于urllib2中可用。
MacFreek

37

编写这段代码解决了我的问题:

requests.get(link, headers = {'User-agent': 'your bot 0.1'})


26
这个答案是不合理的,但是如果由于其他人的滥用而禁止了用户代理,则某些站点会自动返回错误代码429。即使仅发送了几个请求,如果仍然收到错误代码429,请尝试将用户代理设置为其他内容。
Ferry Boender'3

7
还要添加一些站点,除非发送了用户代理,否则某些站点会明确拒绝请求,并且您可能会收到无数其他响应:503/403 /一些通用索引页面。
user3791372

1
可以确认这一点。只是尝试将python与reddit交互,而没有设置用户代理,我总是收到错误代码
429。– Karrq

你能补充一些解释吗?
Tokci

29

正如MRA所说,您不应尝试躲避a 429 Too Many Requests,而应进行相应处理。根据您的用例,您有几种选择:

1)暂停您的过程。服务器通常Retry-after在响应中包含标头,其中包含您应等待的秒数,然后重试。请记住,使进程进入休眠状态可能会导致问题,例如在任务队列中,您应该稍后在该队列中重试该任务,以使工作人员腾出其他空间。

2)指数补偿。如果服务器未告知您等待的时间,则可以使用增加的间隔来重试请求。流行的任务队列Celery 内置了此功能。

3)令牌桶。如果您事先知道在给定时间内可以发出多少个请求,则此技术很有用。每次访问API时,您首先要从存储桶中获取令牌。桶以恒定的速度重新填充。如果存储桶为空,则您必须等待,然后再次点击API。令牌桶通常是在另一端(API)上实现的,但您也可以将它们用作代理,以免获得429 Too Many Requests。Celery的rate_limit功能使用令牌桶算法。

这是一个使用指数补偿和速率限制/令牌桶的Python / Celery应用示例:

class TooManyRequests(Exception):
"""Too many requests"""

@task(
   rate_limit='10/s',
   autoretry_for=(ConnectTimeout, TooManyRequests,),
   retry_backoff=True)
def api(*args, **kwargs):
  r = requests.get('placeholder-external-api')

  if r.status_code == 429:
    raise TooManyRequests()

9

另一个解决方法是使用某种公共VPN或Tor网络来欺骗您的IP。这将假设服务器在IP级别上进行了速率限制。

有一篇简短的博客文章,演示了与urllib2一起使用tor的方法:

http://blog.flip-edesign.com/?p=119


8
这就是为什么我总是要求我的API的用户注册密钥才能发出请求。这样,我可以通过密钥而不是IP限制请求。注册另一个密钥将是获得更高限制的唯一方法。
Mnebuerquo

2
if response.status_code == 429:
  time.sleep(int(response.headers["Retry-After"]))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.