Minulý týden jsem z TripAdvisoru zkoušel vytáhnout hodnocení hotelů a počet recenzí pro zhruba 200 ubytování ve třech evropských městech. Můj první skript — obyčejný requests.get() s výchozími hlavičkami — mi na každém jediném požadavku vrátil parádní chybu 403 Forbidden. Ani jeden byte užitečných dat.
TripAdvisor je jeden z nejbohatších veřejných zdrojů dat v cestovním ruchu: přes 1 miliardu recenzí, více než 8 milionů firemních profilů a přibližně 460 milionů unikátních návštěvníků měsíčně. Ovlivňuje víc než $60 miliard ročních výdajů za cestování. Ale dostat se k těmto datům programově? Tam začíná problém. TripAdvisor používá detekci botů DataDome, Cloudflare WAF, TLS fingerprinting a JavaScriptové challenge — vrstvenou obranu, která většinu naivních pokusů o scraping zastaví ještě dřív, než vůbec začnou. Tahle příručka je přesně ten zdroj, který jsem si přál mít: přímé srovnání tří Python přístupů ke scrapingu (plus jedna no-code varianta), kompletní kód ke každému z nich, strukturovanou sekci pro řešení blokací a znovupoužitelné vzory, které fungují pro hotely, restaurace i atrakce. Ať už jste v Pythonu začátečník, nebo zkušený vývojář, tohle vám ušetří spoustu zbytečných 403ek.
Nechcete psát kód? TripAdvisor můžete scrapovat snadno
Rád bych byl hned od začátku upřímný: hodně lidí, kteří hledají „scrape TripAdvisor with Python“, vlastně netouží po programování. Chtějí hlavně data — názvy hotelů, hodnocení, počet recenzí, ceny — a chtějí je rychle v tabulce. Pokud jste to vy, existuje mnohem kratší cesta.
Thunderbit je naše AI-powered Chrome rozšíření, které umí přečíst libovolnou stránku TripAdvisoru a automaticky navrhnout správné sloupce k extrakci. Workflow jsou opravdu jen dva kliky:
- Otevřete stránku se seznamem na TripAdvisoru (např. výsledky vyhledávání „Hotely v Paříži“).
- Klikněte na „AI Suggest Fields“ v postranním panelu Thunderbitu. AI projde stránku a navrhne sloupce jako Název hotelu, Hodnocení, Počet recenzí, Cena a Lokalita.
- Klikněte na „Scrape.“ Thunderbit vytáhne data ze všech záznamů na stránce — a pokud je potřeba víc výsledků, zvládne i stránkování automaticky.
- Exportujte do Excelu, Google Sheets, Airtable nebo Notion. Export je zdarma v každém plánu.
Thunderbit funguje napříč hotely, restauracemi i atrakcemi bez jakýchkoli změn konfigurace — AI se přizpůsobí tomu, co je na stránce. U stránkovaných výsledků automaticky rozpozná tlačítko „Další“ i nekonečné scrollování. A protože běží ve vašem skutečném Chrome prohlížeči, používá vaše session cookies a fingerprint prohlížeče, což mu dává přirozenou výhodu proti detekci botů.
Vyzkoušet to můžete přes Thunderbit Chrome Extension — bezplatná verze nabízí 6 stránek měsíčně, což stačí na otestování workflow.
Pokud potřebujete programové řízení, vlastní logiku parsování nebo plánujete scrapovat 10 000+ stránek, Python je správná volba. Pokračujte ve čtení.
Proč scrapovat TripAdvisor v Pythonu?
Data z TripAdvisoru mají přímý a měřitelný dopad na byznys. Studie Cornell University ukázala, že zvýšení Global Review Indexu hotelu o 1 bod ze 100 vede k nárůstu průměrné denní sazby o 0,89 % a k růstu Revenue Per Available Room o 1,42 %. Jiná studie na ScienceDirect zjistila, že exogenní zvýšení hodnocení na TripAdvisoru o 1 hvězdičku znamená pro průměrný hotel dodatečný roční příjem ve výši 55 000–75 000 dolarů. Recenze nejsou jen ego-metrika — jsou motorem tržeb.
Takhle různá týmy používají data z TripAdvisoru:
| Případ použití | Kdo z toho těží | Potřebná data |
|---|---|---|
| Analýza konkurence hotelů | Hotelové řetězce, revenue manažeři | Hodnocení, ceny, objem recenzí, vybavení |
| Průzkum restaurací na trhu | Restaurační skupiny, potravinářské značky | Typ kuchyně, cenové rozmezí, sentiment recenzí |
| Sledování trendů u atrakcí | Cestovní kanceláře, turistické organizace | Popularita, sezónní vzorce |
| Analýza sentimentu | Výzkumníci, datoví analytici | Celé znění recenzí, hvězdičky, data |
| Lead generation | Obchodní týmy, cestovní agentury | Názvy firem, kontaktní údaje, lokality |
Proč právě Python? Tři důvody. Zaprvé ekosystém: BeautifulSoup, Selenium, Playwright, Scrapy, httpx, pandas — Python má zralější scrapingové i analytické knihovny než kterýkoli jiný jazyk. Zadruhé 71,7 % vývojářů web scrapingu používá Python, takže máte víc podpory komunity, víc odpovědí na StackOverflow a víc aktuálních návodů. Zatřetí výhoda pipeline: můžete scrapovat s BeautifulSoup, čistit data v pandas, dělat sentiment analysis přes Hugging Face Transformers a stavět dashboardy — všechno v jednom jazyce. Žádné přepínání kontextu.
Tři způsoby, jak scrapovat TripAdvisor v Pythonu (vedle sebe)
Každý konkurenční návod si vybere jeden přístup a ten rozvíjí. To ale moc nepomůže, když se rozhodujete ještě předtím, než začnete psát kód. Tady je srovnávací tabulka, kterou bych si býval přál dostat:
| Přístup | Rychlost | Podpora JS | Odolnost proti botům | Složitost | Nejlepší pro |
|---|---|---|---|---|---|
requests + BeautifulSoup | ⚡ Rychlý (~120–200 stránek/min syrově) | ❌ Žádná | ⚠️ Nízká | Jednoduchá | Statické výsledkové stránky, menší projekty |
| Selenium / headless prohlížeč | 🐢 Pomalý (~8–20 stránek/min) | ✅ Plná | ⚠️ Střední | Střední | Dynamický obsah, klikání na „Read more“, cookie bannery |
| Skryté JSON / GraphQL API | ⚡⚡ Nejší rychlejší (~200–600 stránek/min syrově) | N/A | ✅ Vyšší | Těžká | Velkoobjemová extrakce recenzí a hotelů |
| No-code (Thunderbit) | ⚡ Rychlý | ✅ Vestavěná | ✅ Vestavěná | Nejsnazší | Netechnické uživatele, rychlé jednorázové exporty |
Je tu pár důležitých poznámek. Ty syrové rychlosti jsou teoretické — rate limiting TripAdvisoru (~10–15 požadavků za minutu na IP) ve skutečnosti omezuje průchodnost zhruba na 10 stránek za minutu na IP bez ohledu na přístup. Skrytý JSON vám dává nejvíc dat na jeden request, takže potřebujete méně celkových požadavků a jste méně vystaveni rate limitu. Selenium je v reálných testech asi 5× pomalejší než přístupy přes requests, ale je to jediná možnost, když potřebujete klikat na tlačítka nebo renderovat JavaScript.
Zbytek průvodce prochází všemi třemi Python metodami i s kompletním kódem. Vyberte si tu, která odpovídá vaší situaci, nebo je kombinujte (já často používám requests+BS4 pro seznamy a skrytý JSON pro detailní stránky).
Nastavení Python prostředí
Než začneme, připravme si prostředí. Budete potřebovat Python 3.10+ (doporučuji 3.12 nebo 3.13 — všechny hlavní balíčky je podporují bez známých problémů).
Nainstalujte vše najednou:
pip install requests beautifulsoup4 selenium httpx parsel pandas curl-cffi
Poznámky k balíčkům:
requests(2.33.1) — HTTP požadavky, vyžaduje Python 3.10+beautifulsoup4(4.14.3) — parsování HTMLselenium(4.43.0) — automatizace prohlížeče, vyžaduje Python 3.10+httpx(0.28.1) — asynchronní HTTP klientparsel(1.11.0) — CSS/XPath selektory (lehčí než BS4)pandas(3.0.2) — export dat, vyžaduje Python 3.11+curl-cffi(0.15.0) — imitace TLS fingerprintu (kritické pro obcházení Cloudflare)
ChromeDriver: Pokud používáte Selenium, dobrá zpráva — od Selenium 4.6 dál Selenium Manager automaticky stáhne a uloží správný binární soubor ChromeDriveru. Ruční instalace není potřeba. Verze se párují dynamicky, takže nemusíte řešit nesoulad verzí Chromu.
Virtuální prostředí (doporučeno):
python -m venv tripadvisor-scraper
source tripadvisor-scraper/bin/activate # macOS/Linux
tripadvisor-scraper\Scripts\activate # Windows
Přístup 1: Scrape TripAdvisor pomocí Requests a BeautifulSoup
Tohle je nejjednodušší přístup. Funguje dobře pro scraping výpisů (výsledky vyhledávání hotelů, seznamy restaurací), kde jsou potřebná data přímo v HTML. Žádný prohlížeč, žádné renderování JavaScriptu, minimální nároky na zdroje.
Pochopení vzorů URL na TripAdvisoru
URL TripAdvisoru sledují podle kategorie poměrně předvídatelné vzory:
- Hotely:
https://www.tripadvisor.com/Hotels-g{locationId}-{Location_Name}-Hotels.html - Restaurace:
https://www.tripadvisor.com/Restaurants-g{locationId}-{Location_Name}.html - Atrakce:
https://www.tripadvisor.com/Attractions-g{locationId}-Activities-{Location_Name}.html
Stránkování používá parametr oa (offset anchors) vložený do URL. Každá stránka ukazuje 30 výsledků:
- Stránka 1: základní URL (bez parametru
oa) - Stránka 2:
Hotels-g187768-oa30-Italy-Hotels.html - Stránka 3:
Hotels-g187768-oa60-Italy-Hotels.html
U stránek s recenzemi je offset parametr or a zvyšuje se po 10:
- Stránka 1:
Reviews-or0-Hotel_Name.html - Stránka 2:
Reviews-or10-Hotel_Name.html
Pro zobrazení recenzí ve všech jazycích přidejte do URL ?filterLang=ALL.
Odesílání požadavků s realistickými hlavičkami
TripAdvisor hlavičky kontroluje velmi přísně. Požadavek s defaultními Python hlavičkami je okamžitě zablokován. Musíte napodobit skutečný prohlížeč Chrome:
import requests
import time
import random
session = requests.Session()
headers = {
"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",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Referer": "https://www.tripadvisor.com/",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-CH-UA": '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
"Sec-CH-UA-Mobile": "?0",
"Sec-CH-UA-Platform": '"Windows"',
}
session.headers.update(headers)
url = "https://www.tripadvisor.com/Hotels-g187147-Paris_Ile_de_France-Hotels.html"
response = session.get(url)
print(f"Status: {response.status_code}")
print(f"Délka obsahu: {len(response.text)} znaků")
Důležitý detail: TripAdvisor ověřuje, že User-Agent a hlavičky Sec-CH-UA Client Hints spolu odpovídají. Když tvrdíte, že jste Chrome 135 v User-Agentu, ale Sec-CH-UA říká Chrome 120, budete označeni jako podezřelí. Vždy rotujte celé sady hlaviček dohromady, ne jednotlivé hlavičky zvlášť.
Parsování výpisů pomocí BeautifulSoup
Jakmile dostanete úspěšnou odpověď, data vytáhněte pomocí BeautifulSoup. TripAdvisor používá atributy data-automation a data-test-attribute, které jsou stabilnější než CSS třídy (ty se mění často):
from bs4 import BeautifulSoup
soup = BeautifulSoup(response.text, "html.parser")
# Najít všechny karty hotelů
cards = soup.select('div[data-test-attribute="location-results-card"]')
hotels = []
for card in cards:
# Název hotelu
title_el = card.select_one('div[data-automation="hotel-card-title"]')
name = title_el.get_text(strip=True) if title_el else None
# Odkaz na detail
link_el = card.select_one('div[data-automation="hotel-card-title"] a')
link = "https://www.tripadvisor.com" + link_el["href"] if link_el else None
# Hodnocení
rating_el = card.select_one('[data-automation="bubbleRatingValue"]')
rating = rating_el.get_text(strip=True) if rating_el else None
# Počet recenzí
review_el = card.select_one('[data-automation="bubbleReviewCount"]')
review_count = review_el.get_text(strip=True).replace(",", "").split()[0] if review_el else None
hotels.append({
"name": name,
"rating": rating,
"review_count": review_count,
"url": link,
})
print(f"Na této stránce jsem našel {len(hotels)} hotelů")
for h in hotels[:3]:
print(h)
Poznámka k selektorům: TripAdvisor používá obfuskované názvy tříd CSS (např. FGwzt, yyzcQ), které se mění s každou úpravou webu. Atributy data-automation a data-test-target jsou mnohem stabilnější. Vždy dávejte přednost datovým atributům před třídami.
Zpracování stránkování
Pokud chcete scrapovat víc stránek, projděte offset parametr s rozumnou prodlevou mezi požadavky:
import pandas as pd
all_hotels = []
base_url = "https://www.tripadvisor.com/Hotels-g187147-oa{offset}-Paris_Ile_de_France-Hotels.html"
for page in range(5): # Prvních 5 stránek
offset = page * 30
url = base_url.format(offset=offset) if page > 0 else "https://www.tripadvisor.com/Hotels-g187147-Paris_Ile_de_France-Hotels.html"
response = session.get(url)
if response.status_code != 200:
print(f"Stránka {page + 1}: stav {response.status_code}, končím.")
break
soup = BeautifulSoup(response.text, "html.parser")
cards = soup.select('div[data-test-attribute="location-results-card"]')
for card in cards:
title_el = card.select_one('div[data-automation="hotel-card-title"]')
name = title_el.get_text(strip=True) if title_el else None
rating_el = card.select_one('[data-automation="bubbleRatingValue"]')
rating = rating_el.get_text(strip=True) if rating_el else None
review_el = card.select_one('[data-automation="bubbleReviewCount"]')
review_count = review_el.get_text(strip=True).replace(",", "").split()[0] if review_el else None
all_hotels.append({"name": name, "rating": rating, "review_count": review_count})
print(f"Stránka {page + 1}: nalezeno {len(cards)} hotelů")
time.sleep(random.uniform(3, 7)) # Náhodná prodleva proti rate limitu
df = pd.DataFrame(all_hotels)
print(f"\nCelkem vyextrahováno hotelů: {len(df)}")
time.sleep(random.uniform(3, 7)) je důležitý. Prahová hodnota rate limitu TripAdvisoru je zhruba 10–15 požadavků za minutu na IP. Při rychlejším tempu riskujete CAPTCHA nebo chyby 429.
Omezení tohoto přístupu
Kde tento přístup selhává? Requests+BS4 selže, když:
- TripAdvisor vrací obsah vykreslený JavaScriptem (některé výsledkové stránky JS vyžadují)
- Text recenze je zkrácený za tlačítkem „Read more“
- Ochrana proti botům přejde na JavaScript challenge nebo CAPTCHA
- Potřebujete data, která se načítají až po klientském renderingu (ceny, dostupnost)
Pro tyto scénáře potřebujete buď Selenium (Přístup 2), nebo skrytý JSON (Přístup 3).
Přístup 2: Scrape TripAdvisor pomocí Selenium (headless prohlížeč)
Selenium spouští skutečný prohlížeč, takže umí renderovat JavaScript, klikat na tlačítka, řešit cookie consent bannery a pracovat s dynamickým obsahem. Cena za to: je asi 5× pomalejší a na jednu instanci prohlížeče spotřebuje 300–500 MB RAM.
Nastavení Selenium s anti-detection parametry
Ve výchozím stavu je Selenium snadno detekovatelné. Fingerprinting TripAdvisoru ho okamžitě odhalí. Musíte vypnout automatizační příznaky:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = Options()
options.add_argument("--headless=new") # Nový headless režim (Chrome 112+)
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("--window-size=1920,1080")
options.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")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
driver = webdriver.Chrome(options=options)
# Odstranit webdriver property z navigatoru
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
})
Stačí to pro TripAdvisor? Pro menší scraping (pod 50 stránek) tohle nastavení s rezidenčními proxy obvykle funguje. Při větších objemech budete možná potřebovat undetected-chromedriver nebo nodriver — ochrana DataDome na TripAdvisoru vyhodnocuje přes 1 000 signálů na jeden request včetně TLS fingerprintů, které čisté Selenium neumí napodobit.
Scrapování výsledků hotelů pomocí Selenium
import time
import random
url = "https://www.tripadvisor.com/Hotels-g187147-Paris_Ile_de_France-Hotels.html"
driver.get(url)
# Počkat, až se načtou karty hotelů
wait = WebDriverWait(driver, 15)
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div[data-test-attribute="location-results-card"]')))
# Zpracování cookie consent popupu (pokud se objeví)
try:
cookie_btn = driver.find_element(By.ID, "onetrust-accept-btn-handler")
cookie_btn.click()
time.sleep(1)
except:
pass # Žádný cookie popup
# Extrakce dat o hotelech
cards = driver.find_elements(By.CSS_SELECTOR, 'div[data-test-attribute="location-results-card"]')
hotels = []
for card in cards:
try:
name = card.find_element(By.CSS_SELECTOR, 'div[data-automation="hotel-card-title"]').text
except:
name = None
try:
rating = card.find_element(By.CSS_SELECTOR, '[data-automation="bubbleRatingValue"]').text
except:
rating = None
try:
reviews = card.find_element(By.CSS_SELECTOR, '[data-automation="bubbleReviewCount"]').text
except:
reviews = None
hotels.append({"name": name, "rating": rating, "review_count": reviews})
print(f"Vyextrahováno {len(hotels)} hotelů")
for h in hotels[:3]:
print(h)
Na mém stroji to trvalo asi 8 sekund na jednu stránku — oproti méně než 1 sekundě u requests+BS4. Ten rozdíl se při scrapování stovek stránek rychle nasčítá.
Rozkliknutí „Read more“ a scrapování celých recenzí
Stránky recenzí zkracují dlouhé texty za tlačítkem „Read more“. Selenium na to umí kliknout:
review_url = "https://www.tripadvisor.com/Hotel_Review-g187147-d188726-Reviews-Le_Marais_Hotel-Paris_Ile_de_France.html"
driver.get(review_url)
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div[data-reviewid]')))
time.sleep(2)
# Kliknout na všechna tlačítka „Read more“
read_more_buttons = driver.find_elements(By.XPATH, '//button//*[contains(text(), "Read more")]/..')
for btn in read_more_buttons:
try:
driver.execute_script("arguments[0].click();", btn)
time.sleep(0.3)
except:
pass
# Extrakce recenzí
review_elements = driver.find_elements(By.CSS_SELECTOR, 'div[data-reviewid]')
reviews = []
for rev in review_elements:
try:
title = rev.find_element(By.CSS_SELECTOR, 'div[data-test-target="review-title"]').text
except:
title = None
try:
body = rev.find_element(By.CSS_SELECTOR, 'q.IRsGHoPm span').text
except:
try:
body = rev.find_element(By.CSS_SELECTOR, 'p.partial_entry').text
except:
body = None
try:
rating_class = rev.find_element(By.CSS_SELECTOR, 'div[data-test-target="review-rating"] span').get_attribute("class")
# Hodnocení je zakódované v class jako "ui_bubble_rating bubble_50" = 5.0
rating_num = [c for c in rating_class.split() if "bubble_" in c][0].replace("bubble_", "")
rating = int(rating_num) / 10
except:
rating = None
reviews.append({"title": title, "body": body, "rating": rating})
print(f"Vyextrahováno {len(reviews)} recenzí")
Přidání rotace proxy do Selenium
Pro dlouhodobý scraping budete potřebovat rotaci proxy. Protože selenium-wire je od ledna 2024 zastaralý, použijte vestavěnou podporu proxy v Chromu:
# S proxy bez autentizace
proxy = "http://your-proxy-address:port"
options.add_argument(f"--proxy-server={proxy}")
# U proxy s autentizací použijte Chrome extension nebo BiDi protokol v Selenium 4
Pro programovou rotaci proxy vytvářejte novou instanci driveru s jinou proxy pro každou dávku požadavků. Není to elegantní, ale funguje to spolehlivě.
Přístup 3: Skrytý JSON (bez parsování HTML)
Většina návodů tento přístup úplně přeskakuje, což je škoda — je to nejrychlejší a nejčistší ze všech tří. TripAdvisor vkládá strukturovaná data přímo do HTML stránky ve formě JSONu — uvnitř <script> tagů jako JavaScriptové proměnné typu pageManifest a urqlCache. Extrakce tohoto JSONu vám dá čistší data (hodnocení jako čísla, data ve formátu ISO), méně requestů a žádnou potřebu renderovat JavaScript.
Hledání vloženého JSONu ve zdrojovém kódu stránky
Klíčová myšlenka: můžete použít jednoduchý requests.get() pro stažení stránky a potom vytáhnout JSON ze surového HTML, aniž byste vůbec renderovali JavaScript.
import requests
import re
import json
headers = {
"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",
"Accept-Language": "en-US,en;q=0.9",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Referer": "https://www.tripadvisor.com/",
"Sec-CH-UA": '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
"Sec-CH-UA-Mobile": "?0",
"Sec-CH-UA-Platform": '"macOS"',
}
url = "https://www.tripadvisor.com/Hotel_Review-g188590-d194317-Reviews-NH_City_Centre_Amsterdam.html"
response = requests.get(url, headers=headers)
# Vytáhnout JSON blob pageManifest
match = re.search(r"pageManifest:({.+?})};", response.text)
if match:
page_data = json.loads(match.group(1))
print("Našel jsem data pageManifest")
print(f"Klíče: {list(page_data.keys())[:10]}")
Jak zjistit název proměnné sami: Otevřete libovolnou hotelovou stránku TripAdvisoru v Chromu, klikněte pravým tlačítkem → Zobrazit zdroj stránky a pak Ctrl+F na pageManifest, urqlCache nebo aggregateRating. Ta data tam jsou, jen čekají na parsování.
Parsování JSONu a extrakce strukturovaných dat
TripAdvisor také vkládá data schema.org v application/ld+json, která se ještě snadněji extrahují:
from parsel import Selector
sel = Selector(text=response.text)
# Extrakce JSON-LD strukturovaných dat
json_ld_scripts = sel.xpath("//script[@type='application/ld+json']/text()").getall()
for script in json_ld_scripts:
data = json.loads(script)
if isinstance(data, dict) and data.get("@type") in ["Hotel", "Restaurant", "TouristAttraction"]:
print(f"Název: {data.get('name')}")
print(f"Hodnocení: {data.get('aggregateRating', {}).get('ratingValue')}")
print(f"Počet recenzí: {data.get('aggregateRating', {}).get('reviewCount')}")
print(f"Cenové rozmezí: {data.get('priceRange')}")
print(f"Adresa: {data.get('address', {}).get('streetAddress')}")
print(f"Souřadnice: {data.get('geo', {}).get('latitude')}, {data.get('geo', {}).get('longitude')}")
break
Data JSON-LD jsou vložená do statického HTML a NEvyžadují renderování JavaScriptu. Získáte název objektu, agregované hodnocení, počet recenzí, adresu, souřadnice, cenové rozmezí i URL fotek — bez parsování jediného HTML tagu.
Pro bohatší data (jednotlivé recenze, rozpad hodnocení, seznam vybavení) potřebujete objekt urqlCache:
# Extrakce urqlCache pro detailní data recenzí
cache_match = re.search(r'"urqlCache"\s*:\s*({.+?})\s*,\s*"redux"', response.text)
if cache_match:
cache_data = json.loads(cache_match.group(1))
# Projděte cache a najděte data o recenzích
for key, value in cache_data.items():
if "reviews" in str(value).lower()[:100]:
reviews_data = json.loads(value.get("data", "{}")) if isinstance(value, dict) else None
if reviews_data:
print(f"Našel jsem položku cache s recenzemi: {key[:50]}...")
break
Přesné JSON cesty se občas mění, když TripAdvisor aktualizuje frontend, ale obecná struktura — JSON-LD pro souhrnná data a urqlCache pro detailní data — je stabilní už roky.
Reverzní analýza GraphQL API TripAdvisoru (pokročilé)
Pro velký objem dat vrací GraphQL endpointy TripAdvisoru strukturovaná data přímo. Je to nejrychlejší metoda, ale vyžaduje nejvíc údržby.
import httpx
import random
import string
def generate_request_id():
"""Vygeneruje hodnotu hlavičky X-Requested-By"""
random_chars = ''.join(random.choices(string.ascii_letters + string.digits, k=180))
return f"TNI1625!{random_chars}"
# Vyhledání hotelů v Paříži
search_payload = [{
"variables": {
"request": {
"query": "hotels in Paris",
"limit": 10,
"scope": "WORLDWIDE",
"locale": "en-US",
"scopeGeoId": 1,
"searchCenter": None,
"types": ["LOCATION", "QUERY_SUGGESTION", "RESCUE_RESULT"],
"locationTypes": ["GEO", "AIRPORT", "ACCOMMODATION", "ATTRACTION", "EATERY", "NEIGHBORHOOD"]
}
},
"extensions": {
"preRegisteredQueryId": "84b17ed122fbdbd4"
}
}]
graphql_headers = {
"Content-Type": "application/json",
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.9",
"Origin": "https://www.tripadvisor.com",
"Referer": "https://www.tripadvisor.com/Hotels",
"X-Requested-By": generate_request_id(),
"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",
}
with httpx.Client() as client:
response = client.post(
"https://www.tripadvisor.com/data/graphql/ids",
json=search_payload,
headers=graphql_headers
)
if response.status_code == 200:
results = response.json()
print(json.dumps(results, indent=2)[:1000])
else:
print(f"GraphQL request failed: {response.status_code}")
Pro získání recenzí přes GraphQL:
review_payload = [{
"variables": {
"locationId": 194317, # NH City Centre Amsterdam
"offset": 0,
"limit": 20,
"filters": {},
"sortType": None,
"sortBy": "date",
"language": "en",
"doMachineTranslation": False,
"photosPerReviewLimit": 3
},
"extensions": {
"preRegisteredQueryId": "ef1a9f94012220d3"
}
}]
with httpx.Client() as client:
response = client.post(
"https://www.tripadvisor.com/data/graphql/ids",
json=review_payload,
headers=graphql_headers
)
if response.status_code == 200:
data = response.json()
reviews = data[0]["data"]["locations"][0]["reviewListPage"]["reviews"]
total = data[0]["data"]["locations"][0]["reviewListPage"]["totalCount"]
print(f"Celkem recenzí: {total}")
for r in reviews[:3]:
print(f" [{r['rating']}/5] {r['title']} - {r['createdDate']}")
Důležité upozornění: Hodnoty preRegisteredQueryId (např. 84b17ed122fbdbd4 pro vyhledávání a ef1a9f94012220d3 pro recenze) se mohou rozbít, když TripAdvisor nasadí novou verzi. V takovém případě požadavky selžou potichu. Budete muset znovu zjistit query ID sledováním síťových požadavků v DevTools prohlížeče.
Proč tato metoda snižuje potřebu proxy
Matematika je jednoduchá. S requests+BS4 potřebujete na 100 detailních stránek hotelů 100 requestů. S metodou skrytého JSONu dostanete z jedné načtené stránky všechna data, která potřebujete — bez dalších požadavků na rozbalení recenzí nebo načítání dynamického obsahu. S GraphQL může jeden API call vrátit 20 recenzí najednou. Méně requestů = menší vystavení rate limitům = menší potřeba rotace proxy. U malých a středních projektů (pod 1 000 stránek) možná nebudete potřebovat proxy vůbec, pokud přidáte rozumné prodlevy.
Scrape hotely, restaurace i atrakce jedním znovupoužitelným skriptem
Čtyři z pěti konkurenčních návodů pokrývají jen hotely. TripAdvisor ale má tři hlavní typy obsahu a URL vzory i datová pole se mezi nimi liší. Tady je způsob, jak postavit jednu funkci, která zvládne všechny tři.
Dostupná datová pole podle kategorie
| Pole | Hotely | Restaurace | Atrakce |
|---|---|---|---|
| Název | ✅ | ✅ | ✅ |
| Hodnocení | ✅ | ✅ | ✅ |
| Počet recenzí | ✅ | ✅ | ✅ |
| Cena / cenové rozmezí | ✅ | ✅ | Někdy |
| Adresa | ✅ | ✅ | ✅ |
| Typ kuchyně | ❌ | ✅ | ❌ |
| Délka / typ zájezdu | ❌ | ❌ | ✅ |
| Vybavení | ✅ | ❌ | ❌ |
| Souřadnice | ✅ | ✅ | ✅ |
Vytvoření znovupoužitelné funkce scrape_tripadvisor()
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import random
import re
import json
def scrape_tripadvisor(category, location_id, location_name, num_pages=3):
"""
Scrape TripAdvisor listings across hotels, restaurants, or attractions.
Args:
category: "hotels", "restaurants", or "attractions"
location_id: TripAdvisor geo ID (e.g., "187147" for Paris)
location_name: URL-friendly name (e.g., "Paris_Ile_de_France")
num_pages: Number of pages to scrape
"""
url_patterns = {
"hotels": "https://www.tripadvisor.com/Hotels-g{geo}-oa{offset}-{name}-Hotels.html",
"restaurants": "https://www.tripadvisor.com/Restaurants-g{geo}-oa{offset}-{name}.html",
"attractions": "https://www.tripadvisor.com/Attractions-g{geo}-oa{offset}-Activities-{name}.html",
}
first_page_patterns = {
"hotels": "https://www.tripadvisor.com/Hotels-g{geo}-{name}-Hotels.html",
"restaurants": "https://www.tripadvisor.com/Restaurants-g{geo}-{name}.html",
"attractions": "https://www.tripadvisor.com/Attractions-g{geo}-Activities-{name}.html",
}
headers = {
"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",
"Accept-Language": "en-US,en;q=0.9",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Referer": "https://www.tripadvisor.com/",
"Sec-CH-UA": '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
"Sec-CH-UA-Mobile": "?0",
"Sec-CH-UA-Platform": '"Windows"',
}
session = requests.Session()
session.headers.update(headers)
all_items = []
for page in range(num_pages):
offset = page * 30
if page == 0:
url = first_page_patterns[category].format(geo=location_id, name=location_name)
else:
url = url_patterns[category].format(geo=location_id, offset=offset, name=location_name)
response = session.get(url)
if response.status_code != 200:
print(f" Stránka {page + 1}: stav {response.status_code}, končím.")
break
soup = BeautifulSoup(response.text, "html.parser")
cards = soup.select('div[data-test-attribute="location-results-card"]')
for card in cards:
item = {"category": category}
title_el = card.select_one('div[data-automation="hotel-card-title"]') or card.select_one('a[data-automation]')
item["name"] = title_el.get_text(strip=True) if title_el else None
rating_el = card.select_one('[data-automation="bubbleRatingValue"]')
item["rating"] = rating_el.get_text(strip=True) if rating_el else None
review_el = card.select_one('[data-automation="bubbleReviewCount"]')
item["review_count"] = review_el.get_text(strip=True) if review_el else None
all_items.append(item)
print(f" Stránka {page + 1}: nalezeno {len(cards)} položek")
time.sleep(random.uniform(3, 7))
return pd.DataFrame(all_items)
# Ukázky použití
print("=== Hotely v Paříži ===")
hotels_df = scrape_tripadvisor("hotels", "187147", "Paris_Ile_de_France", num_pages=2)
print(hotels_df.head())
print("\n=== Restaurace v Římě ===")
restaurants_df = scrape_tripadvisor("restaurants", "187791", "Rome_Lazio", num_pages=2)
print(restaurants_df.head())
print("\n=== Atrakce v Barceloně ===")
attractions_df = scrape_tripadvisor("attractions", "187497", "Barcelona_Catalonia", num_pages=2)
print(attractions_df.head())
Jedna funkce, tři kategorie, žádná duplikace kódu. Když TripAdvisor změní selektor, opravíte to na jednom místě.
Co dělat, když vás TripAdvisor blokuje (řešení anti-bot problémů)
Tohle je část, kterou jsem potřeboval nejvíc, když jsem začínal s TripAdvisor scrapingem — a také část, kterou žádný konkurenční návod nepodává strukturovaně. TripAdvisor používá DataDome (analyzuje 5+ bilionů datových bodů denně) spolu s Cloudflare WAF. Tady je diagnostická tabulka pro nejběžnější selhání:
| Příznak | Pravděpodobná příčina | Oprava |
|---|---|---|
| Odpověď HTTP 403 | Chybějící nebo podezřelé hlavičky; Cloudflare JS challenge | Nastavte realistické hlavičky User-Agent, Accept-Language, Referer a Sec-CH-UA. Zajistěte jejich konzistenci. |
| CAPTCHA místo dat | Rate limiting nebo fingerprinting prohlížeče | Rotujte rezidenční proxy, přidejte náhodné prodlevy (2–7 sekund mezi requesty) |
| Prázdné HTML nebo prázdné tělo stránky | JavaScript nebyl vykreslen pomocí requests | Přejděte na Selenium nebo extrahujte data ze skrytého JSONu ve zdrojovém kódu |
| Částečné recenze / „Read more“ se nerozbalí | Obsah se načítá po kliknutí | Použijte Selenium .click() nebo extrahujte z vloženého JSON blobu |
| Recenze jen v jednom jazyce | Chybí parametr jazyka | Přidejte do URL recenzí ?filterLang=ALL |
| Data se přestanou načítat po N stránkách | Rate limit založený na session | Rotujte sessions, mezi dávkami čistěte cookies |
| HTTP 1020 Access Denied | IP/ASN zablokováno Cloudflare | Přepněte z datacentrových na rezidenční proxy |
| Nekonečná CAPTCHA smyčka | Rozbité uchovávání cookies | Nejprve načtěte homepage pro zahřátí session; udržujte cookie jar |
Retry logika s exponenciálním backoffem
Žádný konkurenční článek tenhle kód ve skutečnosti neukazuje. Tady je znovupoužitelná retry funkce:
import time
import random
import requests
def fetch_with_retry(session, url, max_retries=4, base_delay=2, max_delay=60):
"""
Fetch a URL with exponential backoff and jitter.
Rotates User-Agent on each retry.
"""
user_agents = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
"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",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
]
for attempt in range(max_retries):
# Při opakování rotovat User-Agent
if attempt > 0:
session.headers["User-Agent"] = random.choice(user_agents)
try:
response = session.get(url, timeout=30)
if response.status_code == 200:
return response
if response.status_code == 429:
# Respektujte Retry-After, pokud existuje
retry_after = int(response.headers.get("Retry-After", base_delay * (2 ** attempt)))
print(f" Rate limit (429). Čekám {retry_after}s...")
time.sleep(retry_after)
continue
if response.status_code in (403, 503):
wait = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
print(f" Dostali jsme {response.status_code}. Pokus {attempt + 1}/{max_retries} za {wait:.1f}s...")
time.sleep(wait)
continue
# Ostatní chyby — neopakovat
print(f" Neočekávaný stav {response.status_code} pro {url}")
return response
except requests.exceptions.Timeout:
wait = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
print(f" Timeout. Pokus {attempt + 1}/{max_retries} za {wait:.1f}s...")
time.sleep(wait)
print(f" Všechny {max_retries} pokusy vyčerpány pro {url}")
return None
Rotace hlaviček, proxy a sessions
Pro dlouhodobý scraping udržujte pool sad hlaviček a rotujte je dohromady:
import random
HEADER_SETS = [
{
"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",
"Sec-CH-UA": '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
"Sec-CH-UA-Platform": '"Windows"',
},
{
"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",
"Sec-CH-UA": '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
"Sec-CH-UA-Platform": '"macOS"',
},
{
"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",
"Sec-CH-UA": '"Google Chrome";v="134", "Not-A.Brand";v="8", "Chromium";v="134"',
"Sec-CH-UA-Platform": '"Windows"',
},
]
PROXY_LIST = [
"http://user:pass@residential-proxy-1:port",
"http://user:pass@residential-proxy-2:port",
# Přidejte další rezidenční proxy
]
def get_rotated_session():
"""Vytvoří novou session s rotovanými hlavičkami a proxy."""
session = requests.Session()
# Vybereme náhodnou sadu hlaviček
header_set = random.choice(HEADER_SETS)
base_headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Referer": "https://www.tripadvisor.com/",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-CH-UA-Mobile": "?0",
}
base_headers.update(header_set)
session.headers.update(base_headers)
# Vybereme náhodnou proxy
if PROXY_LIST:
proxy = random.choice(PROXY_LIST)
session.proxies = {"http": proxy, "https": proxy}
return session
Typ proxy je zásadní. Datacentrové proxy TripAdvisor blokuje téměř okamžitě (HTTP 1020 Access Denied). Pro dlouhodobý scraping jsou nutné rezidenční proxy — běží přes běžné ISP a jsou téměř nerozeznatelné od skutečných uživatelů. Počítejte s cenou 2,50–8,40 USD/GB podle poskytovatele.
Export a ukládání nasbíraných dat z TripAdvisoru
Jakmile data máte, dostat je do použitelného formátu je jednoduché.
Export do CSV (nejběžnější)
import pandas as pd
df = pd.DataFrame(all_hotels)
df.to_csv("tripadvisor_hotels_paris.csv", index=False, encoding="utf-8-sig")
print(f"Exportováno {len(df)} řádků do CSV")
encoding='utf-8-sig' je důležité — zajistí, že Excel při otevření CSV správně zobrazí ne-latinkové znaky (francouzské akcenty, čínské znaky atd.).
Export do JSON (pro vnořená data)
Když máte recenze vnořené pod hotely, JSON zachová hierarchii:
# Hierarchická struktura
hotel_data = {
"property_id": "d194317",
"name": "NH City Centre Amsterdam",
"rating": 4.0,
"reviews": [
{"title": "Skvělá lokalita", "rating": 5, "date": "2025-03-15", "text": "..."},
{"title": "Průměrný pobyt", "rating": 3, "date": "2025-03-10", "text": "..."},
]
}
# Pro plochou analýzu použijte json_normalize
flat_reviews = pd.json_normalize(
hotel_data,
record_path="reviews",
meta=["property_id", "name"]
)
flat_reviews.to_csv("reviews_flat.csv", index=False)
Dvou-souborový přístup pro relační data
U velkých datasetů používám dva CSV soubory:
hotels.csv— jeden řádek na jednu nemovitost (ploché)reviews.csv— jeden řádek na jednu recenzi, sproperty_idjako cizím klíčem
Snadno to pak spojíte v pandas, nahrajete do databáze nebo importujete do BI nástrojů.
Pokud se nechcete zabývat exportní logikou, Thunderbit umožňuje exportovat nasbíraná data přímo do Excelu, Google Sheets, Airtable nebo Notion — zdarma a bez kódu. Hodí se, když potřebujete výsledky sdílet s netechnickými kolegy.
Tipy pro zodpovědný a efektivní scraping TripAdvisoru
Zodpovědný scraping v šesti bodech:
- Zkontrolujte
robots.txt: TripAdvisor v robots.txt úplně blokuje AI trénovací boty (GPTBot, ClaudeBot atd.). Standardní crawlery mají jen částečně omezené cesty. Podívejte se natripadvisor.com/robots.txt. - Přidávejte prodlevy: 3–7 sekund mezi requesty je bezpečné rozmezí. Při rychlejším tempu než 10–15 požadavků za minutu na IP riskujete rate limiting.
- Scrapujte jen veřejná data. Nepřihlašujte se kvůli obsahu, který je jinak nepřístupný.
- Ukládejte data bezpečně a dodržujte GDPR/CCPA, pokud pracujete s osobními údaji (jména recenzentů apod.).
- Zvažte oficiální API TripAdvisoru, pokud potřebujete data v komerčním měřítku. Developer Portal nabízí přístup k firemním údajům a až 5 recenzím a 5 fotografiím na lokaci — omezené, ale legální a stabilní.
- Buďte si vědomi právního rámce: Rozhodnutí Soudního dvora EU ve věci Ryanair (prosinec 2025) posílilo zákazy scrapingu založené na podmínkách použití napříč EU. Podmínky TripAdvisoru scraping výslovně zakazují. Scrapeujte zodpovědně a na vlastní riziko.
Shrnutí
To je celý obrázek.
- Requests + BeautifulSoup je nejjednodušší cesta. Funguje pro statické výpisové stránky, vyžaduje minimum nastavení a je rychlá. Začněte tady, pokud scrapujete méně než 100 stránek a nepotřebujete obsah renderovaný JavaScriptem.
- Selenium zvládne vše, co requests neumí: dynamický obsah, tlačítka „Read more“, cookie bannery. Je asi 5× pomalejší a náročnější na zdroje, ale je to jediná možnost, když potřebujete s webem interagovat.
- Skrytý JSON / GraphQL je nejčistší a nejrychlejší přístup. Dá vám strukturovaná data bez parsování HTML, sníží počet requestů (a tím i potřebu proxy) a vrací data ve formátech připravených pro analýzu. Vyžaduje víc reverzního inženýrství na začátku a občasnou údržbu, když TripAdvisor změní datovou strukturu.
Znovupoužitelná funkce scrape_tripadvisor() pokrývá hotely, restaurace i atrakce. Neměli byste potřebovat druhý návod.
A pokud během čtení zjistíte, že programování není nic pro vás — nebo prostě potřebujete do konce dne 50 hotelů v tabulce — Chrome rozšíření Thunderbit to zvládne na dva kliky s AI rozpoznáním polí, automatickým stránkováním a exportem zdarma do Excelu nebo Google Sheets. Python není nutný.
Pokud chcete jít víc do hloubky, máme další návody na scrapování na blogu Thunderbit a na našem YouTube kanálu.
Často kladené otázky
1. Je legální scrapovat TripAdvisor?
Podmínky použití TripAdvisoru scraping výslovně zakazují. Soudy ale obecně došly k závěru, že scrapování veřejně dostupných dat (ne za přihlášením) neporušuje v USA zákon Computer Fraud and Abuse Act. Na druhou stranu rozhodnutí Soudního dvora EU ve věci Ryanair z roku 2025 posílilo omezení založená na ToS v Evropě. Scrapujte jen veřejná data, respektujte robots.txt, znovu nepublikujte chráněný obsah a pokud data používáte komerčně, poraďte se s právníkem.
2. Můžu scrapovat TripAdvisor bez Pythonu?
Ano. No-code nástroje jako Thunderbit umí TripAdvisor scrapovat přímo z prohlížeče pomocí AI detekce polí a automatického stránkování. Můžete také použít browser extension, doplňky do Google Sheets nebo komerční scraping API. Python vám dá nejvíc kontroly a flexibility, ale není to jediná možnost.
3. Jak se vyhnout zablokování při scrapování TripAdvisoru?
Klíčové taktiky: používejte realistické a konzistentní hlavičky (hlavně User-Agent a Sec-CH-UA), rotujte rezidenční proxy (datacentrové IP se blokují okamžitě), přidávejte náhodné prodlevy 3–7 sekund mezi requesty, používejte metodu skrytého JSONu, abyste minimalizovali počet requestů, implementujte retry logiku s exponenciálním backoffem a před scrapováním hlubších stránek „zahřejte“ session návštěvou homepage.
4. Jaká data můžu z TripAdvisoru scrapovat?
Hotely, restaurace a atrakce — včetně názvů, hodnocení, počtu recenzí, cenových rozmezí, adres, souřadnic, vybavení (hotely), typů kuchyně (restaurace), délky zájezdů (atrakce) a plných textů recenzí s individuálním hodnocením a datem. Přístupy přes skrytý JSON a GraphQL vrací na jeden request nejbohatší data.
5. Kolik stránek mohu z TripAdvisoru scrapovat za den?
S jednou IP a rozumnými prodlevami zhruba 600–1 000 stránek denně. S 20 rotujícími rezidenčními proxy přibližně 200 000–300 000 stránek denně při použití request-based přístupů. Selenium je pomalejší — počítejte s 8 000–12 000 stránkami denně na proxy. Skrytý JSON / GraphQL dává nejvíc dat na jeden request, takže pro stejné množství informací často potřebujete výrazně méně stránek.
Další informace


