W zeszłym tygodniu próbowałem pobrać oceny hoteli i liczbę recenzji dla około 200 obiektów w trzech europejskich miastach z TripAdvisor. Mój pierwszy skrypt — zwykły requests.get() z domyślnymi nagłówkami — zwrócił piękny błąd 403 Forbidden przy każdym pojedynczym żądaniu. Ani jednego bajtu użytecznych danych.
TripAdvisor to jedno z najbogatszych publicznych źródeł danych w branży turystycznej: ponad , ponad 8 milionów wpisów firm oraz około 460 milionów unikalnych użytkowników miesięcznie. Wpływa na ponad rocznych wydatków na podróże. Ale pobieranie tych danych programowo? Tu zaczynają się schody. TripAdvisor korzysta z wykrywania botów DataDome, zapór Cloudflare WAF, fingerprintingu TLS i wyzwań JavaScript — to wielowarstwowa ochrona, która blokuje większość naiwnych prób scrapowania jeszcze zanim się rozpoczną. Ten poradnik to jedna kompletna instrukcja, której sam chciałbym wtedy mieć: bezpośrednie porównanie trzech podejść w Pythonie (plus opcja no-code), gotowy kod dla każdego z nich, uporządkowaną sekcję rozwiązywania problemów z anty-botami oraz wielokrotnego użytku wzorce, które działają dla hoteli, restauracji i atrakcji. Niezależnie od tego, czy dopiero zaczynasz z Pythonem, czy jesteś doświadczonym programistą, ten materiał pozwoli Ci oszczędzić mnóstwo niepotrzebnych błędów 403.
Nie chcesz pisać kodu? Zrób scraping TripAdvisor prostszą drogą
Chcę powiedzieć to wprost: wiele osób szukających frazy „scrape TripAdvisor with Python” tak naprawdę nie marzy o pisaniu kodu. Po prostu potrzebują danych — nazw hoteli, ocen, liczby recenzji, cen — i to szybko, najlepiej do arkusza kalkulacyjnego. Jeśli to brzmi znajomo, istnieje znacznie krótsza ścieżka.
to oparta na AI wtyczka do Chrome, którą zbudowaliśmy. Potrafi odczytać dowolną stronę TripAdvisor i automatycznie zasugerować odpowiednie kolumny do wyciągnięcia danych. Proces naprawdę sprowadza się do dwóch kliknięć:
- Otwórz stronę z wynikami na TripAdvisor (np. wyszukiwanie „Hotele w Paryżu”).
- Kliknij „AI Suggest Fields” w panelu bocznym Thunderbit. AI analizuje stronę i proponuje kolumny, takie jak Nazwa hotelu, Ocena, Liczba recenzji, Cena i Lokalizacja.
- Kliknij „Scrape”. Thunderbit wyciąga dane ze wszystkich ofert na stronie — a jeśli potrzebujesz więcej wyników, automatycznie obsługuje paginację.
- Wyeksportuj dane do Excela, Google Sheets, Airtable lub Notion. Eksport jest darmowy w każdym planie.
Thunderbit działa dla hoteli, restauracji i atrakcji bez żadnych zmian konfiguracji — AI dopasowuje się do tego, co akurat znajduje się na stronie. W przypadku wyników stronicowanych automatycznie wykrywa przycisk „Next” i nieskończone przewijanie. A ponieważ działa w Twojej prawdziwej przeglądarce Chrome, korzysta z ciasteczek sesji i fingerprintu przeglądarki, co daje mu naturalną przewagę nad mechanizmami wykrywania botów.
Możesz wypróbować to przez — darmowy plan daje 6 stron miesięcznie, czyli wystarczająco, by przetestować ten workflow.
Jeśli potrzebujesz kontroli programistycznej, własnej logiki parsowania albo planujesz zebrać ponad 10 000 stron, Python będzie właściwym wyborem. Czytaj dalej.
Dlaczego warto scrapować TripAdvisor w Pythonie?
Dane z TripAdvisor mają bezpośredni, mierzalny wpływ na biznes. Badanie wykazało, że wzrost Global Review Index hotelu o 1 punkt na 100 punktów przekłada się na wzrost średniej stawki dziennej o 0,89% i wzrost Revenue Per Available Room o 1,42%. Inne badanie pokazało, że zewnętrzny wzrost oceny TripAdvisor o 1 gwiazdkę oznacza dodatkowe 55 000–75 000 dolarów rocznego przychodu dla przeciętnego hotelu. Recenzje to nie tylko wskaźnik „na pokaz” — to realny motor przychodów.
Tak różne zespoły wykorzystują dane z TripAdvisor:
| Przypadek użycia | Kto zyskuje | Jakie dane są potrzebne |
|---|---|---|
| Analiza konkurencji hoteli | Sieci hotelowe, revenue managerowie | Oceny, ceny, liczba recenzji, udogodnienia |
| Badanie rynku restauracji | Grupy restauracyjne, marki food & beverage | Typy kuchni, przedziały cenowe, sentyment recenzji |
| Śledzenie trendów atrakcji | Operatorzy turystyczni, organizacje turystyczne | Popularność, sezonowość |
| Analiza sentymentu | Badacze, analitycy danych | Pełne treści recenzji, oceny gwiazdkowe, daty |
| Generowanie leadów | Zespoły sprzedaży, biura podróży | Nazwy firm, dane kontaktowe, lokalizacje |
Dlaczego akurat Python? Z trzech powodów. Po pierwsze, ekosystem: BeautifulSoup, Selenium, Playwright, Scrapy, httpx, pandas — Python ma dojrzalsze biblioteki do scrapowania i analizy danych niż jakikolwiek inny język. Po drugie, używa Pythona, co oznacza większe wsparcie społeczności, więcej odpowiedzi na Stack Overflow i bardziej aktualne poradniki. Po trzecie, przewaga w całym pipeline: możesz scrapować z BeautifulSoup, czyścić dane w pandas, uruchamiać analizę sentymentu w Hugging Face Transformers i budować dashboardy — wszystko w jednym języku. Bez przełączania kontekstu.
Trzy sposoby na scrapowanie TripAdvisor w Pythonie (porównanie)
Każdy konkurencyjny poradnik wybiera jedno podejście i trzyma się go kurczowo. To nie pomaga, gdy chcesz podjąć decyzję zanim napiszesz kod. Oto tabela porównawcza, którą sam chciałbym wtedy dostać:
| Podejście | Szybkość | Obsługa JS | Odporność na anty-boty | Złożoność | Najlepsze do |
|---|---|---|---|---|---|
requests + BeautifulSoup | ⚡ Szybkie (~120–200 stron/min surowo) | ❌ Brak | ⚠️ Niska | Łatwe | Statyczne strony z listami, małe projekty |
| Selenium / przeglądarka headless | 🐢 Wolne (~8–20 stron/min) | ✅ Pełna | ⚠️ Średnia | Średnia | Treści dynamiczne, kliknięcia „Read more”, banery cookies |
| Ukryte API JSON / GraphQL | ⚡⚡ Najszybsze (~200–600 stron/min surowo) | N/A | ✅ Wyższa | Trudne | Duża skala ekstrakcji opinii/hoteli |
| No-code (Thunderbit) | ⚡ Szybkie | ✅ Wbudowana | ✅ Wbudowana | Najłatwiejsze | Osoby bez kodowania, szybkie jednorazowe eksporty |
Kilka ważnych zastrzeżeń. Podane surowe prędkości są teoretyczne — limity TripAdvisor (~10–15 żądań na minutę na IP) ograniczają rzeczywistą przepustowość do około 10 stron/min na IP, niezależnie od podejścia. Metoda ukrytego JSON daje najwięcej danych na jedno żądanie, więc oznacza mniej zapytań łącznie i mniejsze narażenie na rate limiting. Selenium jest w praktyce około 5 razy wolniejsze niż podejścia oparte na requestach, ale to jedyna opcja, gdy trzeba klikać przyciski albo renderować JavaScript.
Dalsza część poradnika pokazuje wszystkie trzy metody w Pythonie wraz z kompletnym kodem. Wybierz tę, która pasuje do Twojej sytuacji, albo połącz je (ja często używam requests+BS4 dla stron z listami i ukrytego JSON dla stron szczegółowych).
Przygotowanie środowiska Pythona
Zanim zaczniemy, przygotujmy środowisko. Potrzebujesz Pythona 3.10+ (polecam 3.12 lub 3.13 — wszystkie główne pakiety obsługują te wersje bez znanych problemów).
Zainstaluj wszystko jednocześnie:
1pip install requests beautifulsoup4 selenium httpx parsel pandas curl-cffi
Uwagi o pakietach:
requests(2.33.1) — żądania HTTP, wymaga Pythona 3.10+beautifulsoup4(4.14.3) — parsowanie HTMLselenium(4.43.0) — automatyzacja przeglądarki, wymaga Pythona 3.10+httpx(0.28.1) — asynchroniczny klient HTTPparsel(1.11.0) — selektory CSS/XPath (lżejsze niż BS4)pandas(3.0.2) — eksport danych, wymaga Pythona 3.11+curl_cffi(0.15.0) — imitacja fingerprintu TLS (kluczowe przy obchodzeniu Cloudflare)
ChromeDriver: Jeśli korzystasz z Selenium, dobra wiadomość — od Selenium 4.6 Selenium Manager automatycznie pobiera i buforuje właściwy plik ChromeDriver. Nie musisz instalować go ręcznie. Dopasowanie wersji odbywa się dynamicznie, więc nie trzeba martwić się niezgodnością wersji Chrome.
Wirtualne środowisko (zalecane):
1python -m venv tripadvisor-scraper
2source tripadvisor-scraper/bin/activate # macOS/Linux
3tripadvisor-scraper\Scripts\activate # Windows
Podejście 1: scrapowanie TripAdvisor za pomocą Requests i BeautifulSoup
To najprostsza metoda. Sprawdza się dobrze przy stronach z listami (wyniki wyszukiwania hoteli, listy restauracji), gdzie potrzebne dane znajdują się w statycznym HTML. Bez przeglądarki, bez renderowania JavaScript, minimalne zużycie zasobów.
Zrozumienie wzorców URL TripAdvisor
Adresy TripAdvisor mają przewidywalne wzorce zależne od kategorii:
- Hotele:
https://www.tripadvisor.com/Hotels-g{locationId}-{Location_Name}-Hotels.html - Restauracje:
https://www.tripadvisor.com/Restaurants-g{locationId}-{Location_Name}.html - Atrakcje:
https://www.tripadvisor.com/Attractions-g{locationId}-Activities-{Location_Name}.html
Paginacja używa parametru oa (offset anchors), wstawianego do URL. Każda strona pokazuje 30 wyników:
- Strona 1: bazowy URL (bez parametru
oa) - Strona 2:
Hotels-g187768-oa30-Italy-Hotels.html - Strona 3:
Hotels-g187768-oa60-Italy-Hotels.html
Dla stron z recenzjami parametr offsetu to or, z krokiem co 10:
- Strona 1:
Reviews-or0-Hotel_Name.html - Strona 2:
Reviews-or10-Hotel_Name.html
Aby pobrać recenzje we wszystkich językach, dodaj do adresu ?filterLang=ALL.
Wysyłanie żądań z realistycznymi nagłówkami
TripAdvisor bardzo skrupulatnie sprawdza nagłówki. Żądanie z domyślnymi nagłówkami Pythona jest blokowane od razu. Musisz upodobnić się do prawdziwej przeglądarki 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"Długość treści: {len(response.text)} znaków")
Kluczowy szczegół: TripAdvisor sprawdza, czy User-Agent i nagłówki Client Hints Sec-CH-UA są ze sobą spójne. Jeśli deklarujesz Chrome 135 w User-Agent, a w Sec-CH-UA masz Chrome 120, zostaniesz oznaczony jako podejrzany. Zawsze rotuj całe zestawy nagłówków razem, a nie pojedyncze nagłówki.
Parsowanie list z BeautifulSoup
Gdy masz już poprawną odpowiedź, wyciągnij dane za pomocą BeautifulSoup. TripAdvisor używa atrybutów data-automation i data-test-attribute, które są znacznie stabilniejsze niż nazwy klas CSS (zmieniają się bardzo często):
1from bs4 import BeautifulSoup
2soup = BeautifulSoup(response.text, "html.parser")
3# Znajdź wszystkie karty hoteli
4cards = soup.select('div[data-test-attribute="location-results-card"]')
5hotels = []
6for card in cards:
7 # Nazwa hotelu
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 do strony szczegółów
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 # Ocena
14 rating_el = card.select_one('[data-automation="bubbleRatingValue"]')
15 rating = rating_el.get_text(strip=True) if rating_el else None
16 # Liczba recenzji
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"Znaleziono {len(hotels)} hoteli na tej stronie")
26for h in hotels[:3]:
27 print(h)
Uwaga o selektorach: TripAdvisor używa zaciemnionych nazw klas CSS (np. FGwzt, yyzcQ), które zmieniają się przy każdej aktualizacji serwisu. Atrybuty data-automation i data-test-target są znacznie stabilniejsze. Zawsze wybieraj atrybuty danych zamiast nazw klas.
Obsługa paginacji
Aby scrapować wiele stron, przechodź przez parametr offsetu z uprzejmym opóźnieniem między żądaniami:
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): # pierwsze 5 stron
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"Strona {page + 1}: status {response.status_code}, przerywam.")
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"Strona {page + 1}: znaleziono {len(cards)} hoteli")
22 time.sleep(random.uniform(3, 7)) # losowe opóźnienie, by uniknąć rate limitu
23df = pd.DataFrame(all_hotels)
24print(f"\nŁącznie zeskrobano: {len(df)} hoteli")
time.sleep(random.uniform(3, 7)) jest ważne. Próg rate limitu TripAdvisor to mniej więcej 10–15 żądań na minutę na IP. Szybsze tempo uruchamia CAPTCHA albo błędy 429.
Ograniczenia tego podejścia
Kiedy ta metoda przestaje działać? Podejście requests+BS4 zawodzi, gdy:
- TripAdvisor serwuje treści renderowane po stronie JavaScript (niektóre strony wyników wymagają JS)
- Teksty recenzji są skrócone za przyciskiem „Read more”
- Mechanizmy anty-botowe eskalują do wyzwań JavaScript lub CAPTCHA
- Potrzebujesz danych dostępnych dopiero po renderowaniu po stronie klienta (ceny, dostępność)
W takich sytuacjach potrzebujesz albo Selenium (Podejście 2), albo metody ukrytego JSON (Podejście 3).
Podejście 2: scrapowanie TripAdvisor z Selenium (headless browser)
Selenium uruchamia prawdziwą przeglądarkę, więc potrafi renderować JavaScript, klikać przyciski, obsługiwać banery zgody na cookies i wchodzić w interakcję z dynamiczną treścią. Cena: jest około i zużywa 300–500 MB RAM na jedną instancję przeglądarki.
Konfiguracja Selenium z ustawieniami anty-detekcji
W domyślnej konfiguracji Selenium jest bardzo łatwo wykrywalne. Fingerprinting TripAdvisor od razu je rozpoznaje. Trzeba wyłączyć flagi automatyzacji:
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") # nowy tryb headless (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# Usuń właściwość webdriver z navigator
15driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
16 "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
17})
Czy to wystarczy dla TripAdvisor? Przy małej skali scrapowania (poniżej 50 stron) takie ustawienie zwykle działa, jeśli używasz proxy residential. Przy większych wolumenach może być potrzebny undetected-chromedriver albo nodriver — zabezpieczenia DataDome TripAdvisor analizują ponad 1000 sygnałów na żądanie, w tym fingerprinty TLS, których zwykłe Selenium nie potrafi ukryć.
Scrapowanie wyników wyszukiwania hoteli w Selenium
1import time
2import random
3url = "https://www.tripadvisor.com/Hotels-g187147-Paris_Ile_de_France-Hotels.html"
4driver.get(url)
5# Poczekaj, aż załadują się karty hoteli
6wait = WebDriverWait(driver, 15)
7wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div[data-test-attribute="location-results-card"]')))
8# Obsłuż popup zgody na cookies (jeśli się pojawi)
9try:
10 cookie_btn = driver.find_element(By.ID, "onetrust-accept-btn-handler")
11 cookie_btn.click()
12 time.sleep(1)
13except:
14 pass # brak popupu cookies
15# Pobierz dane hoteli
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"Zeskrobano {len(hotels)} hoteli")
33for h in hotels[:3]:
34 print(h)
U mnie ta strona zajmowała około 8 sekund, podczas gdy requests+BS4 poniżej 1 sekundy. Taka 8-krotna różnica bardzo szybko się kumuluje, gdy scrapujesz setki stron.
Rozwijanie „Read more” i pobieranie pełnych recenzji
Strony recenzji skracają długie opinie za przyciskiem „Read more”. Selenium może go kliknąć:
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# Kliknij wszystkie przyciski „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# Pobierz recenzje
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 # Ocena zakodowana w klasie, np. "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"Zeskrobano {len(reviews)} recenzji")
Dodawanie rotacji proxy do Selenium
Przy długotrwałym scrapowaniu potrzebujesz rotacji proxy. Ponieważ selenium-wire jest wycofany od stycznia 2024, użyj wbudowanej obsługi proxy w Chrome:
1# Dla proxy bez uwierzytelniania
2proxy = "http://your-proxy-address:port"
3options.add_argument(f"--proxy-server={proxy}")
4# Dla proxy z uwierzytelnianiem użyj rozszerzenia Chrome lub protokołu BiDi Selenium 4
Do programowej rotacji proxy twórz nową instancję drivera z innym proxy dla każdej partii żądań. Nie jest to eleganckie, ale działa niezawodnie.
Podejście 3: ukryty JSON (bez parsowania HTML)
Większość poradników całkowicie pomija to podejście, a szkoda — jest najszybsze i najbardziej eleganckie z całej trójki. TripAdvisor osadza ustrukturyzowane dane jako JSON bezpośrednio w kodzie stron — wewnątrz tagów <script> jako zmienne JavaScript, takie jak pageManifest i urqlCache. Pobranie tego JSON daje czystsze dane (oceny jako liczby, daty w formacie ISO), przy mniejszej liczbie żądań i bez potrzeby renderowania JavaScript.
Znajdowanie osadzonego JSON w kodzie strony
Kluczowe odkrycie: możesz użyć zwykłego requests.get(), aby pobrać stronę, a następnie wyciągnąć JSON z surowego HTML bez renderowania 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# Wyciągnij blob JSON pageManifest
16match = re.search(r"pageManifest:({.+?})};", response.text)
17if match:
18 page_data = json.loads(match.group(1))
19 print("Znaleziono dane pageManifest")
20 print(f"Klucze: {list(page_data.keys())[:10]}")
Jak samodzielnie znaleźć nazwę zmiennej: Otwórz dowolną stronę hotelu TripAdvisor w Chrome, kliknij prawym przyciskiem → Wyświetl źródło strony, a potem Ctrl+F i wyszukaj pageManifest, urqlCache albo aggregateRating. Dane tam są — czekają tylko na parsowanie.
Parsowanie JSON i ekstrakcja danych strukturalnych
TripAdvisor osadza też dane schema.org application/ld+json, które łatwo wyciągnąć:
1from parsel import Selector
2sel = Selector(text=response.text)
3# Pobierz dane strukturalne 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"Nazwa: {data.get('name')}")
9 print(f"Ocena: {data.get('aggregateRating', {}).get('ratingValue')}")
10 print(f"Liczba recenzji: {data.get('aggregateRating', {}).get('reviewCount')}")
11 print(f"Przedział cenowy: {data.get('priceRange')}")
12 print(f"Adres: {data.get('address', {}).get('streetAddress')}")
13 print(f"Współrzędne: {data.get('geo', {}).get('latitude')}, {data.get('geo', {}).get('longitude')}")
14 break
Dane JSON-LD są osadzone w statycznym HTML i nie wymagają renderowania JavaScript. Dają nazwę obiektu, ocenę zbiorczą, liczbę recenzji, adres, współrzędne, przedział cenowy i adresy URL zdjęć — bez parsowania ani jednego taga HTML.
Do bogatszych danych (pojedyncze recenzje, rozkład ocen, listy udogodnień) potrzebujesz obiektu urqlCache:
1# Wyciągnij urqlCache dla szczegółowych danych o recenzjach
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 # Przeszukaj cache w poszukiwaniu danych recenzji
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"Znaleziono wpis cache recenzji: {key[:50]}...")
11 break
Dokładne ścieżki JSON zmieniają się czasem, gdy TripAdvisor aktualizuje frontend, ale ogólna struktura — JSON-LD dla danych podsumowujących, urqlCache dla danych szczegółowych — pozostaje stabilna od lat.
Reverse engineering API GraphQL TripAdvisor (zaawansowane)
Do ekstrakcji na dużą skalę endpointy GraphQL TripAdvisor zwracają dane strukturalne bezpośrednio. To najszybsza metoda, ale wymaga największej ilości utrzymania.
1import httpx
2import random
3import string
4def generate_request_id():
5 """Wygeneruj wartość nagłówka X-Requested-By"""
6 random_chars = ''.join(random.choices(string.ascii_letters + string.digits, k=180))
7 return f"TNI1625!{random_chars}"
8# Wyszukiwanie hoteli w Paryżu
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"Żądanie GraphQL nie powiodło się: {response.status_code}")
Do pobierania recenzji przez 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"Łączna liczba recenzji: {total}")
28 for r in reviews[:3]:
29 print(f" [{r['rating']}/5] {r['title']} - {r['createdDate']}")
Ważne zastrzeżenie: Wartości preRegisteredQueryId (takie jak 84b17ed122fbdbd4 dla wyszukiwania i ef1a9f94012220d3 dla recenzji) mogą przestać działać, gdy TripAdvisor wdroży nową wersję. Wtedy żądania będą po cichu zawodzić. Trzeba ponownie odkryć identyfikatory zapytań, monitorując ruch sieciowy w DevTools przeglądarki.
Dlaczego ta metoda zmniejsza potrzebę użycia proxy
Matematyka jest prosta. W podejściu requests+BS4 scrapowanie 100 stron szczegółów hoteli wymaga 100 żądań. W metodzie ukrytego JSON każde żądanie zwraca wszystkie potrzebne dane z jednej strony — bez dodatkowych requestów do rozwijania recenzji czy ładowania dynamicznej zawartości. W GraphQL jedno wywołanie API może zwrócić od razu 20 recenzji. Mniej żądań = mniejsze narażenie na rate limiting = mniejsza potrzeba rotacji proxy. Przy małych i średnich projektach (poniżej 1000 stron) możesz w ogóle nie potrzebować proxy, jeśli dodasz rozsądne opóźnienia.
Scrapowanie hoteli, restauracji i atrakcji jednym wielokrotnego użytku skryptem
Cztery na pięć konkurencyjnych poradników omawiają tylko hotele. Tymczasem TripAdvisor ma trzy główne kategorie treści, a wzorce URL i pola danych różnią się między nimi. Oto jak zbudować jedną funkcję, która obsłuży wszystkie trzy.
Pola danych dostępne dla każdej kategorii
| Pole | Hotele | Restauracje | Atrakcje |
|---|---|---|---|
| Nazwa | ✅ | ✅ | ✅ |
| Ocena | ✅ | ✅ | ✅ |
| Liczba recenzji | ✅ | ✅ | ✅ |
| Cena / przedział cenowy | ✅ | ✅ | Czasami |
| Adres | ✅ | ✅ | ✅ |
| Typ kuchni | ❌ | ✅ | ❌ |
| Czas trwania / typ wycieczki | ❌ | ❌ | ✅ |
| Udogodnienia | ✅ | ❌ | ❌ |
| Współrzędne | ✅ | ✅ | ✅ |
Budowa wielokrotnego użytku funkcji scrape_tripadvisor()
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 Scrapuje wyniki TripAdvisor dla hoteli, restauracji lub atrakcji.
11 Args:
12 category: "hotels", "restaurants" lub "attractions"
13 location_id: geo ID TripAdvisor (np. "187147" dla Paryża)
14 location_name: nazwa przyjazna URL (np. "Paris_Ile_de_France")
15 num_pages: liczba stron do zeskrobania
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" Strona {page + 1}: status {response.status_code}, przerywam.")
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" Strona {page + 1}: znaleziono {len(cards)} elementów")
61 time.sleep(random.uniform(3, 7))
62 return pd.DataFrame(all_items)
63# Przykłady użycia
64print("=== Hotele w Paryżu ===")
65hotels_df = scrape_tripadvisor("hotels", "187147", "Paris_Ile_de_France", num_pages=2)
66print(hotels_df.head())
67print("\n=== Restauracje w Rzymie ===")
68restaurants_df = scrape_tripadvisor("restaurants", "187791", "Rome_Lazio", num_pages=2)
69print(restaurants_df.head())
70print("\n=== Atrakcje w Barcelonie ===")
71attractions_df = scrape_tripadvisor("attractions", "187497", "Barcelona_Catalonia", num_pages=2)
72print(attractions_df.head())
Jedna funkcja, trzy kategorie, zero powielania kodu. Jeśli TripAdvisor zmieni selektor, poprawiasz go w jednym miejscu.
Co zrobić, gdy TripAdvisor Cię blokuje (rozwiązywanie problemów z anty-botami)
To jest sekcja, której najbardziej potrzebowałem, gdy zaczynałem scrapować TripAdvisor, i której nie daje żaden konkurencyjny poradnik w uporządkowanej formie. TripAdvisor korzysta jednocześnie z DataDome (analizującego dziennie) i Cloudflare WAF. Oto tabela diagnostyczna najczęstszych problemów:
| Objaw | Prawdopodobna przyczyna | Naprawa |
|---|---|---|
| Odpowiedź HTTP 403 | Brakujące lub podejrzane nagłówki; wyzwanie JS od Cloudflare | Ustaw realistyczne nagłówki User-Agent, Accept-Language, Referer i Sec-CH-UA. Zadbaj o spójność nagłówków. |
| Strona CAPTCHA zamiast danych | Rate limiting albo fingerprinting przeglądarki | Rotuj residential proxy, dodaj losowe opóźnienia (2–7 sekund między żądaniami) |
| Pusty HTML albo pusty body strony | JavaScript nie został wyrenderowany przez requests | Przejdź na Selenium albo wyciągaj dane z ukrytego JSON w kodzie strony |
| Częściowe recenzje / „Read more” się nie rozwija | Treść ładowana po kliknięciu | Użyj Selenium z .click() albo pobierz dane z osadzonego bloku JSON |
| Recenzje tylko w jednym języku | Brak parametru języka | Dodaj ?filterLang=ALL do adresu recenzji |
| Dane przestają się ładować po N stronach | Limit oparty na sesji | Rotuj sesje, czyść ciasteczka między partiami |
| HTTP 1020 Access Denied | IP/ASN zablokowany przez Cloudflare | Zmień proxy z datacenter na residential |
| Pętla wyzwań (nieskończona CAPTCHA) | Uszkodzona trwałość cookies | Rozgrzej sesję, najpierw odwiedzając stronę główną; zachowuj cookie jar |
Logika ponawiania z wykładniczym backoffem
Żaden konkurencyjny artykuł tak naprawdę nie pokazuje tego kodu. Oto wielokrotnego użytku funkcja retry:
1import time
2import random
3import requests
4def fetch_with_retry(session, url, max_retries=4, base_delay=2, max_delay=60):
5 """
6 Pobiera URL z wykładniczym backoffem i jitterem.
7 Rotuje User-Agent przy każdej próbie.
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 # Rotuj User-Agent przy ponownej próbie
17 if attempt > 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 # Szanuj nagłówek Retry-After, jeśli istnieje
25 retry_after = int(response.headers.get("Retry-After", base_delay * (2 ** attempt)))
26 print(f" Rate limit (429). Czekam {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" Otrzymano {response.status_code}. Próba {attempt + 1}/{max_retries} za {wait:.1f}s...")
32 time.sleep(wait)
33 continue
34 # Inne kody błędów — bez ponawiania
35 print(f" Nieoczekiwany status {response.status_code} dla {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. Próba {attempt + 1}/{max_retries} za {wait:.1f}s...")
40 time.sleep(wait)
41 print(f" Wyczerpano wszystkie {max_retries} prób dla {url}")
42 return None
Rotowanie nagłówków, proxy i sesji
Przy długotrwałym scrapowaniu warto utrzymywać pulę zestawów nagłówków i rotować je razem:
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 # Dodaj więcej residential proxy
23]
24def get_rotated_session():
25 """Tworzy nową sesję z rotującymi nagłówkami i proxy."""
26 session = requests.Session()
27 # Wybierz losowy zestaw nagłówków
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 # Wybierz losowe proxy
41 if PROXY_LIST:
42 proxy = random.choice(PROXY_LIST)
43 session.proxies = {"http": proxy, "https": proxy}
44 return session
Typ proxy ma znaczenie. Proxy datacenter są blokowane przez TripAdvisor niemal natychmiast (HTTP 1020 Access Denied). Do długotrwałego scrapowania konieczne są residential proxy — przechodzą przez konsumenckich dostawców internetu i są nie do odróżnienia od prawdziwych użytkowników. Cena zwykle wynosi od $2,50 do $8,40/GB, w zależności od dostawcy.
Eksport i przechowywanie danych z TripAdvisor
Gdy dane są już zebrane, ich zapis do użytecznego formatu jest prosty.
Eksport CSV (najpopularniejszy)
1import pandas as pd
2df = pd.DataFrame(all_hotels)
3df.to_csv("tripadvisor_hotels_paris.csv", index=False, encoding="utf-8-sig")
4print(f"Wyeksportowano {len(df)} wierszy do CSV")
encoding='utf-8-sig' jest ważne — dzięki temu Excel poprawnie wyświetla znaki spoza alfabetu łacińskiego (akcenty francuskie, znaki chińskie itd.) przy otwieraniu pliku CSV.
Eksport JSON (dla danych zagnieżdżonych)
Gdy recenzje są zagnieżdżone pod hotelami, JSON zachowuje strukturę hierarchiczną:
1# Struktura hierarchiczna
2hotel_data = {
3 "property_id": "d194317",
4 "name": "NH City Centre Amsterdam",
5 "rating": 4.0,
6 "reviews": [
7 {"title": "Świetna lokalizacja", "rating": 5, "date": "2025-03-15", "text": "..."},
8 {"title": "Przeciętny pobyt", "rating": 3, "date": "2025-03-10", "text": "..."},
9 ]
10}
11# Do płaskiej analizy użyj 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)
Podejście z dwoma plikami dla danych relacyjnych
Przy dużych zbiorach danych używam dwóch plików CSV:
hotels.csv— jeden wiersz na obiekt (płasko)reviews.csv— jeden wiersz na recenzję, zproperty_idjako kluczem obcym
Ułatwia to łączenie w pandas, ładowanie do bazy danych i import do narzędzi BI.
Jeśli nie chcesz zajmować się logiką eksportu, Thunderbit pozwala do Excela, Google Sheets, Airtable lub Notion — wszystko za darmo i bez kodu. Bardzo przydatne, gdy musisz pokazać wyniki nietechnicznym członkom zespołu.
Wskazówki do odpowiedzialnego i wydajnego scrapowania TripAdvisor
Odpowiedzialne scrapowanie w sześciu punktach:
- Sprawdź
robots.txt: plik robots.txt TripAdvisor całkowicie blokuje boty do trenowania AI (GPTBot, ClaudeBot itd.). Standardowe crawlery mają selektywne ograniczenia ścieżek. Sprawdź go pod adresemtripadvisor.com/robots.txt. - Dodaj opóźnienia: 3–7 sekund między żądaniami to bezpieczny zakres. Szybsze tempo niż 10–15 żądań na minutę na IP uruchamia rate limiting.
- Scrapuj tylko dane publiczne. Nie loguj się, aby uzyskiwać dostęp do treści z ograniczeniami.
- Przechowuj dane bezpiecznie i przestrzegaj GDPR/CCPA, jeśli przetwarzasz dane osobowe (np. imiona recenzentów).
- Rozważ oficjalne API TripAdvisor, jeśli potrzebujesz danych na skalę komercyjną. oferuje dostęp do informacji o firmach oraz do 5 recenzji i 5 zdjęć na lokalizację — ograniczone, ale legalne i stabilne.
- Miej świadomość kontekstu prawnego: wyrok wzmocnił zakazy scrapowania oparte na ToS w całej UE. Regulamin TripAdvisor wyraźnie zabrania scrapowania. Działaj odpowiedzialnie i na własne ryzyko.
Podsumowanie
To pełny obraz sytuacji.
- Requests + BeautifulSoup to najprostsza droga. Działa dla statycznych stron z listami, wymaga minimalnej konfiguracji i jest szybka. Zacznij tutaj, jeśli scrapujesz mniej niż 100 stron i nie potrzebujesz treści renderowanych przez JavaScript.
- Selenium poradzi sobie ze wszystkim, czego nie obsłuży requests: treści dynamiczne, przyciski „Read more”, banery cookies. Jest 5 razy wolniejsze i bardziej zasobożerne, ale to jedyna opcja, gdy musisz wchodzić w interakcję ze stroną.
- Ukryty JSON / GraphQL to najbardziej eleganckie i najszybsze podejście. Daje ustrukturyzowane dane bez parsowania HTML, zmniejsza liczbę żądań (a więc i potrzebę proxy) oraz zwraca dane gotowe do analizy. Wymaga większego reverse engineeringu na starcie i okazjonalnej konserwacji, gdy TripAdvisor zmienia strukturę danych.
Wielokrotnego użytku funkcja scrape_tripadvisor() obsługuje hotele, restauracje i atrakcje. Nie powinieneś potrzebować drugiego tutorialu.
A jeśli w połowie poradnika dojdziesz do wniosku, że kodowanie jednak nie jest dla Ciebie — albo po prostu potrzebujesz 50 hoteli w arkuszu do końca dnia — zrobi to w dwóch kliknięciach, z wykrywaniem pól wspieranym przez AI, automatyczną paginacją i darmowym eksportem do Excela lub Google Sheets. Bez Pythona.
Jeśli chcesz wejść głębiej, mamy więcej poradników na i na naszym .
FAQ
1. Czy scrapowanie TripAdvisor jest legalne?
Regulamin TripAdvisor wyraźnie zabrania scrapowania. Jednak sądy generalnie uznawały, że pobieranie publicznie dostępnych danych (nieza loginem) nie narusza w USA Computer Fraud and Abuse Act. Mimo to wyrok sądu UE w sprawie Ryanair z 2025 roku zaostrzył ograniczenia oparte na ToS w Europie. Scrapuj wyłącznie dane publiczne, respektuj robots.txt, nie publikuj ponownie treści chronionych prawem autorskim i skonsultuj się z prawnikiem, jeśli używasz danych komercyjnie.
2. Czy mogę scrapować TripAdvisor bez Pythona?
Tak. Narzędzia no-code, takie jak , mogą scrapować TripAdvisor bezpośrednio z przeglądarki, z wykrywaniem pól przez AI i automatyczną paginacją. Możesz też używać rozszerzeń do przeglądarki, dodatków do Google Sheets albo komercyjnych API do scrapowania. Python daje największą kontrolę i elastyczność, ale nie jest jedyną opcją.
3. Jak uniknąć blokady podczas scrapowania TripAdvisor?
Kluczowe techniki: używaj realistycznych i spójnych nagłówków (zwłaszcza User-Agent i Sec-CH-UA), rotuj residential proxy (IP z datacenter są blokowane natychmiast), dodawaj losowe opóźnienia 3–7 sekund między żądaniami, korzystaj z metody ukrytego JSON, aby ograniczyć liczbę żądań, wdrażaj retry z wykładniczym backoffem i „rozgrzewaj” sesję przez wejście na stronę główną przed scrapowaniem głębszych podstron.
4. Jakie dane mogę zeskrobać z TripAdvisor?
Hotele, restauracje i atrakcje — w tym nazwy, oceny, liczbę recenzji, przedziały cenowe, adresy, współrzędne, udogodnienia (hotele), typy kuchni (restauracje), czas trwania wycieczki (atrakcje) oraz pełne treści recenzji z indywidualnymi ocenami i datami. Podejścia oparte na ukrytym JSON i GraphQL zwracają najbogatsze dane na jedno żądanie.
5. Ile stron dziennie mogę zeskrobać z TripAdvisor?
Przy jednym IP i rozsądnych opóźnieniach: około 600–1000 stron dziennie. Przy 20 rotujących residential proxy: mniej więcej 200 000–300 000 stron dziennie przy podejściu opartym na requestach. Selenium jest wolniejsze — spodziewaj się 8000–12 000 stron dziennie na jedno proxy. Podejście ukrytego JSON/GraphQL daje najwięcej danych na żądanie, więc do uzyskania tej samej ilości informacji możesz potrzebować znacznie mniej stron.
Dowiedz się więcej