Estrarre dati da TripAdvisor con Python (senza farsi bloccare)

Ultimo aggiornamento il April 17, 2026

La scorsa settimana ho provato a recuperare valutazioni degli hotel e numero di recensioni per circa 200 strutture in tre città europee da TripAdvisor. Il mio primo script — un semplice requests.get() con header predefiniti — ha restituito un elegante 403 Forbidden a ogni singola richiesta. Nemmeno un byte di dati utili.

TripAdvisor è una delle fonti di dati pubbliche più ricche del settore travel: oltre , più di 8 milioni di attività presenti e circa 460 milioni di visitatori unici al mese. Influenza oltre di spesa turistica annua. Ma ottenere quei dati in modo programmatico? Qui le cose si complicano. TripAdvisor usa DataDome per il rilevamento dei bot, Cloudflare WAF, fingerprinting TLS e challenge JavaScript: una difesa a più livelli che blocca la maggior parte dei tentativi di scraping ingenui ancora prima che partano. Questa guida è la risorsa che avrei voluto avere: un confronto diretto tra tre approcci Python per lo scraping (più un’opzione no-code), codice completo per ciascuno, una sezione strutturata per il troubleshooting anti-bot e pattern riutilizzabili che funzionano per hotel, ristoranti e attrazioni. Che tu sia un principiante di Python o uno sviluppatore esperto, ti farà risparmiare un sacco di 403 buttati via.

Non vuoi scrivere codice? Estrarre dati da TripAdvisor nel modo più semplice

Voglio essere chiaro su un punto. Molte persone che cercano "scrape TripAdvisor with Python" in realtà non sono davvero interessate a scrivere codice. Vogliono solo i dati — nomi degli hotel, valutazioni, numero di recensioni, prezzi — in un foglio di calcolo, e in fretta. Se ti riconosci in questa descrizione, c’è una strada molto più breve.

è un’estensione Chrome basata su AI che abbiamo creato e che può leggere qualsiasi pagina di TripAdvisor e suggerire automaticamente le colonne giuste da estrarre. Il flusso di lavoro richiede davvero due clic:

  1. Apri una pagina elenco di TripAdvisor (ad esempio i risultati di ricerca "Hotels in Paris").
  2. Fai clic su "AI Suggest Fields" nella barra laterale di Thunderbit. L’AI analizza la pagina e propone colonne come Nome hotel, Valutazione, Numero di recensioni, Prezzo e Posizione.
  3. Fai clic su "Scrape." Thunderbit estrae i dati da ogni risultato nella pagina — e gestisce automaticamente la paginazione se ti servono più risultati.
  4. Esporta in Excel, Google Sheets, Airtable o Notion. Le esportazioni sono gratuite in tutti i piani.

Thunderbit funziona con hotel, ristoranti e attrazioni senza alcuna modifica di configurazione — l’AI si adatta a ciò che trova sulla pagina. Per i risultati paginati, riconosce automaticamente i pulsanti "Next" e lo scroll infinito. E poiché gira dentro il tuo vero browser Chrome, eredita i cookie di sessione e il fingerprint del browser, il che gli dà un vantaggio naturale contro il rilevamento dei bot.

Puoi provarlo con la — il piano gratuito offre 6 pagine/mese, abbastanza per testare il flusso.

Se ti serve controllo programmatico, logica di parsing personalizzata o prevedi di estrarre 10.000+ pagine, allora Python è la strada giusta. Continua a leggere.

Perché estrarre dati da TripAdvisor con Python?

I dati di TripAdvisor hanno un impatto aziendale diretto e misurabile. Uno ha rilevato che un aumento di 1 punto nel Global Review Index di un hotel su 100 punti porta a un incremento dello 0,89% della tariffa media giornaliera e dell’1,42% del Revenue Per Available Room. Un altro mostra che un aumento esogeno di 1 stella nella valutazione TripAdvisor si traduce in 55.000–75.000 dollari di ricavi annui aggiuntivi per un hotel medio. Le recensioni non sono solo vanity metrics: sono un motore di fatturato.

Ecco come diversi team usano i dati di TripAdvisor:

Caso d’usoChi ne beneficiaDati necessari
Analisi competitiva hotelCatene alberghiere, revenue managerValutazioni, prezzi, volume recensioni, servizi
Ricerca di mercato ristorazioneGruppi di ristorazione, brand foodTipo di cucina, fasce di prezzo, sentiment delle recensioni
Monitoraggio trend attrazioniTour operator, enti del turismoRanking di popolarità, pattern stagionali
Analisi del sentimentRicercatori, data analystTesto completo delle recensioni, stelle, date
Generazione leadTeam sales, agenzie di viaggioNomi attività, contatti, posizioni

Perché proprio Python? Tre motivi. Primo: l’ecosistema. BeautifulSoup, Selenium, Playwright, Scrapy, httpx, pandas — Python ha librerie per scraping e analisi dati più mature di qualsiasi altro linguaggio. Secondo: il usa Python, quindi c’è più supporto della community, più risposte su StackOverflow e guide più aggiornate. Terzo: il vantaggio della pipeline. Puoi fare scraping con BeautifulSoup, pulire i dati con pandas, eseguire analisi del sentiment con Hugging Face Transformers e costruire dashboard — tutto nello stesso linguaggio. Nessun cambio di contesto.

Tre modi per estrarre dati da TripAdvisor con Python (a confronto)

Ogni guida concorrente sceglie un approccio e basta. Non è molto utile quando devi decidere prima di scrivere codice. Ecco la tabella comparativa che avrei voluto qualcuno mi mostrasse:

ApproccioVelocitàSupporto JSResistenza anti-botComplessitàIdeale per
requests + BeautifulSoup⚡ Veloce (~120–200 pagine/min grezzo)❌ Nessuno⚠️ BassaFacilePagine elenco statiche, progetti piccoli
Selenium / Browser headless🐢 Lento (~8–20 pagine/min)✅ Completo⚠️ MediaMediaContenuti dinamici, click su "Read more", banner cookie
JSON nascosto / API GraphQL⚡⚡ Il più veloce (~200–600 pagine/min grezzo)N/A✅ Più altaDifficileEstrazione su larga scala di recensioni/hotel
No-code (Thunderbit)⚡ Veloce✅ Integrato✅ IntegratoFacilissimoNon sviluppatori, export rapidi una tantum

Alcune avvertenze importanti. Quelle velocità grezze sono teoriche: i rate limit di TripAdvisor (~10–15 richieste al minuto per IP) limitano la velocità reale a circa 10 pagine/minuto per IP, indipendentemente dall’approccio. Il metodo JSON nascosto ti permette di ottenere più dati per richiesta, quindi meno richieste totali e minore esposizione ai limiti. Selenium è 5 volte più lento degli approcci basati su requests nei benchmark reali, ma è l’unica opzione quando devi cliccare pulsanti o renderizzare JavaScript.

Il resto di questa guida ti mostra tutti e tre i metodi Python con codice completo. Scegli quello più adatto alla tua situazione, oppure combinali (io uso spesso requests+BS4 per le pagine elenco e JSON nascosto per le pagine dettaglio).

Configurare l’ambiente Python

Prima di iniziare, sistemiamo l’ambiente. Ti serve Python 3.10+ (consiglio 3.12 o 3.13 — tutti i pacchetti principali li supportano senza problemi noti).

Installa tutto in un colpo solo:

1pip install requests beautifulsoup4 selenium httpx parsel pandas curl-cffi

Note sui pacchetti:

  • requests (2.33.1) — richieste HTTP, richiede Python 3.10+
  • beautifulsoup4 (4.14.3) — parsing HTML
  • selenium (4.43.0) — automazione browser, richiede Python 3.10+
  • httpx (0.28.1) — client HTTP asincrono
  • parsel (1.11.0) — selettori CSS/XPath (più leggero di BS4)
  • pandas (3.0.2) — esportazione dati, richiede Python 3.11+
  • curl-cffi (0.15.0) — impersonificazione del fingerprint TLS (fondamentale per aggirare Cloudflare)

ChromeDriver: se usi Selenium, buona notizia — da Selenium 4.6 in poi, Selenium Manager scarica e memorizza automaticamente il binario corretto di ChromeDriver. Nessuna installazione manuale necessaria. La corrispondenza delle versioni è gestita dinamicamente, quindi non devi preoccuparti dei mismatch con Chrome.

Ambiente virtuale (consigliato):

1python -m venv tripadvisor-scraper
2source tripadvisor-scraper/bin/activate  # macOS/Linux
3tripadvisor-scraper\Scripts\activate     # Windows

Approccio 1: estrarre dati da TripAdvisor con Requests e BeautifulSoup

Questo è l’approccio più semplice. Funziona bene per le pagine elenco (risultati hotel, liste di ristoranti) in cui i dati che ti servono sono presenti nell’HTML statico. Niente browser, niente rendering JavaScript, risorse minime.

Capire la struttura degli URL di TripAdvisor

Gli URL di TripAdvisor seguono pattern prevedibili in base alla categoria:

  • Hotel: https://www.tripadvisor.com/Hotels-g{locationId}-{Location_Name}-Hotels.html
  • Ristoranti: https://www.tripadvisor.com/Restaurants-g{locationId}-{Location_Name}.html
  • Attrazioni: https://www.tripadvisor.com/Attractions-g{locationId}-Activities-{Location_Name}.html

La paginazione usa il parametro oa (offset anchors), inserito nell’URL. Ogni pagina mostra 30 risultati:

  • Pagina 1: URL base (senza parametro oa)
  • Pagina 2: Hotels-g187768-oa30-Italy-Hotels.html
  • Pagina 3: Hotels-g187768-oa60-Italy-Hotels.html

Per le pagine recensioni, il parametro di offset è or con incrementi di 10:

  • Pagina 1: Reviews-or0-Hotel_Name.html
  • Pagina 2: Reviews-or10-Hotel_Name.html

Per ottenere le recensioni in tutte le lingue, aggiungi ?filterLang=ALL all’URL.

Inviare richieste con header realistici

TripAdvisor controlla gli header in modo aggressivo. Una richiesta con gli header Python di default viene bloccata subito. Devi imitare un vero browser Chrome:

1import requests
2import time
3import random
4session = requests.Session()
5headers = {
6    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
7    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
8    "Accept-Language": "en-US,en;q=0.9",
9    "Accept-Encoding": "gzip, deflate, br",
10    "Referer": "https://www.tripadvisor.com/",
11    "Sec-Fetch-Dest": "document",
12    "Sec-Fetch-Mode": "navigate",
13    "Sec-Fetch-Site": "none",
14    "Sec-CH-UA": '"Google Chrome";v="135", "Not.A.Brand";v="8", "Chromium";v="135"',
15    "Sec-CH-UA-Mobile": "?0",
16    "Sec-CH-UA-Platform": '"Windows"',
17}
18session.headers.update(headers)
19url = "https://www.tripadvisor.com/Hotels-g187147-Paris_Ile_de_France-Hotels.html"
20response = session.get(url)
21print(f"Status: {response.status_code}")
22print(f"Content length: {len(response.text)} characters")

Dettaglio importante: TripAdvisor verifica che User-Agent e i Client Hints Sec-CH-UA siano coerenti. Se dichiari di essere Chrome 135 nel User-Agent ma il tuo Sec-CH-UA indica Chrome 120, verrai segnalato. Ruota sempre set completi di header, non i singoli header.

Analizzare le liste con BeautifulSoup

Una volta ottenuta una risposta valida, estrai i dati con BeautifulSoup. TripAdvisor usa attributi data-automation e data-test-attribute più stabili dei nomi delle classi CSS, che cambiano spesso:

1from bs4 import BeautifulSoup
2soup = BeautifulSoup(response.text, "html.parser")
3# Trova tutte le card degli hotel
4cards = soup.select('div[data-test-attribute="location-results-card"]')
5hotels = []
6for card in cards:
7    # Nome hotel
8    title_el = card.select_one('div[data-automation="hotel-card-title"]')
9    name = title_el.get_text(strip=True) if title_el else None
10    # Link alla pagina dettaglio
11    link_el = card.select_one('div[data-automation="hotel-card-title"] a')
12    link = "https://www.tripadvisor.com" + link_el["href"] if link_el else None
13    # Valutazione
14    rating_el = card.select_one('[data-automation="bubbleRatingValue"]')
15    rating = rating_el.get_text(strip=True) if rating_el else None
16    # Numero di recensioni
17    review_el = card.select_one('[data-automation="bubbleReviewCount"]')
18    review_count = review_el.get_text(strip=True).replace(",", "").split()[0] if review_el else None
19    hotels.append({
20        "name": name,
21        "rating": rating,
22        "review_count": review_count,
23        "url": link,
24    })
25print(f"Trovati {len(hotels)} hotel in questa pagina")
26for h in hotels[:3]:
27    print(h)

Nota sui selettori: TripAdvisor usa nomi di classi CSS offuscati (come FGwzt, yyzcQ) che cambiano a ogni aggiornamento del sito. Gli attributi data-automation e data-test-target sono molto più stabili. Preferisci sempre gli attributi data rispetto alle classi.

Gestire la paginazione

Per estrarre più pagine, fai un loop sul parametro di offset con una pausa rispettosa tra le richieste:

1import pandas as pd
2all_hotels = []
3base_url = "https://www.tripadvisor.com/Hotels-g187147-oa{offset}-Paris_Ile_de_France-Hotels.html"
4for page in range(5):  # Prime 5 pagine
5    offset = page * 30
6    url = base_url.format(offset=offset) if page > 0 else "https://www.tripadvisor.com/Hotels-g187147-Paris_Ile_de_France-Hotels.html"
7    response = session.get(url)
8    if response.status_code != 200:
9        print(f"Pagina {page + 1}: status {response.status_code}, interrompo.")
10        break
11    soup = BeautifulSoup(response.text, "html.parser")
12    cards = soup.select('div[data-test-attribute="location-results-card"]')
13    for card in cards:
14        title_el = card.select_one('div[data-automation="hotel-card-title"]')
15        name = title_el.get_text(strip=True) if title_el else None
16        rating_el = card.select_one('[data-automation="bubbleRatingValue"]')
17        rating = rating_el.get_text(strip=True) if rating_el else None
18        review_el = card.select_one('[data-automation="bubbleReviewCount"]')
19        review_count = review_el.get_text(strip=True).replace(",", "").split()[0] if review_el else None
20        all_hotels.append({"name": name, "rating": rating, "review_count": review_count})
21    print(f"Pagina {page + 1}: trovati {len(cards)} hotel")
22    time.sleep(random.uniform(3, 7))  # Ritardo casuale per evitare rate limiting
23df = pd.DataFrame(all_hotels)
24print(f"\nTotale hotel estratti: {len(df)}")

Il time.sleep(random.uniform(3, 7)) è importante. La soglia di rate limit di TripAdvisor è circa 10–15 richieste al minuto per IP. Andare più veloce attiva CAPTCHA o errori 429.

Limiti di questo approccio

Dove si rompe? L’approccio requests+BS4 fallisce quando:

  • TripAdvisor serve contenuti renderizzati via JavaScript (alcune pagine risultati richiedono JS)
  • Il testo delle recensioni è troncato dietro il pulsante "Read more"
  • Le difese anti-bot si spingono fino a challenge JavaScript o CAPTCHA
  • Ti servono dati visibili solo dopo il rendering client-side (prezzi, disponibilità)

Per questi casi, ti serve Selenium (Approccio 2) oppure il metodo JSON nascosto (Approccio 3).

Approccio 2: estrarre dati da TripAdvisor con Selenium (browser headless)

Selenium avvia un browser reale, quindi può renderizzare JavaScript, cliccare pulsanti, gestire i banner di consenso cookie e interagire con contenuti dinamici. Il prezzo da pagare: è circa e consuma 300–500 MB di RAM per ogni istanza del browser.

Configurare Selenium con impostazioni anti-detection

Di default, Selenium è facilmente rilevabile. Il fingerprinting di TripAdvisor lo intercetta subito. Devi disabilitare i flag di automazione:

1from selenium import webdriver
2from selenium.webdriver.chrome.options import Options
3from selenium.webdriver.common.by import By
4from selenium.webdriver.support.ui import WebDriverWait
5from selenium.webdriver.support import expected_conditions as EC
6options = Options()
7options.add_argument("--headless=new")  # Usa il nuovo headless mode (Chrome 112+)
8options.add_argument("--disable-blink-features=AutomationControlled")
9options.add_argument("--window-size=1920,1080")
10options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36")
11options.add_experimental_option("excludeSwitches", ["enable-automation"])
12options.add_experimental_option("useAutomationExtension", False)
13driver = webdriver.Chrome(options=options)
14# Rimuovi la proprietà webdriver da navigator
15driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
16    "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
17})

Basta per TripAdvisor? Per scraping su piccola scala (meno di 50 pagine), questa configurazione con proxy residenziali di solito funziona. Per volumi maggiori, potresti aver bisogno di undetected-chromedriver o nodriver: la protezione DataDome di TripAdvisor analizza oltre 1.000 segnali per richiesta, inclusi i fingerprint TLS che il Selenium standard non riesce a imitare.

Estrarre i risultati hotel con Selenium

1import time
2import random
3url = "https://www.tripadvisor.com/Hotels-g187147-Paris_Ile_de_France-Hotels.html"
4driver.get(url)
5# Aspetta il caricamento delle card hotel
6wait = WebDriverWait(driver, 15)
7wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div[data-test-attribute="location-results-card"]')))
8# Gestisci il popup di consenso cookie (se appare)
9try:
10    cookie_btn = driver.find_element(By.ID, "onetrust-accept-btn-handler")
11    cookie_btn.click()
12    time.sleep(1)
13except:
14    pass  # Nessun popup cookie
15# Estrai i dati degli hotel
16cards = driver.find_elements(By.CSS_SELECTOR, 'div[data-test-attribute="location-results-card"]')
17hotels = []
18for card in cards:
19    try:
20        name = card.find_element(By.CSS_SELECTOR, 'div[data-automation="hotel-card-title"]').text
21    except:
22        name = None
23    try:
24        rating = card.find_element(By.CSS_SELECTOR, '[data-automation="bubbleRatingValue"]').text
25    except:
26        rating = None
27    try:
28        reviews = card.find_element(By.CSS_SELECTOR, '[data-automation="bubbleReviewCount"]').text
29    except:
30        reviews = None
31    hotels.append({"name": name, "rating": rating, "review_count": reviews})
32print(f"Estratti {len(hotels)} hotel")
33for h in hotels[:3]:
34    print(h)

Sul mio computer ci sono voluti circa 8 secondi per una singola pagina — contro meno di 1 secondo con requests+BS4. Questa differenza di 8x si accumula rapidamente quando devi estrarre centinaia di pagine.

Espandere "Read More" ed estrarre le recensioni complete

Le pagine recensioni troncano i testi lunghi dietro un pulsante "Read more". Selenium può cliccarlo:

1review_url = "https://www.tripadvisor.com/Hotel_Review-g187147-d188726-Reviews-Le_Marais_Hotel-Paris_Ile_de_France.html"
2driver.get(review_url)
3wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div[data-reviewid]')))
4time.sleep(2)
5# Clicca tutti i pulsanti "Read more"
6read_more_buttons = driver.find_elements(By.XPATH, '//button//*[contains(text(), "Read more")]/..')
7for btn in read_more_buttons:
8    try:
9        driver.execute_script("arguments[0].click();", btn)
10        time.sleep(0.3)
11    except:
12        pass
13# Estrai le recensioni
14review_elements = driver.find_elements(By.CSS_SELECTOR, 'div[data-reviewid]')
15reviews = []
16for rev in review_elements:
17    try:
18        title = rev.find_element(By.CSS_SELECTOR, 'div[data-test-target="review-title"]').text
19    except:
20        title = None
21    try:
22        body = rev.find_element(By.CSS_SELECTOR, 'q.IRsGHoPm span').text
23    except:
24        try:
25            body = rev.find_element(By.CSS_SELECTOR, 'p.partial_entry').text
26        except:
27            body = None
28    try:
29        rating_class = rev.find_element(By.CSS_SELECTOR, 'div[data-test-target="review-rating"] span').get_attribute("class")
30        # La valutazione è codificata nella classe come "ui_bubble_rating bubble_50" = 5.0
31        rating_num = [c for c in rating_class.split() if "bubble_" in c][0].replace("bubble_", "")
32        rating = int(rating_num) / 10
33    except:
34        rating = None
35    reviews.append({"title": title, "body": body, "rating": rating})
36print(f"Estratte {len(reviews)} recensioni")

Aggiungere la rotazione dei proxy a Selenium

Per uno scraping continuo, serve la rotazione dei proxy. Dato che selenium-wire è deprecato da gennaio 2024, usa il supporto proxy integrato di Chrome:

1# Con proxy senza autenticazione
2proxy = "http://your-proxy-address:port"
3options.add_argument(f"--proxy-server={proxy}")
4# Per proxy con autenticazione, usa una estensione Chrome o il protocollo BiDi di Selenium 4

Per ruotare i proxy in modo programmatico, crea una nuova istanza del driver con un proxy diverso per ogni batch di richieste. Non è elegante, ma è affidabile.

Approccio 3: metodo JSON nascosto (senza parsare l’HTML)

Molte guide saltano del tutto questo approccio, ed è un peccato: è il più veloce e pulito dei tre. TripAdvisor incorpora dati strutturati in JSON direttamente nelle pagine HTML — dentro tag <script> come variabili JavaScript tipo pageManifest e urqlCache. Estrarre questo JSON ti dà dati più puliti (valutazioni come numeri, date in formato ISO), con meno richieste e senza bisogno di rendering JavaScript.

Trovare il JSON incorporato nel sorgente pagina

L’idea chiave: puoi usare un semplice requests.get() per scaricare la pagina, poi estrarre il JSON dall’HTML grezzo senza mai renderizzare JavaScript.

1import requests
2import re
3import json
4headers = {
5    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
6    "Accept-Language": "en-US,en;q=0.9",
7    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
8    "Referer": "https://www.tripadvisor.com/",
9    "Sec-CH-UA": '"Google Chrome";v="135", "Not.A.Brand";v="8", "Chromium";v="135"',
10    "Sec-CH-UA-Mobile": "?0",
11    "Sec-CH-UA-Platform": '"macOS"',
12}
13url = "https://www.tripadvisor.com/Hotel_Review-g188590-d194317-Reviews-NH_City_Centre_Amsterdam.html"
14response = requests.get(url, headers=headers)
15# Estrai il blocco JSON pageManifest
16match = re.search(r"pageManifest:({.+?})};", response.text)
17if match:
18    page_data = json.loads(match.group(1))
19    print("Trovati dati pageManifest")
20    print(f"Chiavi: {list(page_data.keys())[:10]}")

Come trovare da solo il nome della variabile: apri una pagina hotel di TripAdvisor in Chrome, clic destro → Visualizza sorgente pagina, poi Ctrl+F per pageManifest o urqlCache o aggregateRating. I dati sono lì, pronti per essere estratti.

Parsare il JSON ed estrarre dati strutturati

TripAdvisor incorpora anche dati schema.org application/ld+json che sono ancora più facili da estrarre:

1from parsel import Selector
2sel = Selector(text=response.text)
3# Estrai i dati strutturati JSON-LD
4json_ld_scripts = sel.xpath("//script[@type='application/ld+json']/text()").getall()
5for script in json_ld_scripts:
6    data = json.loads(script)
7    if isinstance(data, dict) and data.get("@type") in ["Hotel", "Restaurant", "TouristAttraction"]:
8        print(f"Nome: {data.get('name')}")
9        print(f"Valutazione: {data.get('aggregateRating', {}).get('ratingValue')}")
10        print(f"Numero recensioni: {data.get('aggregateRating', {}).get('reviewCount')}")
11        print(f"Fascia prezzo: {data.get('priceRange')}")
12        print(f"Indirizzo: {data.get('address', {}).get('streetAddress')}")
13        print(f"Coordinate: {data.get('geo', {}).get('latitude')}, {data.get('geo', {}).get('longitude')}")
14        break

I dati JSON-LD sono incorporati nell’HTML statico e NON richiedono rendering JavaScript. Ti restituiscono nome della struttura, rating aggregato, numero di recensioni, indirizzo, coordinate, fascia di prezzo e URL delle foto — senza dover parsare nemmeno un tag HTML.

Per dati più ricchi (recensioni individuali, breakdown delle valutazioni, liste dei servizi), serve l’oggetto urqlCache:

1# Estrai urqlCache per i dettagli delle recensioni
2cache_match = re.search(r'"urqlCache"\s*:\s*({.+?})\s*,\s*"redux"', response.text)
3if cache_match:
4    cache_data = json.loads(cache_match.group(1))
5    # Naviga nella cache per trovare i dati delle recensioni
6    for key, value in cache_data.items():
7        if "reviews" in str(value).lower()[:100]:
8            reviews_data = json.loads(value.get("data", "{}")) if isinstance(value, dict) else None
9            if reviews_data:
10                print(f"Trovata voce di cache recensioni: {key[:50]}...")
11                break

I path JSON esatti cambiano di tanto in tanto quando TripAdvisor aggiorna il frontend, ma la struttura generale — JSON-LD per i dati di sintesi, urqlCache per i dati dettagliati — è stabile da anni.

Reverse engineering dell’API GraphQL di TripAdvisor (avanzato)

Per estrazioni su larga scala, gli endpoint GraphQL di TripAdvisor restituiscono i dati già strutturati. È il metodo più veloce, ma richiede anche più manutenzione.

1import httpx
2import random
3import string
4def generate_request_id():
5    """Genera il valore dell'header X-Requested-By"""
6    random_chars = ''.join(random.choices(string.ascii_letters + string.digits, k=180))
7    return f"TNI1625!{random_chars}"
8# Cerca hotel a Parigi
9search_payload = [{
10    "variables": {
11        "request": {
12            "query": "hotels in Paris",
13            "limit": 10,
14            "scope": "WORLDWIDE",
15            "locale": "en-US",
16            "scopeGeoId": 1,
17            "searchCenter": None,
18            "types": ["LOCATION", "QUERY_SUGGESTION", "RESCUE_RESULT"],
19            "locationTypes": ["GEO", "AIRPORT", "ACCOMMODATION", "ATTRACTION", "EATERY", "NEIGHBORHOOD"]
20        }
21    },
22    "extensions": {
23        "preRegisteredQueryId": "84b17ed122fbdbd4"
24    }
25}]
26graphql_headers = {
27    "Content-Type": "application/json",
28    "Accept": "*/*",
29    "Accept-Language": "en-US,en;q=0.9",
30    "Origin": "https://www.tripadvisor.com",
31    "Referer": "https://www.tripadvisor.com/Hotels",
32    "X-Requested-By": generate_request_id(),
33    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
34}
35with httpx.Client() as client:
36    response = client.post(
37        "https://www.tripadvisor.com/data/graphql/ids",
38        json=search_payload,
39        headers=graphql_headers
40    )
41    if response.status_code == 200:
42        results = response.json()
43        print(json.dumps(results, indent=2)[:1000])
44    else:
45        print(f"Richiesta GraphQL fallita: {response.status_code}")

Per recuperare recensioni via GraphQL:

1review_payload = [{
2    "variables": {
3        "locationId": 194317,  # NH City Centre Amsterdam
4        "offset": 0,
5        "limit": 20,
6        "filters": {},
7        "sortType": None,
8        "sortBy": "date",
9        "language": "en",
10        "doMachineTranslation": False,
11        "photosPerReviewLimit": 3
12    },
13    "extensions": {
14        "preRegisteredQueryId": "ef1a9f94012220d3"
15    }
16}]
17with httpx.Client() as client:
18    response = client.post(
19        "https://www.tripadvisor.com/data/graphql/ids",
20        json=review_payload,
21        headers=graphql_headers
22    )
23    if response.status_code == 200:
24        data = response.json()
25        reviews = data[0]["data"]["locations"][0]["reviewListPage"]["reviews"]
26        total = data[0]["data"]["locations"][0]["reviewListPage"]["totalCount"]
27        print(f"Totale recensioni: {total}")
28        for r in reviews[:3]:
29            print(f"  [{r['rating']}/5] {r['title']} - {r['createdDate']}")

Avvertenza importante: i valori preRegisteredQueryId (come 84b17ed122fbdbd4 per la ricerca e ef1a9f94012220d3 per le recensioni) possono rompersi quando TripAdvisor effettua nuovi deploy. Quando succede, le richieste falliscono senza messaggi evidenti. Dovrai riscoprire gli ID delle query monitorando le richieste di rete negli strumenti DevTools del browser.

Perché questo metodo riduce il bisogno di proxy

La matematica è semplice. Con requests+BS4, estrarre 100 pagine dettaglio hotel richiede 100 richieste. Con il metodo JSON nascosto, ogni richiesta restituisce tutti i dati necessari da un solo caricamento pagina — senza richieste aggiuntive per espandere recensioni o caricare contenuti dinamici. Con GraphQL, una singola chiamata API può restituire 20 recensioni alla volta. Meno richieste = meno esposizione ai rate limit = meno necessità di ruotare i proxy. Per progetti piccoli o medi (meno di 1.000 pagine), potresti anche non aver bisogno di proxy se aggiungi ritardi ragionevoli.

Estrarre hotel, ristoranti e attrazioni con un solo script riutilizzabile

Quattro guide su cinque coprono solo gli hotel. Ma TripAdvisor ha tre categorie principali di contenuto, e i pattern degli URL e i campi dati cambiano tra una e l’altra. Ecco come costruire una funzione che le gestisce tutte.

Campi disponibili per categoria

CampoHotelRistorantiAttrazioni
Nome
Valutazione
Numero recensioni
Prezzo/Fascia di prezzoA volte
Indirizzo
Tipo di cucina
Durata/Tipo tour
Servizi
Coordinate

Costruire una funzione scrape_tripadvisor() riutilizzabile

1import requests
2from bs4 import BeautifulSoup
3import pandas as pd
4import time
5import random
6import re
7import json
8def scrape_tripadvisor(category, location_id, location_name, num_pages=3):
9    """
10    Estrae dati da TripAdvisor per hotel, ristoranti o attrazioni.
11    Args:
12        category: "hotels", "restaurants", o "attractions"
13        location_id: geo ID di TripAdvisor (es. "187147" per Parigi)
14        location_name: nome compatibile con l'URL (es. "Paris_Ile_de_France")
15        num_pages: numero di pagine da estrarre
16    """
17    url_patterns = {
18        "hotels": "https://www.tripadvisor.com/Hotels-g{geo}-oa{offset}-{name}-Hotels.html",
19        "restaurants": "https://www.tripadvisor.com/Restaurants-g{geo}-oa{offset}-{name}.html",
20        "attractions": "https://www.tripadvisor.com/Attractions-g{geo}-oa{offset}-Activities-{name}.html",
21    }
22    first_page_patterns = {
23        "hotels": "https://www.tripadvisor.com/Hotels-g{geo}-{name}-Hotels.html",
24        "restaurants": "https://www.tripadvisor.com/Restaurants-g{geo}-{name}.html",
25        "attractions": "https://www.tripadvisor.com/Attractions-g{geo}-Activities-{name}.html",
26    }
27    headers = {
28        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
29        "Accept-Language": "en-US,en;q=0.9",
30        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
31        "Referer": "https://www.tripadvisor.com/",
32        "Sec-CH-UA": '"Google Chrome";v="135", "Not.A.Brand";v="8", "Chromium";v="135"',
33        "Sec-CH-UA-Mobile": "?0",
34        "Sec-CH-UA-Platform": '"Windows"',
35    }
36    session = requests.Session()
37    session.headers.update(headers)
38    all_items = []
39    for page in range(num_pages):
40        offset = page * 30
41        if page == 0:
42            url = first_page_patterns[category].format(geo=location_id, name=location_name)
43        else:
44            url = url_patterns[category].format(geo=location_id, offset=offset, name=location_name)
45        response = session.get(url)
46        if response.status_code != 200:
47            print(f"  Pagina {page + 1}: status {response.status_code}, interrompo.")
48            break
49        soup = BeautifulSoup(response.text, "html.parser")
50        cards = soup.select('div[data-test-attribute="location-results-card"]')
51        for card in cards:
52            item = {"category": category}
53            title_el = card.select_one('div[data-automation="hotel-card-title"]') or card.select_one('a[data-automation]')
54            item["name"] = title_el.get_text(strip=True) if title_el else None
55            rating_el = card.select_one('[data-automation="bubbleRatingValue"]')
56            item["rating"] = rating_el.get_text(strip=True) if rating_el else None
57            review_el = card.select_one('[data-automation="bubbleReviewCount"]')
58            item["review_count"] = review_el.get_text(strip=True) if review_el else None
59            all_items.append(item)
60        print(f"  Pagina {page + 1}: trovati {len(cards)} elementi")
61        time.sleep(random.uniform(3, 7))
62    return pd.DataFrame(all_items)
63# Esempi di utilizzo
64print("=== Hotel a Parigi ===")
65hotels_df = scrape_tripadvisor("hotels", "187147", "Paris_Ile_de_France", num_pages=2)
66print(hotels_df.head())
67print("\n=== Ristoranti a Roma ===")
68restaurants_df = scrape_tripadvisor("restaurants", "187791", "Rome_Lazio", num_pages=2)
69print(restaurants_df.head())
70print("\n=== Attrazioni a Barcellona ===")
71attractions_df = scrape_tripadvisor("attractions", "187497", "Barcelona_Catalonia", num_pages=2)
72print(attractions_df.head())

Una sola funzione, tre categorie, zero duplicazione di codice. Se TripAdvisor cambia un selettore, lo correggi in un punto solo.

Cosa fare quando TripAdvisor ti blocca (troubleshooting anti-bot)

Questa è la sezione che mi sarebbe servita di più quando ho iniziato a fare scraping su TripAdvisor, ed è anche la sezione che nessuna guida concorrente organizza bene. TripAdvisor usa DataDome (che analizza al giorno) insieme a Cloudflare WAF. Ecco una tabella diagnostica per i guasti più comuni:

SintomoCausa probabileSoluzione
Risposta HTTP 403Header mancanti o sospetti; challenge JavaScript di CloudflareImposta header realistici User-Agent, Accept-Language, Referer e Sec-CH-UA. Assicurati che siano coerenti.
Pagina CAPTCHA al posto dei datiRate limiting o fingerprinting del browserRuota proxy residenziali, aggiungi ritardi casuali (2–7 secondi tra le richieste)
HTML vuoto o body della pagina biancoJavaScript non renderizzato da requestsPassa a Selenium oppure estrai il JSON nascosto dal sorgente pagina
Recensioni parziali / "Read more" non si espandeContenuto caricato solo su clickUsa .click() con Selenium oppure estrai dal blob JSON incorporato
Recensioni solo in una linguaParametro lingua mancanteAggiungi ?filterLang=ALL all’URL delle recensioni
I dati smettono di caricarsi dopo N pagineLimite basato sulla sessioneRuota le sessioni, cancella i cookie tra un batch e l’altro
HTTP 1020 Access DeniedIP/ASN bannato da CloudflarePassa da proxy datacenter a proxy residenziali
Loop di challenge (CAPTCHA infinito)Persistenza dei cookie rottaScalda le sessioni visitando prima la homepage; mantieni il cookie jar

Logica di retry con exponential backoff

Nessun articolo concorrente mostra davvero questo codice. Ecco una funzione di retry riutilizzabile:

1import time
2import random
3import requests
4def fetch_with_retry(session, url, max_retries=4, base_delay=2, max_delay=60):
5    """
6    Recupera un URL con exponential backoff e jitter.
7    Ruota il User-Agent a ogni retry.
8    """
9    user_agents = [
10        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
11        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
12        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
13        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
14    ]
15    for attempt in range(max_retries):
16        # Ruota il User-Agent al retry
17        if attempt &gt; 0:
18            session.headers["User-Agent"] = random.choice(user_agents)
19        try:
20            response = session.get(url, timeout=30)
21            if response.status_code == 200:
22                return response
23            if response.status_code == 429:
24                # Rispetta l'header Retry-After, se presente
25                retry_after = int(response.headers.get("Retry-After", base_delay * (2 ** attempt)))
26                print(f"  Rate limit raggiunto (429). Attendo {retry_after}s...")
27                time.sleep(retry_after)
28                continue
29            if response.status_code in (403, 503):
30                wait = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
31                print(f"  Ricevuto {response.status_code}. Retry {attempt + 1}/{max_retries} tra {wait:.1f}s...")
32                time.sleep(wait)
33                continue
34            # Altri codici errore — non ritento
35            print(f"  Status inatteso {response.status_code} per {url}")
36            return response
37        except requests.exceptions.Timeout:
38            wait = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
39            print(f"  Timeout. Retry {attempt + 1}/{max_retries} tra {wait:.1f}s...")
40            time.sleep(wait)
41    print(f"  Esauriti tutti i {max_retries} retry per {url}")
42    return None

Ruotare header, proxy e sessioni

Per uno scraping continuativo, mantieni un pool di set di header e ruotali insieme:

1import random
2HEADER_SETS = [
3    {
4        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
5        "Sec-CH-UA": '"Google Chrome";v="135", "Not.A.Brand";v="8", "Chromium";v="135"',
6        "Sec-CH-UA-Platform": '"Windows"',
7    },
8    {
9        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
10        "Sec-CH-UA": '"Google Chrome";v="135", "Not.A.Brand";v="8", "Chromium";v="135"',
11        "Sec-CH-UA-Platform": '"macOS"',
12    },
13    {
14        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
15        "Sec-CH-UA": '"Google Chrome";v="134", "Not.A.Brand";v="8", "Chromium";v="134"',
16        "Sec-CH-UA-Platform": '"Windows"',
17    },
18]
19PROXY_LIST = [
20    "http://user:pass@residential-proxy-1:port",
21    "http://user:pass@residential-proxy-2:port",
22    # Aggiungi altri proxy residenziali
23]
24def get_rotated_session():
25    """Crea una nuova sessione con header e proxy ruotati."""
26    session = requests.Session()
27    # Scegli un set di header casuale
28    header_set = random.choice(HEADER_SETS)
29    base_headers = {
30        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
31        "Accept-Language": "en-US,en;q=0.9",
32        "Accept-Encoding": "gzip, deflate, br",
33        "Referer": "https://www.tripadvisor.com/",
34        "Sec-Fetch-Dest": "document",
35        "Sec-Fetch-Mode": "navigate",
36        "Sec-CH-UA-Mobile": "?0",
37    }
38    base_headers.update(header_set)
39    session.headers.update(base_headers)
40    # Scegli un proxy casuale
41    if PROXY_LIST:
42        proxy = random.choice(PROXY_LIST)
43        session.proxies = {"http": proxy, "https": proxy}
44    return session

Il tipo di proxy conta. I proxy datacenter vengono bloccati quasi subito da TripAdvisor (HTTP 1020 Access Denied). I proxy residenziali sono obbligatori per uno scraping continuativo: passano attraverso ISP consumer e sono indistinguibili dagli utenti reali. Aspettati di pagare $2,50–$8,40/GB a seconda del provider.

Esportare e salvare i dati estratti da TripAdvisor

Una volta ottenuti i dati, portarli in un formato utilizzabile è semplice.

Export CSV (il più comune)

1import pandas as pd
2df = pd.DataFrame(all_hotels)
3df.to_csv("tripadvisor_hotels_paris.csv", index=False, encoding="utf-8-sig")
4print(f"Esportate {len(df)} righe in CSV")

L’encoding='utf-8-sig' è importante: fa sì che Excel visualizzi correttamente i caratteri non latini (accenti francesi, caratteri cinesi, ecc.) quando apri il CSV.

Export JSON (per dati annidati)

Quando hai recensioni annidate sotto gli hotel, JSON preserva la gerarchia:

1# Struttura gerarchica
2hotel_data = {
3    "property_id": "d194317",
4    "name": "NH City Centre Amsterdam",
5    "rating": 4.0,
6    "reviews": [
7        {"title": "Ottima posizione", "rating": 5, "date": "2025-03-15", "text": "..."},
8        {"title": "Soggiorno nella media", "rating": 3, "date": "2025-03-10", "text": "..."},
9    ]
10}
11# Per analisi piatte, usa json_normalize
12flat_reviews = pd.json_normalize(
13    hotel_data,
14    record_path="reviews",
15    meta=["property_id", "name"]
16)
17flat_reviews.to_csv("reviews_flat.csv", index=False)

Approccio in due file per dati relazionali

Per dataset grandi, io uso due file CSV:

  • hotels.csv — una riga per struttura (flat)
  • reviews.csv — una riga per recensione, con property_id come chiave esterna

In questo modo è facile fare join in pandas, caricare i dati in un database o importarli in tool BI.

Se non vuoi gestire tutta questa logica di export, Thunderbit ti permette di in Excel, Google Sheets, Airtable o Notion — tutto gratis, senza codice. Utile quando devi condividere i risultati con colleghi non tecnici.

Consigli per uno scraping di TripAdvisor responsabile ed efficiente

Scraping responsabile in sei punti:

  • Controlla robots.txt: il robots.txt di TripAdvisor blocca completamente i bot di AI training (GPTBot, ClaudeBot, ecc.). I crawler standard trovano restrizioni selettive su alcuni percorsi. Verificalo su tripadvisor.com/robots.txt.
  • Aggiungi ritardi: 3–7 secondi tra le richieste è un intervallo ragionevole. Andare più veloce di 10–15 richieste al minuto per IP fa scattare il rate limiting.
  • Estrai solo dati pubblici. Non fare login per accedere a contenuti riservati.
  • Conserva i dati in modo sicuro e rispetta GDPR/CCPA se gestisci informazioni personali (nomi dei recensori, ecc.).
  • Valuta l’API ufficiale di TripAdvisor se ti servono dati su scala commerciale. Il offre accesso ai dettagli delle attività più fino a 5 recensioni e 5 foto per località: limitato, ma legale e stabile.
  • Tieni presente il contesto legale: la ha rafforzato nell’UE le restrizioni basate sui Termini di servizio contro lo scraping. I Termini di TripAdvisor vietano esplicitamente lo scraping. Procedi con responsabilità e a tuo rischio.

Conclusione

Questo è il quadro completo.

  • Requests + BeautifulSoup è il percorso più semplice. Funziona per le pagine elenco statiche, richiede poca configurazione ed è veloce. Parti da qui se estrai meno di 100 pagine e non ti servono contenuti renderizzati via JavaScript.
  • Selenium gestisce tutto ciò che requests non può: contenuti dinamici, pulsanti "Read more", banner cookie. È 5 volte più lento e richiede più risorse, ma è l’unica opzione quando devi interagire con la pagina.
  • JSON nascosto / GraphQL è l’approccio più pulito e veloce. Ti dà dati strutturati senza parsare HTML, riduce il numero di richieste (e quindi il bisogno di proxy) e restituisce dati già pronti per l’analisi. Richiede più reverse engineering iniziale e qualche manutenzione quando TripAdvisor cambia la struttura dei dati.

La funzione riutilizzabile scrape_tripadvisor() copre hotel, ristoranti e attrazioni. Non dovresti aver bisogno di un secondo tutorial.

E se a metà guida ti rendi conto che programmare non fa per te — oppure ti servono solo 50 hotel in un foglio di calcolo entro fine giornata — l’ può farlo in due clic, con rilevamento campi basato su AI, paginazione automatica ed export gratuito verso Excel o Google Sheets. Nessun Python richiesto.

Se vuoi andare più a fondo, trovi altri tutorial di scraping sul e sul nostro .

FAQ

1. È legale fare scraping di TripAdvisor?

I Termini di servizio di TripAdvisor vietano esplicitamente lo scraping. Tuttavia, in generale i tribunali hanno ritenuto che estrarre dati pubblicamente accessibili (non protetti da login) non violi il Computer Fraud and Abuse Act negli Stati Uniti. Detto questo, la sentenza UE Ryanair del 2025 ha rafforzato in Europa le restrizioni basate sui Termini di servizio. Estrai solo dati pubblici, rispetta robots.txt, non ripubblicare contenuti protetti da copyright e consulta un legale se usi i dati a fini commerciali.

2. Posso estrarre dati da TripAdvisor senza Python?

Sì. Strumenti no-code come possono estrarre dati da TripAdvisor direttamente dal browser con rilevamento campi basato su AI e paginazione automatica. Puoi anche usare estensioni browser, add-on per Google Sheets o API commerciali di scraping. Python offre il massimo controllo e flessibilità, ma non è l’unica opzione.

3. Come evito di farmi bloccare quando faccio scraping su TripAdvisor?

Le tattiche chiave: usa header realistici e coerenti (soprattutto User-Agent e Sec-CH-UA), ruota proxy residenziali (gli IP datacenter vengono bloccati subito), aggiungi ritardi casuali di 3–7 secondi tra le richieste, usa il metodo JSON nascosto per ridurre al minimo le richieste totali, implementa retry con exponential backoff e scalda le sessioni visitando la homepage prima di passare alle pagine profonde.

4. Quali dati posso estrarre da TripAdvisor?

Hotel, ristoranti e attrazioni — inclusi nomi, valutazioni, numero di recensioni, fasce di prezzo, indirizzi, coordinate, servizi (hotel), tipo di cucina (ristoranti), durata dei tour (attrazioni) e testo completo delle recensioni con singole valutazioni e date. Gli approcci JSON nascosto e GraphQL restituiscono i dati più ricchi per ogni richiesta.

5. Quante pagine posso estrarre da TripAdvisor al giorno?

Con un singolo IP e ritardi ragionevoli: circa 600–1.000 pagine al giorno. Con 20 proxy residenziali in rotazione: circa 200.000–300.000 pagine al giorno usando approcci basati su requests. Selenium è più lento: aspettati 8.000–12.000 pagine al giorno per proxy. L’approccio JSON nascosto/GraphQL ti fa ottenere più dati per richiesta, quindi potresti aver bisogno di molte meno pagine totali per raccogliere la stessa quantità di informazioni.

Scopri di più

Ke
Ke
CTO @ Thunderbit. Ke is the person everyone pings when data gets messy. He's spent his career turning tedious, repetitive work into quiet little automations that just run. If you've ever wished a spreadsheet could fill itself in, Ke has probably already built the thing that does it.
Indice

Prova Thunderbit

Estrai lead e altri dati in soli 2 clic. Potenziato dall’AI.

Scarica Thunderbit È gratis
Estrai dati con l’AI
Trasferisci facilmente i dati a Google Sheets, Airtable o Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week