स्क्रैपिंग में HTTP 429 Too Many Requests कैसे ठीक करें

HTTP 429 Too Many Requests का मतलब है धीमे चलो, यह नहीं कि चले जाओ। सर्वर बता रहा है कि उसे आपका ट्रैफ़िक अब भी चाहिए, बस कम दर पर, और आम तौर पर वह यह भी बता देता है कि कितनी देर रुकना है। इसलिए हल लगभग कभी प्रॉक्सी या नया user agent नहीं होता। हल है एक हेडर पढ़ना, सही ढंग से रुकना, और यह सीमित करना कि एक समय में कितनी रिक्वेस्ट हवा में हैं। यह पोस्ट तीनों बातें कवर करती है, और फिर दिखाती है कि उसी स्टेटस कोड को पहने बॉट ब्लॉक से असली रेट लिमिट को कैसे अलग पहचानें, क्योंकि दोनों पर उलटी प्रतिक्रिया चाहिए।
आपको क्या चाहिए
- उदाहरणों के लिए Python 3.10 या नया, requests लाइब्रेरी के साथ। तर्क किसी भी HTTP क्लाइंट पर सीधे लागू होता है।
- एक टर्मिनल, ताकि रिट्राई का कोड लिखने से पहले आप रिस्पॉन्स हेडर देख सकें।
- CapSkip चालू, और यह सिर्फ़ आखिरी सेक्शन के लिए चाहिए: या तो loopback पते पर लोकल मोड में, या ऐसी मशीन पर सर्वर मोड में जहाँ तक आपके worker पहुँच सकें। दोनों यहाँ कवर हैं: कनेक्शन सेटिंग्स। इसलिए शुरू करने से पहले एक चुन लें।
स्टेप 1: दोबारा भेजने से पहले रिस्पॉन्स पढ़ें
429 को संभालने वाला ज़्यादातर कोड आँख मूँदकर लिखा जाता है, इसीलिए वह काम नहीं करता। पहले असली रिस्पॉन्स देखें। स्टेटस कोड के साथ वे हेडर आते हैं जो बताते हैं कि सीमा क्या है और कब रीसेट होगी, और अलग-अलग सेवाएँ अलग हेडर इस्तेमाल करती हैं।
# No install needed. Dump headers, throw the body away. curl -sS -o /dev/null -D - "https://example.com/api/items?page=2" # Look for these, in this order of usefulness: # Retry-After: 30 seconds, or an HTTP date # RateLimit-Reset: 1724500000 # X-RateLimit-Remaining: 0 # RateLimit-Limit: 100
जो सबसे ज़्यादा मायने रखता है वह Retry-After है। यह ठीक इसी स्थिति के लिए तय किया गया है और दो रूपों में आता है: रुकने के सेकंड की संख्या, या पूरी HTTP तारीख़। दोनों वैध हैं, दोनों असल में मिलते हैं, और जो कोड सिर्फ़ संख्या मान लेता है वह उन साइटों पर टूट जाता है जो तारीख़ भेजती हैं। MDN दोनों रूप दर्ज करता है, और उसका संदर्भ पेज जो 429 स्टेटस कोड के बारे में है, वह भी आपके दो मिनट के लायक है।
इसे सावधानी से पार्स करें, मौजूद हो तो मानें, और न हो तो अपने ही शेड्यूल पर लौट आएँ:
# pip install requests
from email.utils import parsedate_to_datetime
from datetime import datetime, timezone
def retry_delay(response, fallback):
"""Seconds to wait, from Retry-After if the server sent one."""
raw = response.headers.get("Retry-After")
if not raw:
return fallback
try:
return max(0.0, float(raw)) # the delay-seconds form
except ValueError:
pass
try:
when = parsedate_to_datetime(raw) # the HTTP-date form
return max(0.0, (when - datetime.now(timezone.utc)).total_seconds())
except (TypeError, ValueError):
return fallbackजो आए उस पर सीमा लगाएँ। 3600 सेकंड माँगने वाला सर्वर आपसे एक घंटा रुकने को कह रहा है, और रिक्वेस्ट हैंडलर के अंदर इतनी देर आज्ञाकारी ढंग से सोया worker ऊपर की हर चीज़ को अटका हुआ लगेगा। हेडर की वैल्यू और अपनी सीमा में से छोटी लें, और फिर अलग से तय करें कि काम टालना है या नहीं।
स्टेप 2: एक्सपोनेंशियल पीछे हटें, jitter के साथ
जब मानने के लिए कोई Retry-After न हो, तो हर बार इंतज़ार दुगना करें और उसमें बेतरतीबी जोड़ें। दुगना करना आपको उस सेवा को और पीटने से रोकता है जो पहले से जूझ रही है। बेतरतीबी आपके उन बीस worker को, जो एक ही सेकंड में सीमा से टकराए, हमेशा एक ही सेकंड में दोबारा कोशिश करने से रोकती है।
# pip install requests
import random, time, requests
def get_with_backoff(url, attempts=5, base=1.0, ceiling=60.0):
for attempt in range(attempts):
response = requests.get(url, timeout=30)
if response.status_code != 429:
return response
# Full jitter: sleep somewhere in [0, base * 2 ** attempt].
window = min(ceiling, base * (2 ** attempt))
delay = retry_delay(response, random.uniform(0, window))
time.sleep(min(delay, ceiling))
raise RuntimeError(f"still rate limited after {attempts} attempts")
# Waits land near 0-1s, 0-2s, 0-4s, 0-8s, 0-16s unless the
# server named a delay, in which case that wins.फ़ुल jitter, यानी विंडो में थोड़ी हिलावट जोड़ने के बजाय शून्य से विंडो के बीच कोई रैंडम वैल्यू, बेड़े को सबसे तेज़ी से एक-दूसरे से अलग करता है। अगर आप इसे खुद नहीं लिखना चाहते, तो urllib3 में यह पहले से है, पर काम का होने के लिए इसमें दो आर्ग्युमेंट साफ़-साफ़ देने पड़ते हैं:
# pip install requests
import requests
from requests.adapters import HTTPAdapter
from urllib3.util import Retry
retry = Retry(
total=5,
# status_forcelist defaults to none, so 429 is NOT retried
# unless you list it here yourself. This is the usual bug.
status_forcelist=[429, 500, 502, 503, 504],
backoff_factor=1, # 1 * 2 ** previous_retries seconds
backoff_jitter=1.0, # urllib3 2.x only
allowed_methods=["GET", "HEAD"],
)
session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=retry))उस क्लास के बारे में दो बातें जानने लायक हैं। Retry-After का पालन वह खुद कर लेती है, क्योंकि 429 उसके RETRY_AFTER_STATUS_CODES सेट के तीन स्टेटस कोड में से एक है, बाकी दो 413 और 503 हैं, और respect_retry_after_header डिफ़ॉल्ट रूप से true रहता है। पर status_forcelist डिफ़ॉल्ट रूप से पूरी तरह खाली है, इसलिए नया Retry ऑब्जेक्ट 429 पर दोबारा कोशिश नहीं करता, जब तक आप उसे वहाँ न लिखें। लोग उलटा मान लेते हैं और फिर सोचते हैं कि अडैप्टर ने कुछ क्यों नहीं किया।
स्टेप 3: ज़्यादा ज़ोर से रिट्राई करने के बजाय कनकरेंसी घटाएँ
रिट्राई का तर्क लक्षण का इलाज करता है। अगर 429 झटकों में नहीं, लगातार आ रहे हैं, तो आप बस इजाज़त से ज़्यादा माँग रहे हैं, और इलाज है कम भेजना। एक सेमाफ़ोर और रिक्वेस्ट के बीच न्यूनतम अंतराल, किसी भी बैकऑफ़ कर्व से ज़्यादा रेट लिमिटिंग ठीक कर देते हैं।
# Standard library only.
import asyncio
# Six in flight is a sane starting point for an unknown API.
gate = asyncio.Semaphore(6)
MIN_GAP = 0.2 # seconds between starts, per worker
async def fetch(client, url):
async with gate:
response = await client.get(url)
await asyncio.sleep(MIN_GAP)
return response
# Tune down on the first 429, and stay there for a while.
# Tuning back up too eagerly just rediscovers the limit.429 के बाद पहली सहज इच्छा होती है वही लोड ज़्यादा IP पर फैला देना। कुछ टारगेट पर यह काम करता है, पर यह अपने ही समझौतों वाला अलग फ़ैसला है, जिसे हमने अलग से इस गाइड में देखा: कैप्चा प्रॉक्सी रोटेशन। इसे क्षमता का फ़ैसला मानकर करें, किसी हेडर को न पढ़ने के बहाने के तौर पर नहीं।
429 न 403 है, और चुनौती इन दोनों में से कोई नहीं
429 को संभालने में सबसे महँगी गलती यहीं होती है। रेट लिमिटिंग और बॉट डिटेक्शन अलग सिस्टम हैं जो कभी-कभी एक ही स्टेटस कोड साझा कर लेते हैं, और वे आपसे उलटी-उलटी चीज़ें चाहते हैं। रेट लिमिटर से आया HTTP 429 Too Many Requests शेड्यूल का निर्देश है, वहीं यही कोड किसी एंटी-बॉट एज से आए तो वह इनकार है। बॉट ब्लॉक के सामने शालीनता से पीछे हटना एक घंटा बर्बाद करता है। असली रेट लिमिट पर ज़ोर से रिट्राई करना आपका IP बैन करा देता है।
| आपको क्या मिला | आम तौर पर इसका मतलब | असल में क्या काम आता है |
|---|---|---|
| Retry-After हेडर के साथ आया 429 | असली, दर्ज की गई रेट लिमिट | ठीक उतनी देर रुकें, फिर अपनी दर घटाएँ |
| बिना हेडर और HTML बॉडी वाला 429 | API नहीं, कोई एज या एंटी-बॉट लेयर | इसे सीमा नहीं, ब्लॉक मानें |
| तुरंत आ जाने वाला 403 | फ़िंगरप्रिंट, TLS या IP की साख | क्लाइंट सुधारें, क्योंकि इंतज़ार से कुछ नहीं बदलता |
| Retry-After के साथ आया 503 | ओवरलोड या मेंटेनेंस में | 429 जैसा ही बैकऑफ़ रास्ता |
| चुनौती पेज लिए हुए 200 | आपको स्कोर किया गया और बीच में रोका गया | चुनौती सॉल्व करें और आगे बढ़ें |
आखिरी पंक्ति लोगों को चकमा देती है, क्योंकि फेल कुछ भी नहीं हुआ। रिक्वेस्ट 200 लौटी और बॉडी में आपके डेटा की जगह चुनौती पेज है, इसलिए सिर्फ़ स्टेटस कोड देखने वाला रिट्राई लूप उसे मज़े से हमेशा पीटता रहेगा। सिर्फ़ स्टेटस लाइन नहीं, बॉडी में वह निशान देखें जिसकी आपको उम्मीद है। अगर मिली चीज़ चुनौती है, तो जवाब पीछे हटना नहीं, उसे सॉल्व करना है।
अपने सॉल्व लूप को उसी रेक पर मत चढ़ाएँ
कैप्चा के जवाब का इंतज़ार करने वाला पोलिंग लूप खुद एक रिट्राई लूप है, और हाथ से बने लूप ऊपर बताई गलतियाँ ठीक वैसे ही करते हैं। CapSkip में दो बातें इसे मीटर वाली सेवा से आसान बनाती हैं। यह आपके ही हार्डवेयर पर चलता है, इसलिए न चुकने वाला कोई प्रति-सॉल्व कोटा है और न अपनी कोई रेट लिमिट जिससे टकराया जाए। और SDK आपके लिए पहले से पीछे हटते हैं: पोलिंग एक चौथाई सेकंड से शुरू होती है और pollingInterval तक बढ़ती है, जो तय अंतराल नहीं, ऊपरी सीमा है।
# pip install capskip
from capskip import CapSkip, NetworkException, TimeoutException
# Local mode. In Server mode, host is the solver box's address.
solver = CapSkip(host="127.0.0.1", port=8080, pollingInterval=2)
try:
result = solver.recaptcha(
sitekey="YOUR_SITEKEY",
url="https://example.com/page-with-recaptcha",
)
print(result["code"][:24]) # token, submit it with the form
except TimeoutException:
# Polling ran past recaptchaTimeout, 300 seconds by default.
print("gave up waiting, try again or lower the timeout")
except NetworkException:
# The solver is not reachable on that host and port.
print("check CapSkip is running and the mode you set")pollingInterval घटाने से जवाब जल्दी आता है और इसकी कोई कीमत नहीं, और पैसे लेने वाले API पर असल में यह विकल्प आपके पास नहीं होता। अगर आप SDK की जगह कच्चे HTTP एंडपॉइंट पोल कर रहे हैं, तो हर कैप्चा टाइप के लिए सुझाई गई देरी यहाँ दी गई है: API डॉक्युमेंटेशन। साथ ही वह जवाब भी, जो सॉल्व चलते रहने के दौरान आपको मिलेगा।
सॉल्वर को उसकी जगह सर्वर पर चलाना
रेट लिमिटिंग आम तौर पर एक स्क्रिप्ट की नहीं, पूरे बेड़े की समस्या होती है, और बेड़ा एक loopback पता साझा नहीं करता। कनेक्शन सेटिंग्स दोनों हालात कवर करती हैं:
| मोड | किस पर सुनता है | कब इस्तेमाल करें |
|---|---|---|
| लोकल | 127.0.0.1, केवल उसी डिवाइस पर | आपका स्क्रैपर और सॉल्वर एक ही मशीन पर चलते हैं |
| सर्वर | आपका नेटवर्क पता या पब्लिक IP | worker, कंटेनर, कोई VPS या होस्टेड प्लेटफ़ॉर्म API से कॉल करते हैं |
SDK का host सॉल्वर वाली मशीन पर कर दें, और आपके कोड में बाकी कुछ नहीं बदलता, जिससे दस worker एक ही इंस्टैंस साझा कर सकते हैं। अगर कॉल करने वाले आपके नेटवर्क से बाहर बैठे हैं तो स्टैटिक पब्लिक IP की सलाह है। जानकारी यहाँ है: कनेक्शन सेटिंग्स। और सर्वर मोड अब भी आपका हार्डवेयर है और अब भी बिना मीटर: यह बदलता है कि सॉल्वर कहाँ चलता है, यह नहीं कि वह किसका है।
FAQ
क्या मुझे Retry-After हमेशा मानना चाहिए?
मानें, पर एक सीमा के साथ। खिड़की कब फिर खुलेगी, इस बारे में यही सबसे भरोसेमंद संकेत है, इसलिए इसे नज़रअंदाज़ करना सर्वर के बताए से भी खराब अंदाज़ा लगाना है। पर एक घंटे की वैल्यू अलग फ़ैसला है: worker को सुलाकर रखने की जगह काम टाल दें और बाद में लौटें, क्योंकि ऊपर की हर चीज़ इसे अटकना पढ़ेगी।
रेट लिमिट और बॉट ब्लॉक में फ़र्क़ कैसे पहचानूँ?
देखें कि स्टेटस कोड के साथ क्या आया। असली सीमा मशीन के पढ़ने लायक होती है: Retry-After या RateLimit हेडर, छोटी JSON बॉडी, और रुकने पर एक जैसा बर्ताव। बॉट ब्लॉक HTML पेज भेजता है, समय की कोई जानकारी नहीं देता, और अक्सर वही जवाब देता है, आप कितनी भी देर छोड़ दें। दूसरे को लंबी नींद नहीं, दूसरा क्लाइंट चाहिए।
क्या सॉल्वर कभी 429 लौटाता है?
नहीं। CapSkip आपके नियंत्रण वाले हार्डवेयर पर, प्रति-सॉल्व कोटा के बिना चलता है, इसलिए न चुकने वाली कोई बिलिंग विंडो है और न टकराने वाली कोई ऊपरी सीमा। सॉल्व कॉल फेल हो तो आपको नेटवर्क एरर मिलेगा, क्योंकि डीमन तक पहुँच नहीं है, या टाइमआउट, क्योंकि पोलिंग अपनी सीमा पार कर गई। दोनों का मतलब कुछ स्थानीय है, इसलिए host, port और अपना चुना मोड जाँचें।
मेरे worker होस्टेड प्लेटफ़ॉर्म पर चलते हैं। सॉल्वर कहाँ रखें?
अपनी ऐसी मशीन पर जहाँ तक वह प्लेटफ़ॉर्म पहुँच सके, और सॉल्वर सर्वर मोड में। होस्टेड रनर और मैनेज्ड ऑटोमेशन प्लेटफ़ॉर्म आपका loopback पता नहीं देख सकते, इसलिए API को अपने नेटवर्क या पब्लिक IP से बाँधें और हर worker को उसी पर भेजें। एक इंस्टैंस पूरे बेड़े को सेवा देता है, और कोई टनल नहीं चाहिए।
सबसे छोटा जवाब
HTTP 429 Too Many Requests शेड्यूल की समस्या है, इसे वैसे ही बरतें। Retry-After पढ़ें और अपनी चुनी सीमा तक उसे मानें। हेडर न हो तो फ़ुल jitter वाले एक्सपोनेंशियल बैकऑफ़ पर लौटें। फिर अपनी कनकरेंसी घटाएँ, क्योंकि लगातार आते 429 क्षमता की समस्या हैं, जिसे कोई रिट्राई कर्व हल नहीं करता। और दोबारा कोशिश करने से पहले बॉडी देखें, क्योंकि चुनौती पेज पूरी तरह स्वस्थ स्टेटस कोड के साथ आता है और इंतज़ार नहीं, सॉल्व होना चाहता है। उस हिस्से के लिए लोकल चलने वाला असीमित कैप्चा सॉल्वर ही फ़िट बैठता है, और वह worker pool में कैसे बैठता है, यह वेब स्क्रैपिंग के लिए कैप्चा सॉल्वर वाला लेख बताता है।
