← Back to Blog

5 गलतियाँ Scrapy प्रॉक्सी मिडलवेयर में जो प्रॉक्सी ट्रैफ़िक को बेकार कर देती हैं

मिडलवेयर स्क्रैपी में पांच सामान्य गलतियाँ, जिनके कारण प्रॉक्सी ट्रैफ़िक बेकार होता है और पार्सर को बैन मिलते हैं। कोड के उदाहरणों और तैयार समाधानों के साथ।

📅September 29, 2026

क्या Scrapy पर पार्सर टाइमआउट पर गिरता है, प्रॉक्सी पूल अपेक्षित से कई गुना तेजी से खर्च होता है, और लॉग 407 और 403 उत्तरों से भरे होते हैं — क्या यह परिचित दृश्य है? 90% मामलों में समस्या स्वयं प्रॉक्सी में नहीं होती, बल्कि DownloaderMiddleware के लिखने के तरीके में होती है। हम मिडलवेयर में पाँच सबसे सामान्य गलतियों पर चर्चा करते हैं, जो महंगे ट्रैफ़िक को बेकार अनुरोधों में बदल देती हैं, और दिखाते हैं कि उन्हें कोड के साथ कैसे ठीक किया जाए।

गलती 1: प्राइमिटिव रोटेशन बिना प्रॉक्सी की स्थिति के ध्यान में

सबसे सामान्य संरचना, जो ट्यूटोरियल में मिलती है — वह है random.choice(PROXY_LIST) के अंदर process_request में। समस्या यह है कि ऐसी रोटेशन नहीं जानती कि कौन सी प्रॉक्सी अभी बैन हुई है, और कौन सी अभी भी जीवित है। नतीजतन, पार्सर पहले से ही ब्लॉक किए गए IP के माध्यम से अनुरोध भेजना जारी रखता है, 403/429 प्राप्त करता है, पुनः प्रयास करता है — और फिर से वही पता चुनता है, क्योंकि चयन यादृच्छिक है और "खराब" नोड्स को बाहर नहीं करता है।

सही दृष्टिकोण यह है कि प्रत्येक प्रॉक्सी की स्थिति को बनाए रखा जाए: सफल अनुरोधों की संख्या, त्रुटियों की संख्या, अंतिम उपयोग का समय। यहाँ एक न्यूनतम कार्यशील विकल्प है:

import random
import time

class ProxyPool:
    def __init__(self, proxies):
        self.proxies = {p: {"fails": 0, "last_used": 0, "banned_until": 0} for p in proxies}

    def get_proxy(self):
        now = time.time()
        available = [
            p for p, state in self.proxies.items()
            if state["banned_until"] < now
        ]
        if not available:
            # यदि सभी बैन हैं, तो सबसे "पुराने" बैन को रीसेट करें
            available = list(self.proxies.keys())
        return random.choice(available)

    def mark_fail(self, proxy, cooldown=300):
        self.proxies[proxy]["fails"] += 1
        self.proxies[proxy]["banned_until"] = time.time() + cooldown

    def mark_success(self, proxy):
        self.proxies[proxy]["fails"] = 0
        self.proxies[proxy]["last_used"] = time.time()

ऐसा पूल "कूलडाउन" (cooldown) के समय में बैन किए गए IP को बाहर करता है और उन्हें बाद में उपयोग में लाता है। इससे ट्रैफ़िक की खपत कई गुना कम हो जाती है, क्योंकि आप एक ही ब्लॉक किए गए नोड पर लगातार नहीं धावा बोलते हैं।

गलती 2: पुनः प्रयास और स्थिति कोडों की गलत हैंडलिंग

दूसरी सामान्य गलती — Scrapy से मानक RetryMiddleware का बिना संशोधन के उपयोग करना। डिफ़ॉल्ट रूप से, यह उसी प्रॉक्सी पर अनुरोध को पुनः प्रयास करता है, जिसने इसे असफल किया, यदि आपने स्पष्ट रूप से process_exception में प्रॉक्सी बदलने का मिश्रण नहीं किया है। नतीजतन, हमें एक क्लासिक दृश्य मिलता है: 3 पुनः प्रयास, 3 बैन, अनुरोध फिर भी गिरता है, और ट्रैफ़िक पहले ही खर्च हो चुका है।

दूसरा बिंदु — सभी स्थिति कोडों को समान रूप से पुनः प्रयास नहीं किया जाना चाहिए। 429 (बहुत अधिक अनुरोध) को विराम और IP परिवर्तन की आवश्यकता होती है, 403 आमतौर पर एक विशेष प्रॉक्सी का बैन दर्शाता है (तत्काल प्रतिस्थापन की आवश्यकता है), जबकि 5xx — यह अक्सर सर्वर की ओर से एक अस्थायी समस्या होती है, पुनः प्रयास उसी प्रॉक्सी पर किया जा सकता है। यदि सब कुछ एक ही ढेर में डाल दिया जाए, तो मिडलवेयर या तो बहुत आक्रामक रूप से प्रॉक्सी को जलाता है, या बहुत लंबे समय तक इंतजार करता है, जहाँ तुरंत IP बदलना आवश्यक था।

class SmartRetryMiddleware:
    def __init__(self, pool):
        self.pool = pool

    def process_response(self, request, response, spider):
        proxy = request.meta.get("proxy")
        if response.status in (403, 407):
            if proxy:
                self.pool.mark_fail(proxy, cooldown=600)
            new_request = request.copy()
            new_request.meta["proxy"] = self.pool.get_proxy()
            new_request.dont_filter = True
            return new_request

        if response.status == 429:
            if proxy:
                self.pool.mark_fail(proxy, cooldown=120)
            new_request = request.copy()
            new_request.meta["proxy"] = self.pool.get_proxy()
            new_request.dont_filter = True
            return new_request

        if proxy:
            self.pool.mark_success(proxy)
        return response

यहाँ त्रुटियों के प्रकार के अनुसार कूलडाउन को विभाजित करना महत्वपूर्ण है: कठोर बैन (403/407) — लंबा विराम, दर सीमा (429) — छोटा। यह लंबे प्रगति पर ट्रैफ़िक के कई प्रतिशत को बचाता है।

गलती 3: प्रमाणीकरण वाले साइटों के लिए स्टिकी-सेशन्स का अभाव

यदि पार्सर किसी ऐसे साइट के साथ काम कर रहा है, जहाँ लॉगिन, कार्ट, स्थिति बनाए रखने के साथ पेजिनेशन या IP द्वारा जांच के साथ कैप्चा है — तो हर अनुरोध पर प्रॉक्सी बदलने से सत्र टूट जाता है। साइट देखती है कि अनुरोध संख्या 1 एक IP से आया है, जबकि अनुरोध संख्या 2 (उसी कुकी-सेशन के तहत) दूसरे से आया है, और यह तुरंत बॉट सुरक्षा को ट्रिगर करता है, भले ही दोनों IP "स्वच्छ" हों।

समाधान — एक प्रॉक्सी को एक तार्किक सत्र (उदाहरण के लिए, एक विशेष खाते या एक डोमेन के भीतर अनुरोधों की श्रृंखला) के लिए निश्चित समय के लिए बांधना, न कि हर अनुरोध पर इसे बदलना। इसे स्टिकी-सेशन कहा जाता है।

class StickySessionMiddleware:
    def __init__(self, pool, ttl=600):
        self.pool = pool
        self.ttl = ttl
        self.sessions = {}  # session_id -> (proxy, expires_at)

    def process_request(self, request, spider):
        session_id = request.meta.get("session_id")
        if not session_id:
            return

        now = time.time()
        session = self.sessions.get(session_id)

        if session and session[1] > now:
            request.meta["proxy"] = session[0]
        else:
            proxy = self.pool.get_proxy()
            self.sessions[session_id] = (proxy, now + self.ttl)
            request.meta["proxy"] = proxy

उन कार्यों के लिए, जहाँ पूरे सत्र के दौरान IP की स्थिरता की आवश्यकता होती है — प्रमाणीकरण, व्यक्तिगत खाते के साथ काम करना, बहु-चरण फ़ॉर्म — सबसे अच्छा विकल्प हैं रिज़िडेंट प्रॉक्सी जो सत्रों का समर्थन करते हैं: वे एक ही आउटगोइंग IP को कई मिनटों या घंटों तक बनाए रखने की अनुमति देते हैं, और फिर इसे नियंत्रित तरीके से बदलते हैं, न कि हर अनुरोध पर यादृच्छिक रूप से।

गलती 4: प्रॉक्सी प्रमाणीकरण का गलत हस्तांतरण

चौथी गलती — तकनीकी है, लेकिन लगभग हर दूसरे प्रोजेक्ट में मिलती है। डेवलपर्स प्रॉक्सी का लॉगिन और पासवर्ड सीधे http://user:pass@ip:port जैसे URL में request.meta["proxy"] के माध्यम से भेजते हैं। यह अधिकांश मामलों में काम करता है, लेकिन HTTPS टनल के माध्यम से प्रॉक्सी का उपयोग करते समय या कुछ प्रदाताओं के साथ काम करते समय, इस प्रकार के प्रमाणीकरण को मानक HttpProxyMiddleware द्वारा सही ढंग से संसाधित नहीं किया जाता है, और अनुरोध 407 प्रॉक्सी प्रमाणीकरण आवश्यक के साथ गिरते हैं, जबकि क्रेडेंशियल सही होते हैं।

एक अधिक विश्वसनीय तरीका — Proxy-Authorization हेडर को स्पष्ट रूप से, बेस64 में एन्कोडेड करके भेजना:

import base64

class ProxyAuthMiddleware:
    def process_request(self, request, spider):
        proxy = request.meta.get("proxy")
        if not proxy:
            return

        # क्रेडेंशियल्स के बिना प्रॉक्सी
        request.meta["proxy"] = proxy

        user = request.meta.get("proxy_user")
        password = request.meta.get("proxy_pass")
        if user and password:
            credentials = f"{user}:{password}"
            encoded = base64.b64encode(credentials.encode()).decode()
            request.headers["Proxy-Authorization"] = f"Basic {encoded}"

यह दृष्टिकोण सैकड़ों समांतर अनुरोधों के स्केलिंग के दौरान अधिक स्थिरता से काम करता है और इस पर निर्भर नहीं करता कि विशिष्ट पुस्तकालय का संस्करण URL को एम्बेडेड क्रेडेंशियल्स के साथ कैसे पार्स करता है। विशेष रूप से यह मोबाइल प्रॉक्सी के साथ काम करते समय महत्वपूर्ण है, जहाँ प्रमाणीकरण अक्सर IP व्हाइटलिस्ट या हेडर की सख्त जांच पर निर्भर करता है।

गलती 5: बैन की निगरानी और लॉगिंग का अभाव

अंतिम और शायद सबसे महंगी गलती — यह है कि यह लॉगिंग का अभाव है कि कौन सी प्रॉक्सी बैन हो रही है, कितनी बार और किन डोमेन पर। इन डेटा के बिना, यह समझना असंभव है कि वास्तव में ट्रैफ़िक को क्या खा रहा है: क्या प्रॉक्सी पूल ने किसी विशेष साइट पर खुद को समाप्त कर लिया है, या समस्या स्वयं पार्सर में है (बहुत अधिक अनुरोध, विराम का अभाव, संदिग्ध हेडर)।

मिडलवेयर में लॉग करने के लिए न्यूनतम सेट मेट्रिक्स:

  • पार्सिंग सत्र के दौरान प्रत्येक प्रॉक्सी पर अनुरोधों की संख्या
  • प्रत्येक प्रॉक्सी के लिए त्रुटियों की संख्या और कोड (403, 407, 429, 5xx)
  • पहले बैन तक प्रॉक्सी का जीवनकाल
  • वे डोमेन, जिन पर बैन अक्सर होते हैं
import logging
import json

logger = logging.getLogger("proxy_stats")

class ProxyStatsMiddleware:
    def __init__(self):
        self.stats = {}

    def process_response(self, request, response, spider):
        proxy = request.meta.get("proxy", "unknown")
        domain = request.url.split("/")[2]
        key = f"{proxy}|{domain}"

        entry = self.stats.setdefault(key, {"requests": 0, "errors": 0})
        entry["requests"] += 1
        if response.status in (403, 407, 429):
            entry["errors"] += 1

        if entry["requests"] % 50 == 0:
            logger.info(json.dumps(self.stats))

        return response

बिना ऐसी सांख्यिकी के, मिडलवेयर को "ऑप्टिमाइज़" करने के किसी भी प्रयास का परिणाम केवल अटकलें होती हैं। इसके साथ, आप निश्चित रूप से देख सकते हैं: यदि, उदाहरण के लिए, एक विशेष डोमेन पहले 10 अनुरोधों में 80% प्रॉक्सी पूल को बैन करता है — समस्या प्रॉक्सी में नहीं है, बल्कि अनुरोधों के पैटर्न में है (यूजर-एजेंट का रोटेशन नहीं, बहुत उच्च आवृत्ति, अनुरोधों के बीच विराम का अभाव)।

पूर्ण मिडलवेयर का कार्यशील उदाहरण

सब कुछ settings.py में एकत्र करें — मिडलवेयर का क्रम महत्वपूर्ण है, क्योंकि यह निर्धारित करता है कि जाँचें किस क्रम में लागू होती हैं:

DOWNLOADER_MIDDLEWARES = {
    "myproject.middlewares.ProxyAuthMiddleware": 350,
    "myproject.middlewares.StickySessionMiddleware": 400,
    "myproject.middlewares.SmartRetryMiddleware": 550,
    "myproject.middlewares.ProxyStatsMiddleware": 900,
}

RETRY_ENABLED = False  # मानक पुनः प्रयास को बंद करें, अपनी का उपयोग करें
DOWNLOAD_TIMEOUT = 15
CONCURRENT_REQUESTS_PER_DOMAIN = 8

RETRY_ENABLED = False पर ध्यान दें — यह महत्वपूर्ण है, अन्यथा Scrapy का अंतर्निहित पुनः प्रयास तंत्र आपकी प्रॉक्सी परिवर्तन की तर्क के साथ संघर्ष करेगा, और अनुरोध डुप्लिकेट या दो बार पुनः प्रयास करना शुरू कर देंगे। CONCURRENT_REQUESTS_PER_DOMAIN को सीमित करना भी महत्वपूर्ण है — एक डोमेन पर बहुत अधिक समानांतरता, भले ही प्रॉक्सी का रोटेशन हो, एंटी-बॉट सिस्टम के लिए संदिग्ध लगती है।

Scrapy के लिए किस प्रकार की प्रॉक्सी चुनें

मिडलवेयर समस्या का आधा समाधान करता है, दूसरी आधी समस्या — कार्य के लिए सही प्रॉक्सी पूल का चयन करना। नीचे सामान्य पार्सिंग परिदृश्यों के लिए तुलना है।

प्रॉक्सी का प्रकार कब उपयोग करें फायदे नुकसान
डेटा सेंटर प्रॉक्सी कड़ी एंटी-बॉट सुरक्षा के बिना सार्वजनिक पृष्ठों का बड़े पैमाने पर पार्सिंग उच्च गति, कम ट्रैफ़िक लागत आसानी से पहचान लिए जाते हैं, अक्सर ब्लैकलिस्ट में होते हैं
रिज़िडेंट प्रॉक्सी मार्केटप्लेस, JS-सुरक्षा वाली साइटों, प्रमाणीकरण का पार्सिंग वास्तविक IP, बैन का कम प्रतिशत, स्टिकी-सेशन्स का समर्थन DC की तुलना में गति कम है
मोबाइल प्रॉक्सी कड़ी एंटी-बॉट सुरक्षा वाली साइटों और API के मोबाइल संस्करणों का पार्सिंग लक्षित साइटों से अधिकतम विश्वास ट्रैफ़िक की सबसे उच्च लागत

व्यावहारिक नियम: बिना मजबूत सुरक्षा के स्थिर पृष्ठों के पार्सिंग के लिए, इस लेख से समझदार मिडलवेयर के साथ सस्ते डेटा सेंटर प्रॉक्सी उपयुक्त हैं। यदि साइट Cloudflare, PerimeterX, DataDome या समान सिस्टम का उपयोग करती है — डेटा सेंटर IP लगभग तुरंत बैन हो जाएंगे, और यहाँ पहले से ही रेजिडेंट पूल पर स्विच करना अधिक लाभदायक है, मिडलवेयर के डिबगिंग पर समय बचाते हुए।

पार्सर को प्रोडक्शन में लॉन्च करने से पहले चेक-लिस्ट

  • मिडलवेयर कूलडाउन पर बैन किए गए प्रॉक्सी को बाहर करता है, न कि उन्हें यादृच्छिक रूप से चुनता है
  • पुनः प्रयास तर्क त्रुटियों के प्रकार (403/407 बनाम 429 बनाम 5xx) को विभिन्न कूलडाउन के साथ भेद करता है
  • सत्रीय कार्यों के लिए स्टिकी-प्रॉक्सी बाइंडिंग का उपयोग किया जाता है, न कि हर अनुरोध पर परिवर्तन
  • प्रॉक्सी प्रमाणीकरण को केवल URL के माध्यम से नहीं, बल्कि Proxy-Authorization हेडर के माध्यम से भेजा जाता है
  • समस्याओं के निदान के लिए डोमेन और प्रॉक्सी पर बैन की सांख्यिकी रखी जाती है
  • मानक RetryMiddleware Scrapy को बंद किया गया है, ताकि कस्टम तर्क के साथ संघर्ष न हो
  • समानांतरता को समझदारी से सीमित किया गया है, न कि अधिकतम पर सेट किया गया है

निष्कर्ष

Scrapy में प्रॉक्सी ट्रैफ़िक की खपत से संबंधित अधिकांश समस्याएँ अधिक IP पूल खरीदने से नहीं, बल्कि मिडलवेयर की तर्क को सुधारने से हल होती हैं: त्रुटियों को प्रकारों में विभाजित करना, जटिल परिदृश्यों के लिए स्टिकी-सेशन्स, सही प्रमाणीकरण और बैन की निरंतर निगरानी। इस लेख से कोड को आधार के रूप में लिया जा सकता है और इसे विशिष्ट परियोजना के लिए अनुकूलित किया जा सकता है — संरचना छोटे पार्सरों और वितरित Scrapy-Cluster इंस्टॉलेशन दोनों के लिए कार्यशील रहती है।

यदि मिडलवेयर पहले से ही सही ढंग से सेट किया गया है, और फिर भी बैन बहुत बार होते हैं — तो शायद समस्या स्वयं IP पूल की गुणवत्ता में है। उन्नत एंटी-बॉट सुरक्षा वाली साइटों के पार्सिंग के लिए, रिज़िडेंट प्रॉक्सी का प्रयास करना चाहिए, जो सत्रों का समर्थन करते हैं — वे डेटा सेंटर के पते की तुलना में सुरक्षा के सक्रियण के प्रतिशत को काफी कम करते हैं, जबकि मिडलवेयर की वही तर्क होती है।