Screaming Frog ज़्यादातर SEOs के लिए पसंदीदा क्रॉलर है, लेकिन आपने शायद इसकी सीमाओं को महसूस किया है: मुफ्त वर्जन पर 500-URL की लिमिट, बड़ी साइट्स पर RAM का ओवरलोड होना, या GUI की निगरानी के बिना क्रॉल्स को ऑटोमेट करने की इच्छा। Scrapy वह ओपन-सोर्स Python फ्रेमवर्क है जो इन सीमाओं को हटाता है।

अगर आप npm install या git clone चला सकते हैं, तो आप Scrapy चला सकते हैं। लर्निंग कर्व असली है लेकिन मैनेज करने योग्य है, खासकर अगर आप एजेंटिक कोडिंग वर्कफ्लो के माध्यम से CLI टूल्स के साथ पहले से सहज हो रहे हैं।

Scrapy क्यों?

मुख्य लाभ

Screaming Frog त्वरित ऑडिट के लिए अच्छा काम करता है। लेकिन इसकी सीमाएं हैं:

सीमा प्रभाव
500 URL मुफ्त लिमिट बड़ी साइट्स के लिए $259/वर्ष लाइसेंस की आवश्यकता
मेमोरी-हंग्री बड़े क्रॉल्स 8GB+ RAM खपत कर सकते हैं
GUI-डिपेंडेंट ऑटोमेट या शेड्यूल करना मुश्किल
सीमित कस्टमाइज़ेशन कॉन्फ़िगरेशन विकल्प फिक्स्ड हैं

Scrapy इन्हें हल करता है:

Scrapy आपको क्या मिलता है
मुफ्त और ओपन-सोर्स कोई URL लिमिट नहीं, कोई लाइसेंस फीस नहीं
कम मेमोरी फुटप्रिंट डिस्क-बैक्ड क्यू RAM को नियंत्रण में रखते हैं
CLI-नेटिव स्क्रिप्टेबल, cron-एबल, CI/CD-रेडी
पूर्ण Python कस्टमाइज़ेशन जो चाहिए एक्सट्रैक्ट करें, जैसे चाहें फ़िल्टर करें
पॉज़/रिज़्यूम बड़े क्रॉल्स को कभी भी रोकें और जारी रखें
Scrapy हर चीज़ के लिए Screaming Frog को रिप्लेस नहीं करेगा। त्वरित ऑडिट अभी भी GUI में तेज़ हैं। लेकिन बड़े पैमाने के क्रॉल्स, ऑटोमेशन और कस्टम एक्सट्रैक्शन के लिए, इसे अपने टूलकिट में रखना उचित है।

इंस्टॉलेशन

सेटअप

Scrapy Python पर चलता है। चीज़ों को साफ रखने के लिए वर्चुअल एनवायरनमेंट का उपयोग करें:

Debian/Ubuntu:

sudo apt install python3.11-venv
python3 -m venv venv
source venv/bin/activate
pip install scrapy

macOS:

python3 -m venv venv
source venv/bin/activate
pip install scrapy

Windows:

python -m venv venv
venv\Scripts\activate
pip install scrapy
वर्चुअल एनवायरनमेंट महत्वपूर्ण हैं
हमेशा venv का उपयोग करें। ग्लोबली इंस्टॉल करना डिपेंडेंसी कॉन्फ्लिक्ट्स का कारण बनता है और रिप्रोड्यूसिबिलिटी को तोड़ता है।

प्रोजेक्ट बनाना

Scrapy इंस्टॉल होने के बाद:

scrapy startproject myproject
cd myproject
scrapy genspider sitename example.com

यह बनाता है:

myproject/
    scrapy.cfg
    myproject/
        __init__.py
        items.py
        middlewares.py
        pipelines.py
        settings.py
        spiders/
            __init__.py
            sitename.py

Spider कोड spiders/sitename.py में जाता है। कॉन्फ़िगरेशन settings.py में रहता है।

पोलाइट क्रॉलिंग के लिए सेटिंग्स

क्रिटिकल

कुछ भी चलाने से पहले settings.py कॉन्फ़िगर करें। ब्लॉक होना धीमे क्रॉलिंग से ज़्यादा समय बर्बाद करता है।

# पोलाइट क्रॉलिंग
CONCURRENT_REQUESTS_PER_DOMAIN = 5
DOWNLOAD_DELAY = 1
ROBOTSTXT_OBEY = True

# AutoThrottle - सर्वर रिस्पॉन्स के आधार पर स्पीड एडजस्ट करता है
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 5
AUTOTHROTTLE_MAX_DELAY = 60
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
AUTOTHROTTLE_DEBUG = True

# सेफ्टी लिमिट्स
CLOSESPIDER_PAGECOUNT = 10000

# आउटपुट
FEED_EXPORT_ENCODING = "utf-8"
AutoThrottle इनेबल होने पर, 5 कॉनकरेंट रिक्वेस्ट्स एक उचित शुरुआती बिंदु है। अगर सर्वर को दिक्कत हो तो AutoThrottle ऑटोमैटिकली स्लो हो जाएगा। AutoThrottle के बिना, 1-3 से कम शुरू करें।

AutoThrottle

AutoThrottle सर्वर रिस्पॉन्स टाइम को मॉनिटर करता है और क्रॉल स्पीड को ऑटोमैटिकली एडजस्ट करता है:

  • फास्ट रिस्पॉन्स → स्पीड अप
  • स्लो रिस्पॉन्स → स्लो डाउन
  • एरर्स/टाइमआउट्स → काफी स्लो डाउन

Screaming Frog के फिक्स्ड डिले के विपरीत, यह वास्तविक सर्वर कंडीशंस के अनुसार एडाप्ट होता है।

स्टेटस कोड हैंडलिंग

डिफ़ॉल्ट रूप से, Scrapy का HttpErrorMiddleware नॉन-2xx रिस्पॉन्सेस को चुपचाप ड्रॉप कर देता है। इसका मतलब 404s, 301s, 500s आपके callback तक पहुंचने से पहले ही हटा दिए जाते हैं। आपका क्रॉल 100% 200 स्टेटस कोड दिखा सकता है, इसलिए नहीं कि साइट परफेक्ट है, बल्कि इसलिए कि एरर्स फ़िल्टर किए जा रहे हैं।

सभी स्टेटस कोड्स कैप्चर करने के लिए यह अपनी spider क्लास में जोड़ें:

handle_httpstatus_list = [200, 301, 302, 403, 404, 500, 502, 503]

Screaming Frog डिफ़ॉल्ट रूप से सभी स्टेटस कोड्स कैप्चर करता है। यह सेटिंग Scrapy को उस व्यवहार के साथ अलाइन करती है।

रियल-वर्ल्ड परफॉर्मेंस

बेंचमार्क्स

5 कॉनकरेंट रिक्वेस्ट्स और AutoThrottle इनेबल के साथ टेस्ट क्रॉल के वास्तविक नंबर:

क्रॉल प्रोग्रेस पेज/मिनट नोट्स
0-200 पेज 14-22 रैंप-अप
200-500 पेज 10-12 स्टेबलाइज़िंग
500-1,000 पेज 7-10 AutoThrottle एडजस्टिंग
1,000+ पेज 5-7 स्टेडी स्टेट
स्पीड vs. रिलायबिलिटी
ये स्पीड्स धीमी लगती हैं। यही पॉइंट है। AutoThrottle सर्वर हेल्थ को रॉ स्पीड से ज़्यादा प्राथमिकता देता है। ब्लॉक होना और रीस्टार्ट करना मेथोडिकल क्रॉल से ज़्यादा समय बर्बाद करता है।

फीचर कम्पैरिज़न

फीचर Screaming Frog Scrapy
कॉस्ट मुफ्त <500 URLs, ~$259/वर्ष मुफ्त, ओपन सोर्स
मैक्स क्रॉल साइज़ मेमोरी-लिमिटेड डिस्क-बैक्ड क्यू
कस्टमाइज़ेशन लिमिटेड कॉन्फिग ऑप्शंस फुल Python कोड
शेड्यूलिंग मैनुअल या थर्ड-पार्टी नेटिव CLI, cron-एबल
पॉज़/रिज़्यूम हां हां (JOBDIR के साथ)
लर्निंग कर्व लो (GUI) मीडियम (कोड)
रेट लिमिटिंग बेसिक फिक्स्ड डिले AutoThrottle (एडैप्टिव)
JavaScript रेंडरिंग ऑप्शनल (Chrome) ऑप्शनल (playwright/splash)
स्टेटस कोड्स सभी डिफ़ॉल्ट रूप से कॉन्फ़िगरेशन आवश्यक
सबडोमेन फ़िल्टरिंग GUI चेकबॉक्स कोड (फ्लेक्सिबल regex)
एक्सपोर्ट फॉर्मेट्स CSV, Excel, etc. JSON, CSV, XML, कस्टम
CI/CD इंटीग्रेशन मुश्किल नेटिव

URL फ़िल्टरिंग

प्रिसाइज़ कंट्रोल

Screaming Frog चेकबॉक्स उपयोग करता है। Scrapy कोड उपयोग करता है। ट्रेडऑफ है लर्निंग कर्व प्रिसीज़न के लिए।

इंटरनेशनल पाथ्स को एक्सक्लूड करना:

import re
from urllib.parse import urlparse

class MySiteSpider(scrapy.Spider):
    name = "mysite"
    allowed_domains = ["example.com", "www.example.com"]
    start_urls = ["https://www.example.com/"]

    # /uk/, /fr/, /de/ जैसे इंटरनेशनल पाथ्स स्किप करें
    EXCLUDED_PATTERNS = re.compile(
        r"/(in|au|th|es|hk|sg|ph|my|ca|cn|uk|kr|id|fr|vn|de|jp|nl|it|tw)/"
    )

    def filter_links(self, links):
        filtered = []
        for link in links:
            hostname = urlparse(link.url).hostname or ""
            if hostname not in ("example.com", "www.example.com"):
                continue
            if self.EXCLUDED_PATTERNS.search(link.url):
                continue
            filtered.append(link)
        return filtered

आप URL पैटर्न्स, क्वेरी पैरामीटर्स, रिस्पॉन्स हेडर्स, पेज कंटेंट या किसी भी कॉम्बिनेशन से फ़िल्टर कर सकते हैं।

साइटमैप इंटीग्रेशन

URL खोज

Screaming Frog में एक सिंपल “use sitemap” चेकबॉक्स है। Scrapy को कस्टम कोड की ज़रूरत है, लेकिन यह आपको साइटमैप्स को पार्स करने और अपने क्रॉल के साथ इंटीग्रेट करने पर पूर्ण नियंत्रण देता है।

साइटमैप सपोर्ट क्यों जोड़ें?

  • मुख्य नेविगेशन से लिंक नहीं किए गए URLs खोजता है
  • ऑर्फन पेज ढूंढता है जो लिंक-बेस्ड क्रॉलिंग मिस कर देगी
  • तुलना के लिए साइट की “ऑफिशियल” URL लिस्ट मिलती है
  • केवल लिंक्स फॉलो करने से ज़्यादा पेज मिल सकते हैं
  • पूर्ण SEO ऑडिट के लिए आवश्यक

साइटमैप डिटेक्शन और पार्सिंग इनेबल करने के लिए अपने CrawlSpider में ये मेथड्स जोड़ें:

def start_requests(self):
    # First, fetch robots.txt to find sitemaps
    yield Request(
        "https://www.example.com/robots.txt",
        callback=self.parse_robots,
        errback=self.handle_error,
        dont_filter=True,
    )
    # Also try common sitemap locations directly
    common_sitemaps = [
        "https://www.example.com/sitemap.xml",
        "https://www.example.com/sitemap_index.xml",
    ]
    for sitemap_url in common_sitemaps:
        yield Request(
            sitemap_url,
            callback=self.parse_sitemap,
            errback=self.handle_error,
            meta={"sitemap_url": sitemap_url},
        )
    # Also start normal crawl from homepage
    for url in self.start_urls:
        yield Request(url, callback=self.parse_start_url)

def parse_robots(self, response):
    """Parse robots.txt to find sitemap declarations"""
    if response.status != 200:
        return
    for line in response.text.splitlines():
        line = line.strip()
        if line.lower().startswith("sitemap:"):
            sitemap_url = line.split(":", 1)[1].strip()
            if self.is_valid_url(sitemap_url):
                self.logger.info(f"Found sitemap in robots.txt: {sitemap_url}")
                yield Request(
                    sitemap_url,
                    callback=self.parse_sitemap,
                    errback=self.handle_error,
                    meta={"sitemap_url": sitemap_url},
                )

def parse_sitemap(self, response):
    """Parse XML sitemap or sitemap index"""
    if response.status != 200:
        return
    content_type = response.headers.get("Content-Type", b"").decode("utf-8", errors="ignore")
    # Check if this is XML content
    if "xml" not in content_type and not response.text.strip().startswith("<?xml"):
        return
    # Check for sitemap index (contains other sitemaps)
    sitemap_locs = response.xpath("//sitemap/loc/text()").getall()
    if sitemap_locs:
        self.logger.info(f"Found sitemap index with {len(sitemap_locs)} sitemaps")
        for loc in sitemap_locs:
            if self.is_valid_url(loc):
                yield Request(
                    loc,
                    callback=self.parse_sitemap,
                    errback=self.handle_error,
                    meta={"sitemap_url": loc},
                )
    # Parse URL entries from sitemap
    url_locs = response.xpath("//url/loc/text()").getall()
    if url_locs:
        self.logger.info(f"Found {len(url_locs)} URLs in sitemap: {response.url}")
        for loc in url_locs:
            if self.is_valid_url(loc) and self.should_crawl_url(loc):
                yield Request(
                    loc,
                    callback=self.parse_page,
                    errback=self.handle_error,
                )

def parse_start_url(self, response):
    """Handle the start URL and trigger rules"""
    yield from self.parse_page(response)
    yield from self._requests_to_follow(response)

def is_valid_url(self, url):
    """Check if URL is valid and within allowed domains"""
    try:
        parsed = urlparse(url)
        hostname = parsed.hostname or ""
        return hostname in ("example.com", "www.example.com")
    except Exception:
        return False

def should_crawl_url(self, url):
    """Apply the same filtering as filter_links"""
    if self.EXCLUDED_PATTERNS.search(url):
        return False
    return True

def handle_error(self, failure):
    """Handle request errors gracefully"""
    self.logger.warning(f"Request failed: {failure.request.url}")

यह कैसे काम करता है:

  1. start_requests() पहले साइटमैप्स फेच करने के लिए डिफ़ॉल्ट व्यवहार को ओवरराइड करता है
  2. parse_robots() robots.txt में Sitemap: लाइनें ढूंढता है
  3. parse_sitemap() साइटमैप इंडेक्सेस और रेगुलर साइटमैप्स दोनों हैंडल करता है
  4. XPath //sitemap/loc साइटमैप इंडेक्स फाइल्स में नेस्टेड साइटमैप्स ढूंढता है
  5. XPath //url/loc वास्तविक पेज URLs ढूंढता है
  6. साइटमैप URLs पर भी वही डोमेन और पैटर्न फ़िल्टरिंग लागू होती है
  7. Scrapy का बिल्ट-इन deduplication साइटमैप और लिंक्स दोनों में मिले पेजों को डबल-क्रॉलिंग से रोकता है
फीचर Screaming Frog Scrapy
साइटमैप डिटेक्शन चेकबॉक्स कस्टम कोड
robots.txt पार्सिंग ऑटोमैटिक कस्टम कोड
साइटमैप इंडेक्स सपोर्ट हां हां (कोड के साथ)
URL फ़िल्टरिंग GUI ऑप्शंस कोड (पूर्ण नियंत्रण)
क्रॉल के साथ मर्ज हां हां
कस्टम साइटमैप लोकेशन्स मैनुअल एंट्री कोई भी लोकेशन कोड करें

साइटमैप इंटीग्रेशन के साथ, आप नेविगेशन से लिंक नहीं किए गए ऑर्फन पेज, साइटमैप्स में अभी भी लिस्टेड पुराना आर्काइव्ड कंटेंट, trailing slashes के साथ या बिना URL वेरिएशंस, और robots.txt द्वारा ब्लॉक लेकिन साइटमैप में मौजूद पेज खोज सकते हैं। यह SEO ऑडिट के लिए साइट की अधिक पूर्ण तस्वीर देता है।

पॉज़ और रिज़्यूम

एसेंशियल

1,000 पेजों से ज़्यादा के क्रॉल्स के लिए, JOBDIR के साथ पॉज़/रिज़्यूम इनेबल करें:

scrapy crawl myspider -o output.json -s JOBDIR=crawl_state

Scrapy crawl_state/ में स्टेट सेव करता है। पॉज़ करने के लिए Ctrl+C दबाएं। रिज़्यूम करने के लिए वही कमांड चलाएं।

प्रोडक्शन क्रॉल्स के लिए हमेशा JOBDIR उपयोग करें। नेटवर्क इश्यूज़, सिस्टम रीस्टार्ट्स, या बस दिन के लिए रुकने की ज़रूरत से प्रोटेक्ट करता है।

स्टेट में पेंडिंग URLs, देखे गए URLs और रिक्वेस्ट क्यू शामिल है। यह Screaming Frog की सेव/लोड फीचर से ज़्यादा रोबस्ट है क्योंकि यह फाइल-बेस्ड है और सिस्टम रीस्टार्ट्स में भी बना रहता है।

JavaScript रेंडरिंग

Scrapy केवल रॉ HTML फेच करता है। यह JavaScript रेंडर नहीं करता। यह वही है जो curl रिटर्न करता है।

ज़्यादातर SEO क्रॉल्स के लिए, यह ठीक है:

  • मेटा टैग्स, canonicals और h1s आमतौर पर इनीशियल HTML में होते हैं
  • सर्च इंजन मुख्य रूप से सर्वर-रेंडर्ड कंटेंट इंडेक्स करते हैं
  • ज़्यादातर ई-कॉमर्स और कंटेंट साइट्स सर्वर-रेंडर्ड होती हैं

अगर आपकी टारगेट साइट क्लाइंट-साइड कंटेंट रेंडर करती है, आपके पास ऑप्शंस हैं:

पैकेज नोट्स
scrapy-playwright Chromium/Firefox/WebKit उपयोग करता है। मॉडर्न JS साइट्स के लिए रेकमेंडेड
scrapy-splash लाइटवेट, Docker-बेस्ड रेंडरर
scrapy-selenium पुराना अप्रोच, अभी भी काम करता है

JS रेंडरिंग काफी धीमी और रिसोर्स-इंटेंसिव है। इसे तभी जोड़ें जब साइट को इसकी ज़रूरत हो।

Screaming Frog का भी यही ट्रेडऑफ है। JavaScript रेंडरिंग इनेबल करना Chrome को अंडर द हुड उपयोग करता है और क्रॉल्स को काफी धीमा करता है।

मेमोरी मैनेजमेंट

~1,300 पेजों पर पूर्ण फील्ड एक्सट्रैक्शन के साथ:

  • मेमोरी: ~265 MB
  • CPU: ~4%

JOBDIR उपयोग करने से रिक्वेस्ट क्यू डिस्क पर मूव हो जाती है, मेमोरी कम रखते हुए। बहुत बड़े क्रॉल्स (100k+ URLs) के लिए, ये सेटिंग्स जोड़ें:

MEMUSAGE_LIMIT_MB = 1024
MEMUSAGE_WARNING_MB = 800
SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleFifoDiskQueue'
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.FifoMemoryQueue'

यह मेमोरी उपयोग को कैप करता है और scheduler के लिए डिस्क-बैक्ड क्यू फोर्स करता है।

आउटपुट डेटा

कस्टमाइज़ेबल

Scrapy आपको वो एक्सट्रैक्ट करने देता है जो आपको चाहिए। यहां एक comprehensive parse मेथड है जो common SEO फील्ड्स कैप्चर करता है:

def parse_page(self, response):
    yield {
        # बेसिक इन्फो
        "url": response.url,
        "status": response.status,
        "response_time": response.meta.get("download_latency"),

        # SEO फील्ड्स
        "title": response.css("title::text").get(),
        "meta_description": response.css('meta[name="description"]::attr(content)').get(),
        "h1": response.css("h1::text").get(),
        "h2_count": len(response.css("h2").getall()),

        # टेक्निकल
        "canonical": response.css('link[rel="canonical"]::attr(href)').get(),
        "robots": response.css('meta[name="robots"]::attr(content)').get(),
        "content_type": response.headers.get("Content-Type", b"").decode("utf-8"),
        "word_count": len(response.css("body *::text").getall()),

        # लिंक्स
        "internal_links": len([
            a for a in response.css("a::attr(href)").getall()
            if a.startswith("/") or "example.com" in a
        ]),
        "external_links": len([
            a for a in response.css("a::attr(href)").getall()
            if a.startswith("http") and "example.com" not in a
        ]),
    }

यह Screaming Frog के “Internal:All” टैब के समान डेटा देता है, लेकिन आपके कंट्रोल में।

एक्सपोर्ट फॉर्मेट्स: JSON (-o output.json), JSON Lines (-o output.jsonl), CSV (-o output.csv), XML (-o output.xml)।

बड़े क्रॉल्स के लिए JSON Lines बेस्ट है। आप रियल-टाइम में आउटपुट मॉनिटर कर सकते हैं।

Screaming Frog → Scrapy

ट्रांसलेशन गाइड

SF वर्कफ्लो को Scrapy में मैप करना:

Screaming Frog एक्शन Scrapy इक्विवेलेंट
नया क्रॉल शुरू करें scrapy crawl spidername
क्रॉल डिले सेट करें सेटिंग्स में DOWNLOAD_DELAY
कॉनकरेंट थ्रेड्स लिमिट करें CONCURRENT_REQUESTS_PER_DOMAIN
robots.txt रिस्पेक्ट करें ROBOTSTXT_OBEY = True
CSV में एक्सपोर्ट करें -o output.csv
क्रॉल सेव/लोड करें -s JOBDIR=crawl_state
सबडोमेन फ़िल्टर करें spider में कोड (regex)
कस्टम एक्सट्रैक्शन parse() में CSS/XPath सेलेक्टर्स

माइंडसेट शिफ्ट्स:

  1. कॉन्फ़िगरेशन कोड है। चेकबॉक्स क्लिक करने के बजाय settings.py एडिट करें।
  2. एक्सट्रैक्शन एक्सप्लिसिट है। आप लिखते हैं कि क्या डेटा कैप्चर करना है।
  3. शेड्यूलिंग नेटिव है। cron या CI/CD में कमांड्स जोड़ें।
  4. डीबगिंग लॉग्स है। क्या हो रहा है देखने के लिए AUTOTHROTTLE_DEBUG इनेबल करें।

फुल वर्कफ्लो

ऊपर दी गई स्टैंडर्ड सेटिंग्स के साथ, आप 15 मिनट से कम में Scrapy इंस्टॉल और क्रॉलिंग कर सकते हैं:

python3 -m venv venv
source venv/bin/activate  # Windows पर venv\Scripts\activate
pip install scrapy
scrapy startproject urlcrawler
cd urlcrawler
scrapy genspider mysite example.com
# पोलाइट क्रॉलिंग कॉन्फिग के साथ settings.py एडिट करें
# अपनी parse लॉजिक के साथ spiders/mysite.py एडिट करें
scrapy crawl mysite -o urls.jsonl -s JOBDIR=crawl_state

Scrapy Shell

जब आप कस्टम कॉन्फ़िगरेशन बना रहे हों, अपने सेलेक्टर्स और सेटिंग्स को इंटरैक्टिवली टेस्ट करने के लिए Scrapy Shell उपयोग करें:

scrapy shell "https://example.com"

यह रिस्पॉन्स पहले से लोडेड के साथ एक इंटरैक्टिव Python कंसोल खोलता है। अपने spider में जोड़ने से पहले रियल-टाइम में CSS और XPath सेलेक्टर्स टेस्ट करें:

>>> response.css('title::text').get()
'Example Domain'
>>> response.xpath('//h1/text()').get()
'Example Domain'

Scrapy Shell इटरेशन टाइम को काफी कम करता है। फुल क्रॉल्स चलाए बिना एक्सट्रैक्शन लॉजिक वैलिडेट करें।

Complete Spider Template

यहां एक production-ready spider template है जो इस गाइड की सभी best practices को combine करता है:

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from urllib.parse import urlparse
import re


class SEOSpider(CrawlSpider):
    name = "seo_spider"
    allowed_domains = ["example.com", "www.example.com"]
    start_urls = ["https://www.example.com/"]

    # सभी स्टेटस कोड्स कैप्चर करें
    handle_httpstatus_list = [200, 301, 302, 403, 404, 500, 502, 503]

    # इंटरनेशनल पाथ्स एक्सक्लूड करें
    EXCLUDED_PATTERNS = re.compile(
        r"/(in|au|th|es|hk|sg|ph|my|ca|cn|uk|kr|id|fr|vn|de|jp|nl|it|tw)/"
    )

    rules = (
        Rule(
            LinkExtractor(
                allow_domains=["example.com", "www.example.com"],
                deny=[r"\?", r"#"],  # क्वेरी स्ट्रिंग्स और फ्रैगमेंट्स स्किप करें
            ),
            callback="parse_page",
            follow=True,
        ),
    )

    def parse_page(self, response):
        # एक्सक्लूडेड पाथ्स स्किप करें
        if self.EXCLUDED_PATTERNS.search(response.url):
            return

        yield {
            # बेसिक इन्फो
            "url": response.url,
            "status": response.status,
            "response_time": response.meta.get("download_latency"),

            # SEO फील्ड्स
            "title": response.css("title::text").get(),
            "meta_description": response.css(
                'meta[name="description"]::attr(content)'
            ).get(),
            "h1": response.css("h1::text").get(),
            "h2_count": len(response.css("h2").getall()),

            # टेक्निकल
            "canonical": response.css('link[rel="canonical"]::attr(href)').get(),
            "robots": response.css('meta[name="robots"]::attr(content)').get(),
            "content_type": response.headers.get("Content-Type", b"").decode("utf-8"),
            "word_count": len(response.css("body *::text").getall()),

            # लिंक्स
            "internal_links": len([
                a for a in response.css("a::attr(href)").getall()
                if a.startswith("/") or "example.com" in a
            ]),
            "external_links": len([
                a for a in response.css("a::attr(href)").getall()
                if a.startswith("http") and "example.com" not in a
            ]),
        }

इसे spiders/seo_spider.py में सेव करें और चलाएं:

scrapy crawl seo_spider -o results.jsonl -s JOBDIR=crawl_state

कब क्या उपयोग करें

Screaming Frog:

Scrapy:

  • 10,000 URLs से ज़्यादा की साइट्स
  • ऑटोमेटेड, शेड्यूल्ड क्रॉल्स
  • कस्टम एक्सट्रैक्शन ज़रूरतें
  • CI/CD इंटीग्रेशन
  • मेमोरी कंस्ट्रेंट्स
  • वर्जन-कंट्रोल्ड कॉन्फिग्स

निष्कर्ष

Scrapy की सेटअप कर्व Screaming Frog से ज़्यादा स्टीप है, लेकिन यह उन प्रैक्टिकल लिमिट्स को हटाता है जो GUI क्रॉलर्स लगाते हैं। कोई URL कैप्स नहीं, कोई लाइसेंस फीस नहीं, कम मेमोरी यूसेज और नेटिव ऑटोमेशन।

छोटे से शुरू करें। जो साइट आप जानते हैं उसे क्रॉल करें। कंज़र्वेटिव सेटिंग्स उपयोग करें। आउटपुट की तुलना Screaming Frog से करें। डेटा मैच होगा, लेकिन आपके पास एक ऐसा टूल होगा जो स्केल करता है।