Cách tránh lỗi HTTP 429 (Quá nhiều yêu cầu) python


91

Tôi đang cố gắng sử dụng Python để đăng nhập vào một trang web và thu thập thông tin từ một số trang web và tôi gặp lỗi sau:

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

Tôi đã sử dụng time.sleep()và nó hoạt động, nhưng nó có vẻ không thông minh và không đáng tin cậy, có cách nào khác để tránh lỗi này không?

Đây là mã của tôi:

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
Không có cách nào để giải quyết vấn đề này, đây là một biện pháp thực thi phía máy chủ theo dõi số lượng yêu cầu / đơn vị thời gian bạn thực hiện. Nếu bạn vượt quá đơn vị này, bạn sẽ tạm thời bị chặn. Một số máy chủ gửi thông tin này trong tiêu đề, nhưng những trường hợp đó rất hiếm. Kiểm tra các tiêu đề nhận được từ máy chủ, sử dụng thông tin có sẵn .. Nếu không, hãy kiểm tra xem bạn có thể gõ nhanh như thế nào mà không bị bắt và sử dụng a sleep.
Torxed

Câu trả lời:


158

Nhận được trạng thái 429 không phải là lỗi , đó là máy chủ khác "có nhã ý" đề nghị các bạn dừng yêu cầu spam. Rõ ràng, tỷ lệ yêu cầu của bạn quá cao và máy chủ không sẵn sàng chấp nhận điều này.

Bạn không nên tìm cách "né" điều này hoặc thậm chí cố gắng phá vỡ cài đặt bảo mật của máy chủ bằng cách cố gắng giả mạo IP của mình, bạn chỉ nên tôn trọng câu trả lời của máy chủ bằng cách không gửi quá nhiều yêu cầu.

Nếu mọi thứ được thiết lập đúng cách, bạn cũng sẽ nhận được tiêu đề "Thử lại sau" cùng với phản hồi 429. Tiêu đề này chỉ định số giây bạn nên đợi trước khi thực hiện một cuộc gọi khác. Cách thích hợp để giải quyết "vấn đề" này là đọc tiêu đề này và để quy trình của bạn ngủ trong nhiều giây đó.

Bạn có thể tìm thêm thông tin về trạng thái 429 tại đây: http://tools.ietf.org/html/rfc6585#page-3


23
Chà, chưa ai nói rằng tất cả các máy chủ web đều được định cấu hình chính xác. Ngoài ra, vì hầu hết các bộ giới hạn tỷ lệ đang xác định khách truy cập theo IP, điều này có thể dẫn đến sự cố trong trường hợp các IP được chia sẻ động. Nếu bạn vẫn nhận được trạng thái 429 mặc dù bạn tin chắc rằng bạn đã không gửi quá nhiều yêu cầu, bạn có thể xem xét liên hệ với quản trị viên của trang web.
MRA

2
Cảm ơn bạn đã đề cập đến tiêu đề "Thử lại sau". Tôi rất thích một ví dụ mã để xem cách lấy giá trị đó (Tôi đang sử dụng urllib, để OP cơ khí hóa, trong cả hai trường hợp, tôi không nghĩ rằng các tiêu đề được bao gồm trong ngoại lệ được nêu ra)
MacFreek

@MacFreek Tôi không có sẵn bất kỳ ví dụ mã Python cụ thể nào, nhưng tôi giả sử một số ví dụ về cách truy xuất tiêu đề phản hồi nói chung có thể được lấy từ câu trả lời cho câu hỏi này: stackoverflow.com/q/843392
MRA

Cảm ơn @MRA. Tôi thấy rằng các tiêu đề cũng có sẵn trong ngoại lệ: sau khi bắt HTTPError as my_exception, nó có sẵn my_exception.headers, ít nhất là cho urllib2.
MacFreek

37

Viết đoạn mã này đã khắc phục sự cố của tôi:

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


26
Câu trả lời này bị phản đối, nhưng một số trang web tự động trả về mã lỗi 429 nếu tác nhân người dùng bị cấm do lạm dụng từ người khác. Nếu bạn nhận được mã lỗi 429 ngay cả khi bạn chỉ gửi một vài yêu cầu, hãy thử đặt tác nhân người dùng thành một thứ khác.
Phà Boender

7
Cũng muốn nói thêm, một số trang web từ chối yêu cầu một cách rõ ràng trừ khi tác nhân người dùng được gửi đi và bạn có thể nhận được vô số phản hồi khác: 503/403 / một số trang chỉ mục chung chung.
user3791372

1
Có thể xác nhận điều này. Chỉ cần cố gắng để giao tiếp với python reddit và không thiết lập các đại lý người sử dụng tôi đã luôn luôn nhận được mã lỗi 429.
Karrq

bạn có thể thêm một số lời giải thích xin vui lòng?
Tokci

29

Như MRA đã nói, bạn không nên cố gắng né tránh 429 Too Many Requestsmà thay vào đó hãy xử lý nó cho phù hợp. Bạn có một số tùy chọn tùy thuộc vào trường hợp sử dụng của mình:

1) Ngủ quá trình của bạn . Máy chủ thường bao gồm một Retry-aftertiêu đề trong phản hồi với số giây bạn phải đợi trước khi thử lại. Hãy nhớ rằng việc ngủ một quy trình có thể gây ra sự cố, ví dụ như trong hàng đợi tác vụ, thay vào đó bạn nên thử lại tác vụ sau đó để giải phóng nhân viên làm những việc khác.

2) Chốt lại theo cấp số nhân . Nếu máy chủ không cho bạn biết phải đợi bao lâu, bạn có thể thử lại yêu cầu của mình bằng cách tăng dần các lần tạm dừng ở giữa. Hàng đợi tác vụ phổ biến Celery có tính năng này được tích hợp sẵn .

3) Thùng mã thông báo . Kỹ thuật này hữu ích nếu bạn biết trước số lượng yêu cầu bạn có thể thực hiện trong một thời gian nhất định. Mỗi lần bạn truy cập API, lần đầu tiên bạn tìm nạp một mã thông báo từ nhóm. Thùng được đổ đầy với tốc độ không đổi. Nếu thùng trống, bạn biết rằng bạn sẽ phải đợi trước khi nhấn lại API. Các nhóm mã thông báo thường được triển khai ở đầu bên kia (API) nhưng bạn cũng có thể sử dụng chúng làm proxy để tránh bị nhận 429 Too Many Requests. Tính năng rate_limit của Celery sử dụng thuật toán nhóm mã thông báo.

Dưới đây là một ví dụ về ứng dụng Python / Celery sử dụng dự phòng theo cấp số nhân và giới hạn tỷ lệ / nhóm mã thông báo:

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

Một giải pháp khác sẽ là giả mạo IP của bạn bằng một số loại VPN công cộng hoặc mạng Tor. Điều này sẽ giả sử giới hạn tốc độ trên máy chủ ở cấp IP.

Có một bài đăng ngắn trên blog trình bày cách sử dụng tor cùng với urllib2:

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


8
Đây là lý do tại sao tôi luôn yêu cầu người dùng API của tôi đăng ký khóa để thực hiện yêu cầu. Bằng cách này, tôi có thể giới hạn yêu cầu theo khóa thay vì IP. Đăng ký một khóa khác sẽ là cách duy nhất để nhận được giới hạn cao hơn.
Mnebuerquo

2
if response.status_code == 429:
  time.sleep(int(response.headers["Retry-After"]))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.