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?
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 |
Installazione
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
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
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"
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
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 |
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ì | 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
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
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.
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
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
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à:
- La configurazione è codice. Modifica
settings.pyinvece di cliccare checkbox. - L’estrazione è esplicita. Tu scrivi quali dati catturare.
- La programmazione è nativa. Aggiungi comandi a cron o CI/CD.
- Il debugging sono i log. Abilita
AUTOTHROTTLE_DEBUGper 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:
- Audit veloci sotto 500 URL
- Risultati necessari in minuti
- Esplorazione visuale del sito
- Non a proprio agio con CLI
- Usare i dati di Screaming Frog con Redirects.net
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.