Comment éviter l'erreur HTTP 429 (trop de requêtes) python

91

J'essaie d'utiliser Python pour me connecter à un site Web et recueillir des informations à partir de plusieurs pages Web et j'obtiens l'erreur suivante:

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

Je l'ai utilisé time.sleep()et cela fonctionne, mais cela semble inintelligent et peu fiable, y a-t-il un autre moyen d'éviter cette erreur?

Voici mon code:

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")
Aous1000
la source
6
Il n'y a pas moyen de contourner cela, il s'agit d'une application côté serveur gardant une trace du nombre de demandes / d'unité de temps que vous faites. Si vous dépassez cette unité, vous serez temporairement bloqué. Certains serveurs envoient ces informations dans l'en-tête, mais ces occasions sont rares. Vérifiez les en-têtes reçus du serveur, utilisez les informations disponibles. Sinon, vérifiez à quelle vitesse vous pouvez marteler sans vous faire prendre et utilisez un sleep.
Torxed le

Réponses:

158

Recevoir un statut 429 n'est pas une erreur , c'est l'autre serveur "gentiment" qui vous demande d'arrêter les demandes de spam. De toute évidence, votre taux de requêtes a été trop élevé et le serveur n'est pas disposé à accepter cela.

Vous ne devez pas chercher à «esquiver» cela, ni même essayer de contourner les paramètres de sécurité du serveur en essayant d'usurper votre adresse IP, vous devez simplement respecter la réponse du serveur en n'envoyant pas trop de requêtes.

Si tout est configuré correctement, vous aurez également reçu un en-tête "Retry-after" avec la réponse 429. Cet en-tête spécifie le nombre de secondes que vous devez attendre avant d'effectuer un autre appel. La bonne façon de traiter ce "problème" est de lire cet en-tête et de mettre votre processus en veille pendant autant de secondes.

Vous pouvez trouver plus d'informations sur le statut 429 ici: http://tools.ietf.org/html/rfc6585#page-3

MRA
la source
23
Eh bien, personne n'a jamais dit que tous les serveurs Web étaient configurés correctement. De plus, étant donné que la plupart des limiteurs de débit identifient les visiteurs par IP, cela peut entraîner des problèmes dans un scénario où les adresses IP sont partagées dynamiquement. Si vous continuez à recevoir le statut 429 alors que vous êtes certain de ne pas avoir envoyé trop de demandes du tout, vous pouvez envisager de contacter l'administrateur du site.
MRA
2
Merci d'avoir mentionné l'en-tête "Réessayer après". J'adorerais un exemple de code pour voir comment obtenir cette valeur (j'utilisais urllib, pour OP mécaniser, dans les deux cas, je ne pense pas que les en-têtes soient inclus dans l'exception levée)
MacFreek
@MacFreek Je n'ai pas d'exemple de code Python particulier prêt, mais je suppose que certains exemples sur la façon de récupérer les en-têtes de réponse en général peuvent être tirés des réponses à cette question: stackoverflow.com/q/843392
MRA
Merci @MRA. J'ai trouvé que les en-têtes sont également disponibles dans l'exception: après capture HTTPError as my_exception, il est disponible dans my_exception.headers, au moins pour urllib2.
MacFreek
37

L'écriture de ce morceau de code a résolu mon problème:

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

tadm123
la source
26
Cette réponse est rejetée, mais certains sites renvoient automatiquement le code d'erreur 429 si l'agent utilisateur est banni en raison d'abus de la part d'autres personnes. Si vous obtenez le code d'erreur 429 même si vous n'avez envoyé que quelques demandes, essayez de définir l'agent utilisateur sur autre chose.
Ferry Boender
7
Je voudrais également ajouter que certains sites refusent clairement les demandes à moins qu'un agent utilisateur ne soit envoyé, et vous pouvez obtenir une myriade d'autres réponses: 503/403 / une page d'index générique.
user3791372
1
Peut le confirmer. J'essayais juste d'interfacer python avec reddit et sans configurer l'agent utilisateur, j'obtenais toujours le code d'erreur 429.
Karrq
pouvez-vous ajouter quelques explications s'il vous plaît?
Tokci
29

Comme MRA l'a dit, vous ne devriez pas essayer d'esquiver un 429 Too Many Requestsmais plutôt le gérer en conséquence. Vous avez plusieurs options en fonction de votre cas d'utilisation:

1) Mettez votre processus en veille . Le serveur inclut généralement un en- Retry-aftertête dans la réponse avec le nombre de secondes que vous êtes censé attendre avant de réessayer. Gardez à l'esprit que la mise en veille d'un processus peut causer des problèmes, par exemple dans une file d'attente de tâches, où vous devriez plutôt réessayer la tâche plus tard pour libérer le worker pour d'autres choses.

2) Réduction exponentielle . Si le serveur ne vous dit pas combien de temps attendre, vous pouvez réessayer votre demande en augmentant les pauses entre les deux. La tâche populaire file d' attente Céleri a cette fonctionnalité intégrée à droite dans .

3) Seau à jetons . Cette technique est utile si vous savez à l'avance combien de demandes vous êtes en mesure de faire dans un temps donné. Chaque fois que vous accédez à l'API, vous récupérez d'abord un jeton dans le compartiment. Le seau est rempli à un débit constant. Si le compartiment est vide, vous savez que vous devrez attendre avant de toucher à nouveau l'API. Les buckets de jetons sont généralement implémentés à l'autre extrémité (l'API), mais vous pouvez également les utiliser comme proxy pour éviter d'obtenir un fichier 429 Too Many Requests. La fonctionnalité rate_limit de Celery utilise un algorithme de seau à jetons.

Voici un exemple d'application Python / Celery utilisant une interruption exponentielle et un compartiment de limitation de débit / de jetons:

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()
psaniko
la source
9

Une autre solution de contournement serait d'usurper votre adresse IP en utilisant une sorte de réseau VPN public ou Tor. Cela supposerait la limitation de débit sur le serveur au niveau IP.

Il y a un bref article de blog montrant une façon d'utiliser tor avec urllib2:

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

Gaurav Agarwal
la source
8
C'est pourquoi j'exige toujours que les utilisateurs de mes API s'inscrivent pour une clé pour faire des demandes. De cette façon, je peux limiter les demandes par clé plutôt que par IP. L'enregistrement pour une autre clé serait le seul moyen d'obtenir une limite plus élevée.
Mnebuerquo
2
if response.status_code == 429:
  time.sleep(int(response.headers["Retry-After"]))
Davidbrown
la source