Smart_SEO_Media
EN
15 min lettura Tutorial

Come uso Claude per fare audit SEO in 20 minuti invece di ore

Quello che prima richiedeva un'intera giornata ora richiede 20 minuti. Il workflow esatto — con codice Python, chiamate Claude API e i risultati reali.

Arriva un nuovo cliente. Il sito non ranka. Il traffico è piatto o in calo, e hanno bisogno di risposte.

L'approccio vecchio? Aprire cinque tool diversi. Esportare CSV da Google Search Console, Screaming Frog, Ahrefs, forse Semrush. Fissare fogli di calcolo per ore cercando di ricostruire una storia da dati frammentati. A fine giornata hai un documento — ma hai bruciato le ore più produttive su lavoro che non richiedeva pensiero strategico.

Io non lo faccio più. Ho costruito una pipeline che combina script Python, la Claude API, dati live sulle keyword da DataForSEO, e una serie di prompt strutturati che gestiscono tutto: dal crawl iniziale a un calendario editoriale pubblicabile. Il pensiero strategico è ancora mio. Il lavoro meccanico no.

Ti mostro esattamente come funziona.

La pipeline in sintesi

Pipeline completa

CRAWL → AUDIT → GSC → GAP CONTENUTI → KW RESEARCH → PIANO → ARTICOLI
  ↑        ↑       ↑         ↑               ↑            ↑         ↑
Python  Claude  Claude+   Claude+        DataForSEO+   Claude   Claude+
crawler prompt  Python    DataForSEO     Claude score   skill   DataForSEO

Ogni passo alimenta il successivo. Puoi fermarti ai primi due per un audit rapido, oppure concatenare tutto per una strategia SEO completa. Vediamoli uno per uno.

Step 1: Crawl del sito

Tutto inizia con i dati. Uso un crawler Python che estrae gli essenziali tecnici da ogni pagina: title tag, meta description, heading, canonical, schema markup, link interni, immagini, tempi di caricamento, word count.

seo_crawler.py

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
import pandas as pd
import time, json

class SEOCrawler:
    def __init__(self, base_url, max_pages=100):
        self.base_url = base_url
        self.domain = urlparse(base_url).netloc
        self.max_pages = max_pages
        self.visited = set()
        self.results = []
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'SEOAuditBot/1.0 (internal audit tool)'
        })

    def crawl(self):
        self.queue = [self.base_url]
        while self.queue and len(self.visited) < self.max_pages:
            url = self.queue.pop(0)
            if url in self.visited:
                continue
            self.visited.add(url)
            page_data = self.analyze_page(url)
            if page_data:
                self.results.append(page_data)
            time.sleep(0.5)
        return self.results

    def analyze_page(self, url):
        try:
            response = self.session.get(url, timeout=15)
            load_time = response.elapsed.total_seconds()
            soup = BeautifulSoup(response.text, 'html.parser')
            title_tag = soup.find('title')
            title = title_tag.get_text().strip() if title_tag else None
            meta_desc_tag = soup.find('meta', attrs={'name': 'description'})
            meta_desc = meta_desc_tag['content'].strip() if meta_desc_tag else None
            h1_tags = [h.get_text().strip() for h in soup.find_all('h1')]
            h2_tags = [h.get_text().strip() for h in soup.find_all('h2')]
            canonical_tag = soup.find('link', attrs={'rel': 'canonical'})
            canonical = canonical_tag['href'] if canonical_tag else None
            images = soup.find_all('img')
            images_no_alt = [img for img in images
                             if not img.get('alt') or img['alt'].strip() == '']
            internal_links, external_links = [], []
            for link in soup.find_all('a', href=True):
                href = urljoin(url, link['href'])
                parsed = urlparse(href)
                if parsed.netloc == self.domain:
                    internal_links.append(href)
                    if href not in self.visited and href not in self.queue:
                        self.queue.append(href)
                else:
                    external_links.append(href)
            for tag in soup(['script','style','nav','footer','header']):
                tag.decompose()
            word_count = len(soup.get_text(separator=' ', strip=True).split())
            schema_types = []
            for script in soup.find_all('script',
                                        attrs={'type': 'application/ld+json'}):
                try:
                    data = json.loads(script.string)
                    if isinstance(data, dict):
                        schema_types.append(data.get('@type', 'Unknown'))
                except (json.JSONDecodeError, TypeError):
                    pass
            return {
                'url': url,
                'status_code': response.status_code,
                'load_time_seconds': round(load_time, 2),
                'title': title,
                'title_length': len(title) if title else 0,
                'meta_description': meta_desc,
                'meta_description_length': len(meta_desc) if meta_desc else 0,
                'h1_count': len(h1_tags),
                'h2_count': len(h2_tags),
                'canonical': canonical,
                'canonical_matches_url': canonical == url if canonical else None,
                'word_count': word_count,
                'total_images': len(images),
                'images_without_alt': len(images_no_alt),
                'internal_links_count': len(internal_links),
                'schema_types': schema_types,
                'has_schema': len(schema_types) > 0,
            }
        except requests.RequestException as e:
            return {'url': url, 'status_code': 'Error', 'error': str(e)}

    def to_csv(self, filename='seo_crawl_data.csv'):
        df = pd.DataFrame(self.results)
        df.to_csv(filename, index=False)
        return df

Niente di complicato. Dati puliti, una riga per pagina, pronti per essere passati a Claude.

Step 2: L'audit tecnico con Claude

Qui entra Claude. Non passo solo il CSV dicendo "analizza questo". Uso un prompt strutturato che include il contesto del cliente — settore, obiettivi, dimensione del team, CMS. Claude usa tutto per calibrare le raccomandazioni.

audit.py

import anthropic, pandas as pd

def run_seo_audit(csv_path, client_info):
    client = anthropic.Anthropic()
    df = pd.read_csv(csv_path)
    crawl_data = df.to_json(orient='records', indent=2)

    prompt = f"""Sei un consulente SEO tecnico senior.

## CONTESTO CLIENTE
- Sito: {client_info['website']}
- Settore: {client_info['industry']}
- Obiettivo: {client_info['goal']}
- Team: {client_info['team_size']}
- CMS: {client_info['cms']}

## DATI CRAWL
{crawl_data}

## DELIVERABLE
Produci un audit prioritizzato:

1. EXECUTIVE SUMMARY (3-4 frasi, salute complessiva)
2. PROBLEMI CRITICI — stanno attivamente danneggiando il ranking.
   Per ognuno: Cosa (con URL) → Perché conta → Come fixare → Effort → Impact
3. QUICK WINS — alto impatto, meno di un giorno di lavoro.
4. GAP DI CONTENUTO — pagine thin, cluster mancanti, opportunità keyword.
5. DEBITO TECNICO — importante ma non urgente (orizzonte 1-3 mesi).
6. ACTION PLAN — Top 15 azioni ordinate per impact/effort.
   Formato: Azione | Ore | Impatto | Priorità (P0-P3)

Sii specifico. Ogni raccomandazione collegata ai dati reali."""

    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=8000,
        messages=[{"role": "user", "content": prompt}]
    )
    return response.content[0].text

Ecco cosa restituisce (esempio reale anonimizzato, cliente B2B SaaS):

Output — example.com

1. EXECUTIVE SUMMARY
Il sito ha fondamenta tematiche solide ma soffre di problemi
tecnici significativi che limitano l'efficienza del crawl.
Il 47% delle pagine ha meta description duplicate o assenti,
12 pagine restituiscono soft 404, e il blog ha 23 pagine
sotto 300 parole. Fixare i problemi P0 dovrebbe sbloccare
miglioramenti di ranking entro 4-6 settimane.

2. PROBLEMI CRITICI

2.1 Title Tag Duplicati (14 pagine)
  • /features e /product → entrambe "Project Management Software"
  • /pricing e /pricing-enterprise → entrambe "Pricing Plans"
  • 9 pagine tag blog → tutte solo "Blog"
Perché: Cannibalizzazione keyword tra /features e /product.
Fix: Title unici e differenziati per ognuna.
Effort: Basso (2-3h) | Impatto: Alto

2.2 Soft 404 (12 pagine)
  • /integrations/slack-legacy  (34 parole)
  • /integrations/hipchat        (28 parole)
  • /blog/author/jsmith          (0 parole)
Fix: 301 redirect verso equivalenti attivi o 410.
Effort: Basso (1-2h) | Impatto: Medio

6. ACTION PLAN
 #  | Azione                            | Ore  | Impatto | Priorità
----|-----------------------------------|------|---------|----------
 1  | Fix title tag duplicati           |  3h  | Alto    | P0
 2  | Risolvere soft 404                |  2h  | Medio   | P0
 3  | Abilitare canonical sitewide      | 1.5h | Medio   | P0
 4  | Meta description (top 10 pag.)    | 2.5h | Alto    | P1
 5  | Espandere comparison pages (3)    | 12h  | Alto    | P1
...
P0 totale: ~6.5 ore. Inizia da qui.

URL specifici. Numeri specifici. Prioritizzato per impatto. Questo audit da solo richiederebbe una giornata intera manualmente — qui si genera in 5 minuti. Ma siamo solo all'inizio.

Step 3: Layerare i dati di Search Console

Il crawl intercetta i problemi tecnici. Per il layer di performance — quali keyword stanno vincendo, quali stanno sanguinando, dove si nascondono le vere opportunità — servono i dati di Google Search Console.

Il principio chiave: il calcolo lo fa Python, l'interpretazione la fa Claude. Questo pattern lo uso dappertutto.

gsc_analysis.py

def gsc_analysis(queries_csv, pages_csv, domain):
    client = anthropic.Anthropic()
    queries_df = pd.read_csv(queries_csv)

    # Python fa i calcoli numerici
    expected_ctr = {1:28, 2:15, 3:11, 4:8, 5:7, 6:5, 7:4, 8:3, 9:2.5, 10:2}
    queries_df['expected_ctr'] = queries_df['position'].round().map(expected_ctr)
    queries_df['ctr_gap'] = queries_df['ctr'] - queries_df['expected_ctr']

    # Quick wins: posizione 5-15, alto volume impressioni
    quick_wins = queries_df[
        (queries_df['position'] >= 5) &
        (queries_df['position'] <= 15) &
        (queries_df['impressions'] > 500)
    ].sort_values('impressions', ascending=False)

    # Claude interpreta
    prompt = f"""Sei un analista SEO senior. Dominio: {domain}
...
Consegna:
1. Overview performance con distribuzione posizioni
2. Anomalie CTR (opportunità di ottimizzazione title/meta)
3. Quick wins con stima traffico guadagnato se spostati top 3
4. Report cannibalizzazione: pagina primaria vs pagine da consolidare
5. Top 10 azioni prioritizzate con stima impatto traffico"""

    response = client.messages.create(
        model="claude-sonnet-4-6", max_tokens=8000,
        messages=[{"role": "user", "content": prompt}])
    return response.content[0].text

Step 4: Gap analysis competitiva

Ora sappiamo cosa manca tecnicamente e quali keyword perdono posizioni. La domanda successiva è sempre: cosa coprono i competitor che noi non copriamo?

Qui uso DataForSEO live. Parso la sitemap del cliente per mappare la copertura esistente, auto-scopro i competitor più vicini, poi eseguo una domain intersection per trovare ogni keyword dove un competitor ranka e il cliente no.

gap_analysis.py

def find_content_gaps(domain, competitors=None, market="Italy", language="it"):
    dataforseo_auth = ("login", "password")
    api = "https://api.dataforseo.com/v3"

    # Gap analysis: cosa hanno i competitor che noi non abbiamo?
    gaps = {}
    for comp in competitors:
        resp = requests.post(f"{api}/dataforseo_labs/google/domain_intersection/live",
            auth=dataforseo_auth,
            json=[{"target1": comp, "target2": domain,
                   "location_name": market, "language_code": language,
                   "intersections": False,  # ← solo keyword che NON abbiamo
                   "limit": 100,
                   "filters": [["keyword_data.keyword_info.search_volume", ">", 100]]}])
        gaps[comp] = resp.json()

    # Claude clusterizza, assegna score e prioritizza
    prompt = f"""Sei un content strategist. Sito: {domain}
...
Consegna:
1. Mappa copertura attuale: punti di forza e aree thin
2. Lista gap per competitor, raggruppati per topic
3. Clusterizza in PILLAR → Cluster → Supporting keywords
4. Score priorità:
   (Volume×0.25) + ((100-KD)×0.25) + (Gap×0.3) + (Rilevanza×0.2)
5. Top 10 contenuti da creare:
   Keyword | Formato | Parole | Competitor da studiare | Priorità"""

    response = client.messages.create(
        model="claude-sonnet-4-6", max_tokens=6000,
        messages=[{"role": "user", "content": prompt}])
    return response.content[0].text

Il parametro intersections: False è la chiave: dice a DataForSEO "mostrami solo le keyword dove il competitor A ranka e il mio cliente no". Claude clusterizza questi gap in pillar tematici, li scoreizza per volume, difficoltà e rilevanza business, e mi dice esattamente cosa creare prima.

Step 5: Keyword research ed espansione

La gap analysis mi dice dove sono le opportunità. Ora devo andare più in profondità — volume, difficoltà, intent, termini correlati per costruire un brief completo.

keyword_research.py

def keyword_research(seed_keywords, market="Italy", language="it"):
    # Tre chiamate DataForSEO: overview, suggestions, related
    seed_data = requests.post(f"{api}/keyword_overview/live", ...).json()
    suggestions = requests.post(f"{api}/keyword_suggestions/live", ...).json()
    related = requests.post(f"{api}/related_keywords/live", ...).json()

    # Claude clusterizza e prioritizza
    prompt = """Clusterizza tutte le keyword per topic e intent.
Score: (Volume×0.4) + ((100-KD)×0.3) + (Intent_Match×0.3)
Categorizza:
  🟢 Quick Wins: alto volume + KD sotto 40
  🟡 Strategiche: alto volume + alto KD (lungo periodo)
  🔵 Long Tail: basso volume + basso KD (contenuto di supporto)
  ⚪ Skip: basso volume + alto KD
Tabella: Keyword | Volume | KD | CPC | Intent | Categoria | Cluster"""

Tre chiamate API a DataForSEO. Una chiamata a Claude. Mappa keyword completa con cluster, priorità e raccomandazioni contenuto.

Step 6: Il piano editoriale

Keyword ricercate. Gap identificati. Priorità assegnate. Adesso trasformo tutto in un calendario editoriale che il team può eseguire.

content_plan.py

def create_content_plan(keyword_data, gap_data, site_goals,
                        frequency="8/mese", period="trimestrale"):
    prompt = f"""Costruisci un calendario editoriale.

## PARAMETRI: {frequency} | {period}

Architettura: Pillar (2000-4000 parole) → Cluster (800-1500)
              → Supporting (500-800)
Mix contenuti: 50-60% informazionale | 20-30% commerciale | 10-20% transazionale
Score priorità: (Volume×0.3) + ((100-KD)×0.3) + (Business_Value×0.4)

Calendario mensile:
Settimana | Titolo | Keyword Target | Tipo | Parole | Priorità

Brief P1 per ogni contenuto prioritario:
- Keyword primaria + secondarie
- H1 e struttura H2
- URL competitor da analizzare
- Angolo unico / valore aggiunto
- Target link interni

Regola: Pillar prima dei cluster. 20% buffer per emergenze."""

Step 7: Scrivere il contenuto

Il calendario dice cosa pubblicare e quando. Per ogni pezzo, analizzo i competitor che rankano per quella keyword — struttura degli heading, cosa coprono, cosa mancano — e genero un articolo completo e ottimizzato.

write_article.py

def write_seo_article(keyword, market="Italy", language="it"):
    # SERP analysis: chi ranka e come
    serp = requests.post(f"{api}/serp/google/organic/live/advanced",
        json=[{"keyword": keyword, "location_name": market,
               "language_code": language, "depth": 10}]).json()

    # Struttura contenuti top 3 competitor
    top_urls = [item['url'] for item in serp['items'][:3]
                if item.get('type') == 'organic']
    structures = []
    for url in top_urls:
        resp = requests.post(f"{api}/on_page/content_parsing/live",
            json=[{"url": url}])
        structures.append({'url': url, 'data': resp.json()})

    prompt = f"""Processo:
1. Analizza struttura heading dei competitor
2. Identifica l'intent: cosa vuole ottenere chi cerca?
3. Trova il gap: cosa aggiungerebbe valore oltre ai risultati esistenti?
4. Costruisci l'outline: H1 → H2 → H3. Varianti keyword negli heading.
   Word count target: media competitor + 20%.
5. Scrivi l'articolo. Paragrafi corti. Consigli azionabili.
   Keyword placement naturale. Includi sezione FAQ.
   Segna i punti di link interno come [LINK INTERNO: topic].
6. Valida: densità keyword 1-2%. Title sotto 60 char.
   Meta description sotto 155 char. Tutti i topic competitor coperti.
7. Consegna: meta title + meta description + articolo completo
   in markdown + raccomandazioni link interni + suggerimenti schema."""

L'articolo torna pronto per la pubblicazione. Non perfetto — lo rivedo sempre e aggiungo il mio layer strategico — ma sono al 90% dal punto di partenza. L'analisi competitor, l'outline, la scrittura, la validazione SEO: tutto gestito.

La catena completa

Ecco come appare la pipeline completa quando colleghi ogni step:

pipeline_completa.py

client_info = {
    'website': 'https://cliente.com',
    'industry': 'B2B SaaS - Project Management',
    'goal': 'Aumentare demo organiche del 40% in Q2',
    'team_size': '1 SEO + 2 writer',
    'cms': 'WordPress con Yoast SEO'
}

# 1. CRAWL — dati tecnici
crawler = SEOCrawler(client_info['website'], max_pages=200)
crawler.crawl()
crawler.to_csv('crawl_data.csv')

# 2. AUDIT — problemi prioritizzati
audit = run_seo_audit('crawl_data.csv', client_info)

# 3. GSC — analisi performance
gsc = gsc_analysis('gsc_queries.csv', 'gsc_pages.csv', 'cliente.com')

# 4. GAP — cosa hanno i competitor che noi non abbiamo
gaps = find_content_gaps('cliente.com', market="Italy", language="it")

# 5. KEYWORD — espandi i gap in target
kw = keyword_research(['project management software'], market="Italy")

# 6. PIANO — calendario editoriale con brief
plan = create_content_plan(kw, gaps, client_info)

# 7. SCRIVI — primo articolo prioritario
article = write_seo_article('project management per piccoli team')

# Salva tutto
for name, content in [('audit', audit), ('gsc', gsc), ('gaps', gaps),
                        ('keywords', kw), ('plan', plan), ('article', article)]:
    with open(f'{name}_report.md', 'w') as f:
        f.write(content)

print("Pipeline completa.")

Dal crawl grezzo al piano contenuti e primo articolo. Quello che prima richiedeva una settimana ora gira in un pomeriggio.

Perché funziona

Il segreto non è nessuno step singolo. È come si compongono.

L'audit trova gap di contenuto. Il gap analyzer li quantifica con dati reali sui competitor. La keyword research li prioritizza. Il content planner li pianifica. Il writer li crea. Ogni step alimenta il successivo, e a ogni stadio Claude aggiunge il layer di ragionamento strategico che altrimenti richiederebbe ore di lavoro manuale.

Guarda l'output dell'audit. Non dice solo "hai title tag duplicati". Identifica il rischio di cannibalizzazione tra /features e /product, suggerisce title differenziati, e segnala che le pagine tag del blog sono un problema separato con una soluzione diversa. Questo tipo di comprensione contestuale — collegare un problema tecnico al suo impatto business — è quello che distingue un checklist da un audit.

Ed è quello che richiedeva ore.

Quello che questo non è

Non sto rimpiazzando l'expertise SEO. Sto eliminando le parti che non la richiedono.

Il pensiero strategico conta ancora. Capire il business del cliente, leggere tra le righe dei loro analytics, riconoscere quando i dati raccontano una storia fuorviante — quello è ancora lavoro umano, e deve restarlo.

Il principio guida è sempre lo stesso: calcola con Python, interpreta con Claude, verifica con il tuo cervello. Questa divisione del lavoro funziona. Ogni output è una bozza, non un deliverable finale. Rivedo, aggiusto, aggiungo contesto e applico giudizio. Ma parto al 90% invece che dallo 0%.

Da dove iniziare

Non hai bisogno della pipeline completa dal giorno uno. Ecco come costruirla gradualmente:

Settimana 1

Crawler + prompt audit

Da solo risparmia ore per cliente. Il punto di ingresso più veloce.

Settimana 2

Aggiungi l'analisi GSC

Pre-calcola CTR gap e cannibalizzazione in Python, fai interpretare a Claude.

Settimana 3

Integra DataForSEO

I dati live rendono le raccomandazioni di Claude molto più specifiche.

Settimana 4

Concatena tutto

Content planner + generazione articoli. La pipeline completa.

Vuoi implementare questa pipeline?

Se stai valutando come portare l'AI nel tuo workflow SEO — o vuoi che io costruisca e gestisca questa pipeline per il tuo progetto — parliamone.

Parliamo

Continua a leggere