Screaming Frog è il crawler di riferimento per la maggior parte dei SEO, ma probabilmente hai già incontrato i suoi limiti: il tetto di 500 URL nella versione gratuita, la RAM al massimo su siti grandi, o il desiderio di automatizzare i crawl senza sorvegliare una GUI. Scrapy è il framework Python open source che rimuove questi limiti.

Se riesci a eseguire npm install o git clone, puoi eseguire Scrapy. La curva di apprendimento è reale ma gestibile, specialmente se ti stai già familiarizzando con gli strumenti CLI attraverso workflow di codifica agentica.

Perché Scrapy?

Vantaggi Chiave

Screaming Frog funziona bene per audit veloci. Ma ha dei limiti:

Limitazione Impatto
Limite gratuito 500 URL Richiede licenza $259/anno per siti più grandi
Avido di memoria Crawl grandi possono consumare 8GB+ di RAM
Dipendente dalla GUI Difficile da automatizzare o programmare
Personalizzazione limitata Le opzioni di configurazione sono fisse

Scrapy risolve questi problemi:

Scrapy Cosa ottieni
Gratuito e open source Nessun limite URL, nessuna tariffa di licenza
Footprint di memoria ridotto Code su disco mantengono la RAM sotto controllo
Nativo CLI Scriptabile, programmabile con cron, pronto per CI/CD
Personalizzazione Python completa Estrai ciò che ti serve, filtra come vuoi
Pausa/Riprendi Ferma e continua crawl grandi in qualsiasi momento
Scrapy non sostituirà Screaming Frog per tutto. Gli audit veloci sono ancora più rapidi in una GUI. Ma per crawl su larga scala, automazione ed estrazione personalizzata, vale la pena averlo nel tuo toolkit.

Installazione

Setup

Scrapy funziona su Python. Usa un ambiente virtuale per mantenere tutto pulito:

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
Gli Ambienti Virtuali Contano
Usa sempre un venv. L'installazione globale causa conflitti di dipendenze e compromette la riproducibilità.

Creare un Progetto

Con Scrapy installato:

scrapy startproject myproject
cd myproject
scrapy genspider sitename example.com

Questo crea:

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

Il codice dello spider va in spiders/sitename.py. La configurazione sta in settings.py.

Impostazioni per Crawling Educato

Critico

Configura settings.py prima di eseguire qualsiasi cosa. Essere bloccati spreca più tempo che crawlare lentamente.

# Crawling educato
CONCURRENT_REQUESTS_PER_DOMAIN = 5
DOWNLOAD_DELAY = 1
ROBOTSTXT_OBEY = True

# AutoThrottle - regola velocità in base alla risposta del server
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 5
AUTOTHROTTLE_MAX_DELAY = 60
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
AUTOTHROTTLE_DEBUG = True

# Limiti di sicurezza
CLOSESPIDER_PAGECOUNT = 10000

# Output
FEED_EXPORT_ENCODING = "utf-8"
Con AutoThrottle abilitato, 5 richieste concorrenti è un punto di partenza ragionevole. AutoThrottle rallenterà automaticamente se il server ha difficoltà. Senza AutoThrottle, inizia più basso a 1-3.

AutoThrottle

AutoThrottle monitora i tempi di risposta del server e regola automaticamente la velocità di crawl:

  • Risposte veloci → accelera
  • Risposte lente → rallenta
  • Errori/timeout → rallenta significativamente

A differenza dei ritardi fissi di Screaming Frog, si adatta alle condizioni reali del server.

Gestione Codici di Stato

Per impostazione predefinita, l’HttpErrorMiddleware di Scrapy elimina silenziosamente le risposte non-2xx. Ciò significa che 404, 301, 500 vengono scartati prima di raggiungere il tuo callback. Il tuo crawl potrebbe mostrare 100% codici di stato 200, non perché il sito è perfetto, ma perché gli errori vengono filtrati.

Aggiungi questo alla tua classe spider per catturare tutti i codici di stato:

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

Screaming Frog cattura tutti i codici di stato per impostazione predefinita. Questa impostazione allinea Scrapy a quel comportamento.

Performance nel Mondo Reale

Benchmark

Numeri reali da un crawl di test con 5 richieste concorrenti e AutoThrottle abilitato:

Progresso Crawl Pagine/Minuto Note
0-200 pagine 14-22 Avvio
200-500 pagine 10-12 Stabilizzazione
500-1.000 pagine 7-10 AutoThrottle si regola
1.000+ pagine 5-7 Stato stabile
Velocità vs. Affidabilità
Queste velocità sembrano lente. È il punto. AutoThrottle dà priorità alla salute del server sulla velocità bruta. Essere bloccati e riavviare spreca più tempo di un crawl metodico.

Confronto Funzionalità

Funzionalità Screaming Frog Scrapy
Costo Gratis <500 URL, ~$259/anno Gratis, open source
Dimensione max crawl Limitata dalla memoria Code su disco
Personalizzazione Opzioni config limitate Codice Python completo
Programmazione Manuale o terze parti CLI nativo, programmabile con cron
Pausa/Riprendi Sì (con JOBDIR)
Curva di apprendimento Bassa (GUI) Media (codice)
Limitazione velocità Ritardi fissi base AutoThrottle (adattivo)
Rendering JavaScript Opzionale (Chrome) Opzionale (playwright/splash)
Codici di stato Tutti per default Richiede configurazione
Filtro sottodomini Checkbox GUI Codice (regex flessibile)
Formati export CSV, Excel, ecc. JSON, CSV, XML, personalizzato
Integrazione CI/CD Difficile Nativa

Filtraggio URL

Controllo Preciso

Screaming Frog usa checkbox. Scrapy usa codice. Il compromesso è curva di apprendimento per precisione.

Escludere percorsi internazionali:

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/"]

    # Salta percorsi internazionali come /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

Puoi filtrare per pattern URL, parametri query, header di risposta, contenuto pagina o qualsiasi combinazione.

Pausa e Riprendi

Essenziale

Per crawl oltre 1.000 pagine, abilita pausa/riprendi con JOBDIR:

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

Scrapy salva lo stato in crawl_state/. Premi Ctrl+C per mettere in pausa. Esegui lo stesso comando per riprendere.

Usa sempre JOBDIR per crawl di produzione. Protegge da problemi di rete, riavvii di sistema, o semplicemente il bisogno di fermarsi per la giornata.

Lo stato include URL in attesa, URL visti e la coda richieste. Questo è più robusto della funzione salva/carica di Screaming Frog perché è basato su file e sopravvive ai riavvii di sistema.

Rendering JavaScript

Scrapy recupera solo HTML grezzo. Non esegue il rendering JavaScript. È lo stesso che restituisce curl.

Per la maggior parte dei crawl SEO, va bene:

  • Meta tag, canonical e h1 sono solitamente nell’HTML iniziale
  • I motori di ricerca indicizzano principalmente contenuti renderizzati lato server
  • La maggior parte dei siti e-commerce e di contenuti sono renderizzati lato server

Se il tuo sito target renderizza contenuti lato client, hai opzioni:

Pacchetto Note
scrapy-playwright Usa Chromium/Firefox/WebKit. Raccomandato per siti JS moderni
scrapy-splash Leggero, renderer basato su Docker
scrapy-selenium Approccio più vecchio, funziona ancora

Il rendering JS è significativamente più lento e richiede più risorse. Aggiungilo solo se il sito lo richiede.

Screaming Frog ha un compromesso simile. Abilitare il rendering JavaScript usa Chrome sotto il cofano e rallenta notevolmente i crawl.

Gestione Memoria

A ~1.300 pagine con estrazione completa dei campi:

  • Memoria: ~265 MB
  • CPU: ~4%

Usare JOBDIR sposta le code richieste su disco, mantenendo bassa la memoria. Per crawl molto grandi (100k+ URL), aggiungi queste impostazioni:

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

Questo limita l’uso della memoria e forza le code su disco per lo scheduler.

Dati di Output

Personalizzabile

Output base dello spider:

{
    "url": "https://www.example.com/page/",
    "title": "Titolo Pagina Qui",
    "status": 200
}

Per i crawl SEO, vorrai campi simili a ciò che esporta Screaming Frog:

def parse_page(self, response):
    yield {
        "url": response.url,
        "status": response.status,
        "title": response.css("title::text").get(),
        "meta_description": response.css("meta[name='description']::attr(content)").get(),
        "meta_robots": response.css("meta[name='robots']::attr(content)").get(),
        "h1": response.css("h1::text").get(),
        "canonical": response.css("link[rel='canonical']::attr(href)").get(),
        "og_title": response.css("meta[property='og:title']::attr(content)").get(),
        "og_description": response.css("meta[property='og:description']::attr(content)").get(),
        "word_count": len(response.text.split()) if response.status == 200 else None,
        "content_type": response.headers.get("Content-Type", b"").decode("utf-8", errors="ignore"),
    }

Aggiungi o rimuovi campi in base alle tue esigenze. I selettori CSS funzionano per qualsiasi elemento on-page.

Formati export: JSON (-o output.json), JSON Lines (-o output.jsonl), CSV (-o output.csv), XML (-o output.xml).

JSON Lines è migliore per crawl grandi. I file sono validi riga per riga durante il crawl, quindi puoi monitorare con tail -f. Il JSON standard non è valido fino al completamento del crawl.

Screaming Frog → Scrapy

Guida alla Traduzione

Mappare workflow SF a Scrapy:

Azione Screaming Frog Equivalente Scrapy
Avviare nuovo crawl scrapy crawl spidername
Impostare ritardo crawl DOWNLOAD_DELAY nelle impostazioni
Limitare thread concorrenti CONCURRENT_REQUESTS_PER_DOMAIN
Rispettare robots.txt ROBOTSTXT_OBEY = True
Esportare in CSV -o output.csv
Salvare/Caricare crawl -s JOBDIR=crawl_state
Filtrare sottodomini Codice nello spider (regex)
Estrazione personalizzata Selettori CSS/XPath in parse()

Cambi di mentalità:

  1. La configurazione è codice. Modifica settings.py invece di cliccare checkbox.
  2. L’estrazione è esplicita. Tu scrivi quali dati catturare.
  3. La programmazione è nativa. Aggiungi comandi a cron o CI/CD.
  4. Il debugging sono i log. Abilita AUTOTHROTTLE_DEBUG per vedere cosa succede.

Workflow Completo

Con le impostazioni standard sopra, puoi avere Scrapy installato e in crawling in meno di 15 minuti:

python3 -m venv venv
source venv/bin/activate  # venv\Scripts\activate su Windows
pip install scrapy
scrapy startproject urlcrawler
cd urlcrawler
scrapy genspider mysite example.com
# Modifica settings.py con config crawl educato
# Modifica spiders/mysite.py con la tua logica di parse
scrapy crawl mysite -o urls.jsonl -s JOBDIR=crawl_state

Scrapy Shell

Mentre costruisci configurazioni personalizzate, usa Scrapy Shell per testare selettori e impostazioni interattivamente:

scrapy shell "https://example.com"

Questo apre una console Python interattiva con la risposta già caricata. Testa selettori CSS e XPath in tempo reale prima di aggiungerli al tuo spider:

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

Scrapy Shell riduce significativamente il tempo di iterazione. Valida la logica di estrazione senza eseguire crawl completi.

Template Spider Completo

Uno spider pronto per la produzione con filtraggio URL, gestione codici di stato ed estrazione completa campi SEO:

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


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

    # Cattura tutti i codici di stato HTTP, non solo 2xx
    handle_httpstatus_list = [200, 301, 302, 403, 404, 500, 502, 503]

    # Pattern URL da escludere
    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=()),
            callback="parse_page",
            follow=True,
            process_links="filter_links",
        ),
    )

    def filter_links(self, links):
        filtered = []
        for link in links:
            parsed = urlparse(link.url)
            hostname = parsed.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

    def parse_page(self, response):
        yield {
            "url": response.url,
            "status": response.status,
            "title": response.css("title::text").get(),
            "meta_description": response.css("meta[name='description']::attr(content)").get(),
            "meta_robots": response.css("meta[name='robots']::attr(content)").get(),
            "h1": response.css("h1::text").get(),
            "canonical": response.css("link[rel='canonical']::attr(href)").get(),
            "og_title": response.css("meta[property='og:title']::attr(content)").get(),
            "og_description": response.css("meta[property='og:description']::attr(content)").get(),
            "word_count": len(response.text.split()) if response.status == 200 else None,
            "content_type": response.headers.get("Content-Type", b"").decode("utf-8", errors="ignore"),
        }

Sostituisci example.com con il tuo dominio target. Regola EXCLUDED_PATTERNS per la struttura URL del tuo sito.

Quando Usare Quale

Screaming Frog:

Scrapy:

  • Siti oltre 10.000 URL
  • Crawl automatizzati e programmati
  • Esigenze di estrazione personalizzata
  • Integrazione CI/CD
  • Vincoli di memoria
  • Configurazioni versionabili

La Conclusione

Scrapy ha una curva di setup più ripida di Screaming Frog, ma rimuove i limiti pratici che i crawler GUI impongono. Nessun limite URL, nessuna tariffa di licenza, minor utilizzo di memoria e automazione nativa.

Inizia in piccolo. Fai crawl di un sito che conosci. Usa impostazioni conservative. Confronta l’output con Screaming Frog. I dati corrisponderanno, ma avrai uno strumento che scala.