Guida a Pytrends in Python: estrarre dati da Google Trends e gestire l’errore 429

Cerca:

Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
risolvere errore 429 too many requests python

Quando iniziamo a estrarre dati dal web per i nostri progetti, l’entusiasmo di vedere i primi grafici si scontra spesso con un ostacolo frustrante: i server si stancano di noi.

È esattamente quello che succede quando proviamo a interrogare Google Trends tramite script.

Un attimo prima stai analizzando tranquillamente l’andamento di una parola chiave e, l’attimo dopo, lo script si blocca sputando un errore “429 Too Many Requests”.

In questa guida metteremo le mani sul codice usando pytrends, la libreria Python più usata per dialogare con Google Trends.

Vedremo come scaricare e visualizzare i dati, ma soprattutto capiremo come scrivere un codice robusto.

Invece di far crollare il programma al primo errore, gli insegneremo ad aspettare pazientemente e riprovare, affrontando concetti essenziali come il rate-limiting e l’idempotenza.

 

Pubblicità

Esercizio

Titolo: Prime ricerche con pytrends

Testo:
Devi analizzare l’andamento delle ricerche per “intelligenza artificiale” in Italia negli ultimi 12 mesi. Scrivi uno script Python che:

  1. Si connetta a Google Trends tramite pytrends.

  2. Scarichi i dati giornalieri per la parola chiave specificata.

  3. Mostri le prime 5 righe del DataFrame ottenuto.

Soluzione commentata:

# Esercizio 1 
from pytrends.request import TrendReq
import pandas as pd
import time

print("Connessione a Google Trends...")
pytrends = TrendReq(hl='it-IT', tz=360)

kw_list = ["intelligenza artificiale"]
pytrends.build_payload(kw_list, timeframe='today 12-m', geo='IT')

print("Scaricamento dati (potrebbe richiedere qualche secondo)...")
time.sleep(3)  # pausa precauzionale

df = pytrends.interest_over_time()

print("\nPrime 5 righe dei dati:")
print(df.head())

print(f"\nFormato del dataset: {df.shape}")
print(f"Periodo coperto: dalla {df.index.min()} alla {df.index.max()}")

Output atteso (esempio):

Connessione a Google Trends...
Scaricamento dati (potrebbe richiedere qualche secondo)...

Prime 5 righe dei dati:
            intelligenza artificiale  isPartial
date                                           
2025-04-13                        94      False
2025-04-20                        81      False
2025-04-27                        80      False
2025-05-04                        92      False
2025-05-11                        83      False

Formato del dataset: (53, 2)
Periodo coperto: dalla 2025-04-13 00:00:00 alla 2026-04-12 00:00:00

Analisi dei risultati ottenuti

I dati che vedi mostrano l’interesse settimanale (non giornaliero) per “intelligenza artificiale” in Italia:

Cosa significano questi numeri?

  • 94 81, 80, 92, 83 = interesse relativo (scala 0-100)

    • 100 = massimo interesse nel periodo considerato

    • 83 significa che in quella settimana l’interesse era all’83% del picco

  • isPartial = False = dati definitivi (non provvisori)

⚠️ Perché hai dati settimanali invece che giornalieri?

Google Trends restituisce dati giornalieri solo per periodi ≤ 9 mesi.
Abbiamo richiesto 'today 12-m' (12 mesi), quindi automaticamente restituisce dati settimanali.

💡 Osservazione: Se vuoi dati giornalieri, usa timeframe='today 3-m' o 'today 6-m'.

Visualizziamo i dati

import matplotlib.pyplot as plt

# L'output che hai ottenuto
df = pd.DataFrame({
    'intelligenza artificiale': [83, 73, 72, 80, 79],
    'isPartial': [False, False, False, False, False]
}, index=pd.to_datetime(['2025-04-13', '2025-04-20', '2025-04-27', '2025-05-04', '2025-05-11']))

# Grafico
plt.figure(figsize=(10, 5))
plt.plot(df.index, df['intelligenza artificiale'], marker='o', linewidth=2, markersize=8)
plt.title('Interesse per "Intelligenza Artificiale" in Italia (aprile-maggio 2025)')
plt.xlabel('Settimana')
plt.ylabel('Interesse relativo (0-100)')
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

# Statistiche di base
print(f"Valore massimo: {df['intelligenza artificiale'].max()}")
print(f"Valore minimo: {df['intelligenza artificiale'].min()}")
print(f"Media nel periodo: {df['intelligenza artificiale'].mean():.1f}")
print(f"Trend: {'in aumento' if df['intelligenza artificiale'].iloc[-1] > df['intelligenza artificiale'].iloc[0] else 'in diminuzione'}")

Valore massimo: 83
Valore minimo: 72
Media nel periodo: 77.4
Trend: in diminuzione

Perché Google Trends cambia automaticamente la granularità dei dati (giornaliera vs settimanale vs mensile) in base al periodo selezionato?

Google Trends adotta questa strategia per tre motivi principali:

  1. Privacy: Dati troppo granulari su periodi lunghi potrebbero identificare singoli utenti

  2. Stabilità statistica: Su periodi lunghi, i dati giornalieri avrebbero troppa varianza casuale

  3. Performance: Ridurre la quantità di dati rende le API più veloci e scalabili

Regole di Google Trends:

  • ≤ 9 mesi → dati giornalieri

  • 9-24 mesi → dati settimanali

  • > 24 mesi → dati mensili

Forse potrebbe interessarti anche:  Test Chi-Quadrato per l'Indipendenza: Guida Pratica con 6 Esercizi Svolti

Tutto bellissimo finché funziona… ma se Google si arrabbia?

A questo punto abbiamo uno script che si connette, scarica i dati e ci disegna persino un bel grafico.

Perfetto, no? Sì, finché facciamo solo un paio di prove.

Ma cosa succede se chiediamo a Google troppi dati troppo in fretta, magari inserendo il nostro codice in un ciclo per analizzare decine di parole chiave diverse? Il server si accorge del traffico e ci chiude la porta in faccia, bloccando l’esecuzione e restituendo il temutissimo errore 429: Too Many Requests.

Invece di far crollare l’intero programma (costringendoci a riavviarlo a mano e perdere il lavoro fatto fino a quel momento), possiamo insegnare al nostro script a essere paziente e a “ripararsi da solo”.

Vediamo come gestire questa situazione in modo professionale.

Gestione dell’errore 429 con retry e attesa

Il codice che abbiamo usato

max_retries = 3
for attempt in range(max_retries):
    try:
        df = pytrends.interest_over_time()
        break  # se funziona, esce dal ciclo
    except Exception as e:
        if "429" in str(e) or "TooManyRequests" in str(e):
            wait_time = (attempt + 1) * 30  # aspetta 30s, poi 60s, poi 90s
            print(f"Troppe richieste. Attendo {wait_time} secondi...")
            time.sleep(wait_time)
        else:
            raise e  # se è un altro errore, lo rilancia

Analisi 

Impostazione del numero massimo di tentativi

max_retries = 3
  • Proviamo massimo 3 volte prima di arrenderci

  • Se dopo 3 tentativi fallisce ancora, l’errore verrà propagato

Ciclo for con contatore dei tentativi

for attempt in range(max_retries):  # attempt = 0, 1, 2
  • attempt = 0 → primo tentativo

  • attempt = 1 → secondo tentativo

  • attempt = 2 → terzo tentativo

Blocco try-except

try:
    df = pytrends.interest_over_time()
    break  # successo! esco dal ciclo
  • Provo a scaricare i dati

  • Se riesco (break), esco dal ciclo e continuo

 Gestione specifica dell’errore 429

except Exception as e:
    if "429" in str(e) or "TooManyRequests" in str(e):
  • Controllo se l’errore è proprio il 429

  • Converto l’errore in stringa e cerco “429” o “TooManyRequests”

Attesa progressiva (Linear Backoff) (strategia intelligente)

wait_time = (attempt + 1) * 30

Questa è la parte geniale:

Tentativo (attempt) Calcolo Tempo attesa
0 (primo fallimento) (0+1) × 30 = 30 30 secondi
1 (secondo fallimento) (1+1) × 30 = 60 60 secondi
2 (terzo fallimento) (2+1) × 30 = 90 90 secondi

💡 Perché attesa crescente?

  • Se Google è sovraccarico, attendere più a lungo aumenta le probabilità di successo

  • Evita di “infastidire” il server con troppi tentativi ravvicinati

 Attesa effettiva

print(f"Troppe richieste. Attendo {wait_time} secondi...")
time.sleep(wait_time)
  • time.sleep() blocca l’esecuzione per X secondi

  • Poi il ciclo riprova automaticamente

Rilancio di altri errori

else:
    raise e

Schema del flusso di esecuzione

Tentativo 1 → Prova a scaricare

Fallisce con 429?
↓ SÌ
Aspetta 30 secondi

Tentativo 2 → Riprova a scaricare

Fallisce con 429?
↓ SÌ
Aspetta 60 secondi

Tentativo 3 → Riprova a scaricare

Successo! → break ed esce

💡 Concetti teorici importanti

Rate Limiting

  • I server limitano il numero di richieste per IP in un intervallo di tempo

  • Protegge il server da overload e abusi

  • Google Trends permette circa 1 richiesta ogni 10-15 secondi

Backoff esponenziale

  • Strategia classica nelle API

  • Aumenta gradualmente il tempo di attesa

  • Riduce il carico sul server e migliora le chance di successo

Idempotenza

  • La richiesta interest_over_time() è idempotente (stesso risultato se ripetuta)

  • Perfetto per retry!

Forse potrebbe interessarti anche:  Esercizi sul P-Value nel Marketing: Una Guida Pratica con Python

 Idempotenza: concetto fondamentale per le API e i retry

 L’idempotenza è un concetto cruciale nell’informatica, specialmente quando lavoriamo con API e retry automatici.

Definizione

Idempotenza = un’operazione che può essere eseguita più volte producendo sempre lo stesso risultato della prima esecuzione.

In altre parole: fare un’operazione 1 volta o 100 volte porta allo stesso effetto finale.

 Esempio pratico con interest_over_time()

# Prima chiamata
df1 = pytrends.interest_over_time()

# Seconda chiamata (dopo 30 secondi)
df2 = pytrends.interest_over_time()

# Terza chiamata
df3 = pytrends.interest_over_time()

# Risultato: df1, df2, df3 sono IDENTICI (stessi dati, stessi valori)

Perché sono identici?
Perché stai solo LEGGENDO dati storici. Google Trends non cambia i dati del passato tra una richiesta e l’altra.

Confronto: operazioni idempotenti vs non idempotenti

Tipo Operazione 1ª esecuzione 2ª esecuzione È idempotente?
LETTURA interest_over_time() Legge dati Legge stessi dati ✅ SÌ
LETTURA SELECT * FROM users Legge utenti Legge stessi utenti ✅ SÌ
SCRITTURA UPDATE user SET saldo=100 Imposta saldo a 100 Imposta di nuovo a 100 ✅ SÌ
SCRITTURA DELETE FROM user WHERE id=5 Cancella user 5 User 5 già cancellato (nessun effetto) ✅ SÌ
SCRITTURA INSERT INTO log (msg) Aggiunge riga Aggiunge SECONDA riga ❌ NO
SCRITTURA saldo += 50 (incremento) Aumenta di 50 Aumenta di altri 50 ❌ NO

Esempio: transazione bancaria

Operazione NON idempotente (PERICOLOSA per retry)

def trasferisci_soldi(importo):
    # SOTTRAE 50€ dal conto (MODIFICA lo stato)
    conto.saldo -= importo  
    banca.invia_soldi(destinatario, importo)

Problema: Se la richiesta fallisce per timeout e riprovi automaticamente:

  • 1° tentativo: sottrae 50€ ✅ (ma la risposta non arriva mai per un problema di rete)

  • 2° tentativo: sottrae altri 50€ 💸

  • Risultato: Il cliente perde 100€ invece di 50€!

Operazione idempotente (SICURA per retry)

def imposta_saldo_a(importo, id_transazione):
    # IMPOSTA il saldo a un valore specifico
    if not transazione_gia_eseguita(id_transazione):
        conto.saldo = importo
        segna_transazione_come_eseguita(id_transazione)

Perché è sicuro?

  • La seconda volta che chiami imposta_saldo_a(1000), il saldo rimane 1000

  • Puoi ritentare 100 volte senza conseguenze negative

Tabella riassuntiva delle operazioni HTTP comuni

Metodo HTTP È idempotente? Esempio Perché?
GET ✅ SÌ GET /trends/ai Legge solo dati
PUT ✅ SÌ PUT /user/5 nome=Mario Sostituisce l’intera risorsa
DELETE ✅ SÌ DELETE /user/5 Cancella (la seconda volta non cambia nulla)
POST ❌ NO POST /orders Crea un NUOVO ordine ogni volta
PATCH ❌ NO PATCH /user/5 saldo+50 Aggiornamento parziale (es. incremento)

Perché le API bancarie spesso richiedono un idempotency_key (chiave di idempotenza) nelle richieste POST?

Perché i POST non sono idempotenti (creano nuove risorse).
Se un pagamento fallisce per timeout di rete e il client riprova, si rischierebbe di addebitare il cliente due volte.

La idempotency_key è un ID unico che il client invia. Il server memorizza le chiavi già processate: se riceve la stessa chiave due volte, restituisce lo stesso risultato senza eseguire l’operazione una seconda volta.

Esempio:

POST /pagamento
Idempotency-Key: abc-123
Corpo: {"importo": 50, "da": "Mario", "a": "Luigi"}

# Se la richiesta fallisce e viene reinviata con stessa chiave...
POST /pagamento 
Idempotency-Key: abc-123 # STESSA CHIAVE!
Corpo: {"importo": 50, "da": "Mario", "a": "Luigi"}

# Il server risponde con lo stesso risultato senza riaddebitare

Domanda di riflessione

Cosa succederebbe se non gestissimo l’errore 429?

Il programma si interromperebbe immediatamente con un’eccezione non gestita, mostrando:

TooManyRequestsError: The request failed: Google returned a response with code 429

L’utente dovrebbe riavviare manualmente lo script, perdendo tutto il lavoro precedente.
Con la gestione dei retry, il programma si “ripara da solo” in modo automatico!

Forse potrebbe interessarti anche:  Distribuzione Gamma: La Guida Pratica per Modellare Tempi di Attesa (con Esempi e Codice Python)

Riepilogo: cosa abbiamo imparato

  1. Try-except per catturare errori specifici

  2. Ciclo for con contatore per limitare i tentativi

  3. Attesa progressiva (backoff) per rispettare i limiti del server

  4. Break per uscire dal ciclo in caso di successo

  5. Rilancio condizionale degli errori non gestibili

Questa è una best practice universale per qualsiasi API esterna (Twitter, Reddit, Google, ecc.)!


Commento Applicativo: Perché questo esercizio è prezioso

 Dal punto di vista applicativo, questo esercizio non è un semplice tutorial su come fare un grafico a linee. È uno spaccato reale di Data Engineering applicata al Marketing e alla SEO. Le aziende usano script identici a questo per monitorare l’interesse verso i propri brand rispetto ai competitor, alimentando dashboard automatizzate che guidano le decisioni di business.

Le peculiarità che lo rendono interessante:

La granularità dinamica dei dati (Il tranello di Google): La particolarità più affascinante (e spesso frustrante per i data analyst) affrontata nell’articolo è il comportamento adattivo di Google Trends. Il fatto che passando da 9 a 12 mesi i dati cambino da giornalieri a settimanali costringe chi analizza i dati a prendere decisioni di design a monte: mi serve il quadro a lungo termine o il dettaglio quotidiano?

Passaggio da Script a “Sistema”: Lo script inizia come un compito scolastico (connetti, scarica, stampa) ma evolve rapidamente. L’implementazione del meccanismo di retry trasforma un banale script procedurale in un sistema resiliente. Insegna che nel mondo reale, scrivere codice funzionante è solo metà del lavoro; l’altra metà è scrivere codice che sappia gestire il fallimento dei sistemi esterni con cui comunica. L’incursione teorica sull’idempotenza fornisce infine la base teorica del perché possiamo permetterci di automatizzare questi ritentativi senza fare danni.

Percorso di studio: Web Scraping e Automazione

Dalla raccolta dati alle architetture scalabili: costruisci competenze reali e subito applicabili.

💡 Strategia: BeautifulSoup → Asyncio → Selenium = stack completo per qualsiasi scenario.

Pubblicità