Förra veckan försökte jag hämta hotellbetyg och antal recensioner för ungefär 200 boenden i tre europeiska städer från TripAdvisor. Mitt första skript — en enkel requests.get() med standardheaders — möttes av en elegant 403 Forbidden på varje enda förfrågan. Inte en enda användbar byte data.
TripAdvisor är en av resebranschens mest värdefulla offentliga datakällor: över , mer än 8 miljoner företagslistningar och omkring 460 miljoner unika besökare per månad. Plattformen påverkar mer än i årlig reseförbrukning. Men att få ut den datan programmatiskt? Där blir det snabbt knepigt. TripAdvisor använder DataDome för botdetektering, Cloudflare WAF, TLS-fingerprinting och JavaScript-utmaningar — ett skydd i flera lager som stoppar de flesta naiva scrapingförsök redan innan de hinner igång. Den här guiden är den enda resurs jag önskar att jag hade haft: en jämförelse sida vid sida av tre Python-metoder för scraping (plus ett no-code-alternativ), komplett kod för varje metod, en strukturerad felsökningssektion för anti-bot-skydd och återanvändbara mönster som funkar för hotell, restauranger och sevärdheter. Oavsett om du är nybörjare i Python eller en erfaren utvecklare borde det här spara dig massor av bortkastade 403:or.
Vill du inte skriva kod? Skrapa TripAdvisor på det enkla sättet
Jag vill vara helt tydlig med en sak. Många som söker efter "scrape TripAdvisor with Python" är egentligen inte särskilt sugna på att skriva kod. De vill bara ha datan — hotellnamn, betyg, antal recensioner, priser — i ett kalkylblad, snabbt. Om det låter som du finns det en mycket kortare väg.
är ett AI-drivet Chrome-tillägg som vi byggt för att kunna läsa vilken TripAdvisor-sida som helst och automatiskt föreslå rätt kolumner att extrahera. Arbetsflödet är verkligen bara två klick:
- Öppna en TripAdvisor-listningssida (t.ex. sökresultat för "Hotels in Paris").
- Klicka på "AI Suggest Fields" i Thunderbits sidofält. AI:n skannar sidan och föreslår kolumner som Hotel Name, Rating, Review Count, Price och Location.
- Klicka på "Scrape." Thunderbit hämtar data från alla listningar på sidan — och hanterar sidnumrering automatiskt om du behöver fler resultat.
- Exportera till Excel, Google Sheets, Airtable eller Notion. Export är gratis i alla planer.
Thunderbit funkar för hotell, restauranger och sevärdheter utan att du behöver ändra någon konfiguration — AI:n anpassar sig efter innehållet på sidan. För paginerade resultat känner den själv av "Next"-knappar och oändlig scroll. Och eftersom det körs i din riktiga Chrome-webbläsare använder det dina sessionscookies och ditt webbläsarfingeravtryck, vilket ger en naturlig fördel mot botdetektering.
Du kan testa det med — gratisnivån ger dig 6 sidor/månad, vilket räcker för att prova flödet.
Om du behöver programmatisk kontroll, egen parslogik eller planerar att skrapa 10 000+ sidor, då är Python rätt väg. Fortsätt läsa.
Varför skrapa TripAdvisor med Python?
TripAdvisor-data har direkt och mätbar affärsnytta. En visade att en ökning med 1 poäng i ett hotells Global Review Index på 100-poängsskalan leder till en 0,89 % ökning av genomsnittligt dagspris och en 1,42 % ökning av Revenue Per Available Room. En annan visade att en exogen ökning på 1 stjärna i TripAdvisor-betyg motsvarar 55 000–75 000 dollar i extra årlig intäkt för ett genomsnittligt hotell. Recensioner är alltså inte bara fåfängemått — de driver intäkter.
Så här använder olika team TripAdvisor-data:
| Användningsområde | Vem får nytta | Vilken data behövs |
|---|---|---|
| Konkurrentanalys för hotell | Hotellkedjor, intäktsansvariga | Betyg, priser, antal recensioner, bekvämligheter |
| Marknadsresearch för restauranger | Restauranggrupper, livsmedelsvarumärken | Kökstyp, prisnivåer, sentiment i recensioner |
| Spårning av trender för sevärdheter | Researrangörer, turistorganisationer | Popularitetsrankning, säsongsmönster |
| Sentimentanalys | Forskare, dataanalytiker | Fullständig recensionstext, stjärnbetyg, datum |
| Leadgenerering | Säljteam, resebyråer | Företagsnamn, kontaktuppgifter, platser |
Varför just Python? Tre skäl. För det första ekosystemet: BeautifulSoup, Selenium, Playwright, Scrapy, httpx, pandas — Python har mer mogna bibliotek för scraping och dataanalys än något annat språk. För det andra använder Python, vilket betyder mer community-stöd, fler Stack Overflow-svar och fler uppdaterade guider. För det tredje pipeline-fördelen: du kan skrapa med BeautifulSoup, städa med pandas, köra sentimentanalys med Hugging Face Transformers och bygga dashboards — allt i ett och samma språk. Ingen kontextväxling.
Tre sätt att skrapa TripAdvisor med Python (jämförelse)
Varje konkurrerande guide väljer en metod och kör på. Det hjälper inte när du försöker välja innan du skriver kod. Här är jämförelsetabellen jag önskar att någon hade gett mig:
| Metod | Hastighet | JS-stöd | Motståndskraft mot botsskydd | Komplexitet | Bäst för |
|---|---|---|---|---|---|
requests + BeautifulSoup | ⚡ Snabb (~120–200 sidor/min rått) | ❌ Inget | ⚠️ Låg | Enkel | Statisk listningssidor, små projekt |
| Selenium / huvudlös webbläsare | 🐢 Långsam (~8–20 sidor/min) | ✅ Fullt | ⚠️ Medel | Medel | Dynamiskt innehåll, klick på "Read more", cookie-banners |
| Dold JSON / GraphQL API | ⚡⚡ Snabbast (~200–600 sidor/min rått) | N/A | ✅ Högre | Svår | Storskalig extrahering av recensioner/hotell |
| No-code (Thunderbit) | ⚡ Snabb | ✅ Inbyggt | ✅ Inbyggt | Enklast | Icke-utvecklare, snabba engångsexporter |
Några viktiga brasklappar. De råa hastigheterna är teoretiska — TripAdvisors rate limits (~10–15 förfrågningar per minut per IP) begränsar den faktiska genomströmningen till ungefär 10 sidor/minut per IP oavsett metod. Den dolda JSON-metoden ger mest data per förfrågan, vilket betyder färre totala anrop och mindre exponering för rate limiting. Selenium är 5x långsammare än request-baserade metoder i praktiska tester, men det är det enda alternativet när du behöver klicka på knappar eller rendera JavaScript.
Resten av guiden går igenom alla tre Python-metoder med komplett kod. Välj den som passar din situation, eller kombinera dem (jag använder ofta requests+BS4 för listningssidor och dold JSON för detaljsidor).
Så sätter du upp din Python-miljö
Innan vi kör igång, låt oss förbereda miljön. Du behöver Python 3.10+ (jag rekommenderar 3.12 eller 3.13 — alla större paket stöder dem utan kända problem).
Installera allt på en gång:
1pip install requests beautifulsoup4 selenium httpx parsel pandas curl-cffi
Paketnoteringar:
requests(2.33.1) — HTTP-förfrågningar, kräver Python 3.10+beautifulsoup4(4.14.3) — HTML-parsningselenium(4.43.0) — Webbläsarautomatisering, kräver Python 3.10+httpx(0.28.1) — Asynkron HTTP-klientparsel(1.11.0) — CSS/XPath-selektorer (lättare än BS4)pandas(3.0.2) — Export av data, kräver Python 3.11+curl_cffi(0.15.0) — TLS-fingerprint imitation (viktigt för att kringgå Cloudflare)
ChromeDriver: Om du använder Selenium är det goda nyheter — sedan Selenium 4.6 laddar Selenium Manager automatiskt ner och cachelagrar rätt ChromeDriver-binär. Ingen manuell installation behövs. Den matchar versioner dynamiskt, så du slipper bekymra dig om att Chrome-versionen inte stämmer.
Virtuell miljö (rekommenderas):
1python -m venv tripadvisor-scraper
2source tripadvisor-scraper/bin/activate # macOS/Linux
3tripadvisor-scraper\Scripts\activate # Windows
Metod 1: Skrapa TripAdvisor med Requests och BeautifulSoup
Det här är den enklaste metoden. Den fungerar bra för att skrapa listningssidor (hotellsökresultat, restauranglistor) där datan du behöver finns i den statiska HTML-koden. Ingen webbläsare, ingen JavaScript-rendering, minimal resursanvändning.
Förstå TripAdvisors URL-mönster
TripAdvisors URL:er följer förutsägbara mönster per kategori:
- Hotell:
https://www.tripadvisor.com/Hotels-g{locationId}-{Location_Name}-Hotels.html - Restauranger:
https://www.tripadvisor.com/Restaurants-g{locationId}-{Location_Name}.html - Sevärdheter:
https://www.tripadvisor.com/Attractions-g{locationId}-Activities-{Location_Name}.html
Sidnumreringen använder parametern oa (offset anchors), som läggs in i URL:en. Varje sida visar 30 resultat:
- Sida 1: bas-URL (ingen
oa-parameter) - Sida 2:
Hotels-g187768-oa30-Italy-Hotels.html - Sida 3:
Hotels-g187768-oa60-Italy-Hotels.html
För recensionssidor är offset-parametern or med steg om 10:
- Sida 1:
Reviews-or0-Hotel_Name.html - Sida 2:
Reviews-or10-Hotel_Name.html
För att få recensioner på alla språk, lägg till ?filterLang=ALL i URL:en.
Skicka förfrågningar med realistiska headers
TripAdvisor kontrollerar headers aggressivt. En förfrågan med standardheaders från Python blockeras direkt. Du behöver imitera en riktig Chrome-webbläsare:
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")
Viktig detalj: TripAdvisor verifierar att dina User-Agent- och Sec-CH-UA-headers hänger ihop. Om du påstår att du är Chrome 135 i User-Agent men Sec-CH-UA säger Chrome 120, blir du flaggad. Rotera alltid hela headeruppsättningar tillsammans, inte enskilda headers.
Parsning av listningar med BeautifulSoup
När du väl fått ett lyckat svar extraherar du datan med BeautifulSoup. TripAdvisor använder attribut som data-automation och data-test-attribute, och de är stabilare än CSS-klassnamn (som ändras ofta):
1from bs4 import BeautifulSoup
2soup = BeautifulSoup(response.text, "html.parser")
3# Hitta alla hotellkort
4cards = soup.select('div[data-test-attribute="location-results-card"]')
5hotels = []
6for card in cards:
7 # Hotellnamn
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 # Länk till detaljsida
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 # Betyg
14 rating_el = card.select_one('[data-automation="bubbleRatingValue"]')
15 rating = rating_el.get_text(strip=True) if rating_el else None
16 # Antal recensioner
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"Found {len(hotels)} hotels on this page")
26for h in hotels[:3]:
27 print(h)
Om selektorer: TripAdvisor använder obfuskerade CSS-klassnamn (som FGwzt, yyzcQ) som ändras vid varje uppdatering av sajten. Attributen data-automation och data-test-target är mycket stabilare. Föredra alltid dataattribut framför klassnamn.
Hantera sidnumrering
För att skrapa flera sidor loopar du över offset-parametern med en artig fördröjning mellan förfrågningarna:
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): # Första 5 sidorna
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"Page {page + 1}: Got status {response.status_code}, stopping.")
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"Page {page + 1}: {len(cards)} hotels found")
22 time.sleep(random.uniform(3, 7)) # Slumpmässig paus för att undvika rate limiting
23df = pd.DataFrame(all_hotels)
24print(f"\nTotal hotels scraped: {len(df)}")
time.sleep(random.uniform(3, 7)) är viktigt. TripAdvisors rate limit ligger ungefär på 10–15 förfrågningar per minut per IP. Kör du snabbare än så triggar du CAPTCHA eller 429-fel.
Begränsningar med denna metod
När faller den här lösningen isär? Requests+BS4-metoden fungerar inte när:
- TripAdvisor levererar JavaScript-renderat innehåll (vissa sökresultatsidor kräver JS)
- Recensionstexten ligger bakom "Read more"-knappar
- Anti-bot-skyddet eskalerar till JavaScript-utmaningar eller CAPTCHA
- Du behöver data som bara visas efter klient-side-rendering (priser, tillgänglighet)
För de här scenarierna behöver du antingen Selenium (Metod 2) eller den dolda JSON-metoden (Metod 3).
Metod 2: Skrapa TripAdvisor med Selenium (huvudlös webbläsare)
Selenium startar en riktig webbläsare, vilket betyder att den kan rendera JavaScript, klicka på knappar, hantera cookie-samtycken och interagera med dynamiskt innehåll. Priset: den är ungefär och använder 300–500 MB RAM per webbläsarinstans.
Konfigurera Selenium med anti-detektion
Som standard är Selenium lätt att upptäcka. TripAdvisors fingerprinting fångar det direkt. Du behöver stänga av automation-flaggor:
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") # Använd nytt headless-läge (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# Ta bort webdriver-egenskapen från navigator
15driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
16 "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
17})
Räcker detta för TripAdvisor? För små volymer (under 50 sidor) fungerar den här setupen ofta tillsammans med residential proxies. För större volymer kan du behöva undetected-chromedriver eller nodriver — TripAdvisors DataDome-skydd analyserar över 1 000 signaler per förfrågan, inklusive TLS-fingerprints som vanlig Selenium inte kan förfalska.
Skrapa hotellresultat med Selenium
1import time
2import random
3url = "https://www.tripadvisor.com/Hotels-g187147-Paris_Ile_de_France-Hotels.html"
4driver.get(url)
5# Vänta på att hotellkorten ska laddas
6wait = WebDriverWait(driver, 15)
7wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div[data-test-attribute="location-results-card"]')))
8# Hantera cookie-popupen (om den visas)
9try:
10 cookie_btn = driver.find_element(By.ID, "onetrust-accept-btn-handler")
11 cookie_btn.click()
12 time.sleep(1)
13except:
14 pass # Ingen cookie-popup
15# Extrahera hotelldata
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"Scraped {len(hotels)} hotels")
33for h in hotels[:3]:
34 print(h)
Det här tog ungefär 8 sekunder för en enda sida på min dator — jämfört med under 1 sekund med requests+BS4. Den där 8x skillnaden växer snabbt när du skrapar hundratals sidor.
Expandera "Read more" och skrapa fullständiga recensioner
Recensionssidor kapar långa recensioner bakom en "Read more"-knapp. Selenium kan klicka på den:
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# Klicka på alla "Read more"-knappar
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# Extrahera recensioner
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 # Betyg kodas i klassnamn som "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"Scraped {len(reviews)} reviews")
Lägg till proxyrotation i Selenium
För långvarig scraping behöver du proxyrotation. Eftersom selenium-wire har varit avvecklat sedan januari 2024 bör du använda Chromes inbyggda proxy-stöd:
1# Med proxy utan autentisering
2proxy = "http://your-proxy-address:port"
3options.add_argument(f"--proxy-server={proxy}")
4# För proxies med autentisering, använd ett Chrome-tillägg eller Selenium 4:s BiDi-protokoll
För att rotera proxies programmatiskt skapar du en ny drivrutin med en annan proxy för varje batch av förfrågningar. Det är inte elegant, men det är pålitligt.
Metod 3: Den dolda JSON-metoden (hoppa över HTML-parsning helt)
De flesta guider hoppar helt över den här metoden, vilket är synd — det är den snabbaste och renaste av de tre. TripAdvisor bäddar in strukturerad data som JSON direkt i sina HTML-sidor — i <script>-taggar som JavaScript-variabler som pageManifest och urqlCache. Genom att extrahera den här JSON:en får du renare data (betyg som siffror, datum i ISO-format) med färre anrop och utan att behöva rendera JavaScript.
Hitta den inbäddade JSON:en i sidkällan
Nyckelinsikten: du kan använda en enkel requests.get() för att hämta sidan och sedan extrahera JSON från den råa HTML-koden utan att någonsin rendera 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# Extrahera JSON-blocket pageManifest
16match = re.search(r"pageManifest:({.+?})};", response.text)
17if match:
18 page_data = json.loads(match.group(1))
19 print("Found pageManifest data")
20 print(f"Keys: {list(page_data.keys())[:10]}")
Så hittar du variabelnamnet själv: Öppna valfri hotellsida i TripAdvisor i Chrome, högerklicka → Visa sidkälla, och sök med Ctrl+F efter pageManifest eller urqlCache eller aggregateRating. Datan finns där, redo att parsas.
Parsning av JSON och extrahering av strukturerad data
TripAdvisor bäddar också in schema.org-data i application/ld+json, och den är ännu enklare att extrahera:
1from parsel import Selector
2sel = Selector(text=response.text)
3# Extrahera strukturerad JSON-LD-data
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"Name: {data.get('name')}")
9 print(f"Rating: {data.get('aggregateRating', {}).get('ratingValue')}")
10 print(f"Review Count: {data.get('aggregateRating', {}).get('reviewCount')}")
11 print(f"Price Range: {data.get('priceRange')}")
12 print(f"Address: {data.get('address', {}).get('streetAddress')}")
13 print(f"Coordinates: {data.get('geo', {}).get('latitude')}, {data.get('geo', {}).get('longitude')}")
14 break
JSON-LD-datan är inbäddad i den statiska HTML-koden och kräver INTE JavaScript-rendering. Den ger dig namn på objektet, sammanlagt betyg, antal recensioner, adress, koordinater, prisintervall och bild-URL:er — allt utan att du behöver parsa en enda HTML-tagg.
För rikare data (enskilda recensioner, betygsfördelning, lista över bekvämligheter) behöver du objektet urqlCache:
1# Extrahera urqlCache för detaljerad recensionsdata
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 # Navigera i cachen för att hitta recensionsdata
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"Found review cache entry: {key[:50]}...")
11 break
De exakta JSON-sökvägarna ändras ibland när TripAdvisor uppdaterar frontend, men den övergripande strukturen — JSON-LD för sammanfattad data, urqlCache för detaljerad data — har varit stabil i flera år.
Reverse engineering av TripAdvisors GraphQL-API (avancerat)
För storskalig extraktion returnerar TripAdvisors GraphQL-endpoints strukturerad data direkt. Det här är den snabbaste metoden men också den som kräver mest underhåll.
1import httpx
2import random
3import string
4def generate_request_id():
5 """Generate the X-Requested-By header value"""
6 random_chars = ''.join(random.choices(string.ascii_letters + string.digits, k=180))
7 return f"TNI1625!{random_chars}"
8# Sök efter hotell i Paris
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"GraphQL request failed: {response.status_code}")
För att hämta recensioner 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"Total reviews: {total}")
28 for r in reviews[:3]:
29 print(f" [{r['rating']}/5] {r['title']} - {r['createdDate']}")
Viktig brasklapp: preRegisteredQueryId-värdena (som 84b17ed122fbdbd4 för sök och ef1a9f94012220d3 för recensioner) kan sluta fungera när TripAdvisor deployar om. När det händer misslyckas dina anrop tyst. Då behöver du hitta om query-ID:n genom att övervaka nätverksanrop i webbläsarens DevTools.
Varför den här metoden minskar behovet av proxies
Matematiken är enkel. Med requests+BS4 kräver skrapning av 100 hotell-detaljsidor 100 förfrågningar. Med den dolda JSON-metoden returnerar varje förfrågan all data du behöver från en enda sidladdning — inga extra anrop för att expandera recensioner eller ladda dynamiskt innehåll. Med GraphQL kan ett enda API-anrop returnera 20 recensioner samtidigt. Färre förfrågningar = mindre exponering för rate limiting = mindre behov av proxyrotation. För små till medelstora projekt (under 1 000 sidor) kanske du inte behöver proxies alls om du lägger in vettiga pauser.
Skrapa hotell, restauranger och sevärdheter med ett återanvändbart skript
Fyra av fem konkurrerande guider täcker bara hotell. Men TripAdvisor har tre huvudkategorier av innehåll, och URL-mönstren och fälten skiljer sig mellan dem. Så här bygger du en funktion som klarar alla tre.
Tillgängliga datafält per kategori
| Fält | Hotell | Restauranger | Sevärdheter |
|---|---|---|---|
| Namn | ✅ | ✅ | ✅ |
| Betyg | ✅ | ✅ | ✅ |
| Antal recensioner | ✅ | ✅ | ✅ |
| Pris/prisintervall | ✅ | ✅ | Ibland |
| Adress | ✅ | ✅ | ✅ |
| Kökstyp | ❌ | ✅ | ❌ |
| Varaktighet/turtyp | ❌ | ❌ | ✅ |
| Bekvämligheter | ✅ | ❌ | ❌ |
| Koordinater | ✅ | ✅ | ✅ |
Bygga en återanvändbar scrape_tripadvisor()-funktion
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 Skrapa TripAdvisor-listningar för hotell, restauranger eller sevärdheter.
11 Args:
12 category: "hotels", "restaurants" eller "attractions"
13 location_id: TripAdvisors geo-ID (t.ex. "187147" för Paris)
14 location_name: URL-vänligt namn (t.ex. "Paris_Ile_de_France")
15 num_pages: Antal sidor att skrapa
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" Page {page + 1}: Status {response.status_code}, stopping.")
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" Page {page + 1}: {len(cards)} items found")
61 time.sleep(random.uniform(3, 7))
62 return pd.DataFrame(all_items)
63# Exempel på användning
64print("=== Hotell i Paris ===")
65hotels_df = scrape_tripadvisor("hotels", "187147", "Paris_Ile_de_France", num_pages=2)
66print(hotels_df.head())
67print("\n=== Restauranger i Rom ===")
68restaurants_df = scrape_tripadvisor("restaurants", "187791", "Rome_Lazio", num_pages=2)
69print(restaurants_df.head())
70print("\n=== Sevärdheter i Barcelona ===")
71attractions_df = scrape_tripadvisor("attractions", "187497", "Barcelona_Catalonia", num_pages=2)
72print(attractions_df.head())
En funktion, tre kategorier, noll kodduplicering. Om TripAdvisor ändrar en selektor fixar du det på ett ställe.
Vad du ska göra när TripAdvisor blockerar dig (anti-bot-felsökning)
Det här är den sektion jag själv behövde mest när jag började skrapa TripAdvisor, och det är också den sektion som ingen konkurrerande guide presenterar på ett strukturerat sätt. TripAdvisor använder DataDome (som analyserar per dag) tillsammans med Cloudflare WAF. Här är en diagnos-tabell för de vanligaste felen:
| Symptom | Trolig orsak | Åtgärd |
|---|---|---|
| HTTP 403-svar | Saknade eller misstänkta headers; Cloudflare JS-utmaning | Sätt realistiska User-Agent, Accept-Language, Referer och Sec-CH-UA-headers. Se till att de är konsekventa. |
| CAPTCHA-sida istället för data | Rate limiting eller webbläsarfingeravtryck | Rotera residential proxies, lägg in slumpmässiga pauser (2–7 sekunder mellan förfrågningar) |
| Tom HTML eller blank sidkropp | JavaScript renderas inte av requests | Byt till Selenium eller extrahera från dold JSON i sidkällan |
| Delvisa recensioner / "Read more" expanderar inte | Innehåll laddas vid klickhändelse | Använd Seleniums .click() eller extrahera från inbäddat JSON-block |
| Recensioner bara på ett språk | Saknar språkparameter | Lägg till ?filterLang=ALL i recensions-URL:en |
| Datan slutar laddas efter N sidor | Sessionsbaserad rate limit | Rotera sessioner, rensa cookies mellan batcher |
| HTTP 1020 Access Denied | IP/ASN blockerat av Cloudflare | Byt från datacenter-proxy till residential proxies |
| Oändlig challenge loop (CAPTCHA) | Trasig cookie-persistens | Värm upp sessioner genom att besöka startsidan först; behåll cookie jar |
Retry-logik med exponentiell backoff
Ingen konkurrerande artikel visar faktiskt den här koden. Här är en återanvändbar retry-funktion:
1import time
2import random
3import requests
4def fetch_with_retry(session, url, max_retries=4, base_delay=2, max_delay=60):
5 """
6 Hämta en URL med exponentiell backoff och jitter.
7 Rotera User-Agent vid varje 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 # Rotera User-Agent vid retry
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 # Respektera Retry-After om den finns
25 retry_after = int(response.headers.get("Retry-After", base_delay * (2 ** attempt)))
26 print(f" Rate limited (429). Waiting {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" Got {response.status_code}. Retry {attempt + 1}/{max_retries} in {wait:.1f}s...")
32 time.sleep(wait)
33 continue
34 # Andra felkoder — försök inte igen
35 print(f" Unexpected status {response.status_code} for {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} in {wait:.1f}s...")
40 time.sleep(wait)
41 print(f" All {max_retries} retries exhausted for {url}")
42 return None
Rotera headers, proxies och sessioner
För uthållig scraping bör du ha en pool av header-set och rotera dem tillsammans:
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 # Lägg till fler residential proxies
23]
24def get_rotated_session():
25 """Skapa en ny session med roterade headers och proxy."""
26 session = requests.Session()
27 # Välj ett slumpmässigt header-set
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 # Välj en slumpmässig proxy
41 if PROXY_LIST:
42 proxy = random.choice(PROXY_LIST)
43 session.proxies = {"http": proxy, "https": proxy}
44 return session
Proxytypen spelar roll. Datacenter-proxies blockeras nästan omedelbart av TripAdvisor (HTTP 1020 Access Denied). Residential proxies är i praktiken nödvändiga för uthållig scraping — de går via konsument-ISPer och är svåra att skilja från riktiga användare. Räkna med att betala 2,50–8,40 USD/GB beroende på leverantör.
Exportera och lagra din TripAdvisor-data
När du väl har datan är det enkelt att få in den i ett användbart format.
CSV-export (vanligast)
1import pandas as pd
2df = pd.DataFrame(all_hotels)
3df.to_csv("tripadvisor_hotels_paris.csv", index=False, encoding="utf-8-sig")
4print(f"Exported {len(df)} rows to CSV")
encoding='utf-8-sig' är viktigt — det gör att Excel visar icke-latinska tecken korrekt (franska accenter, kinesiska tecken etc.) när CSV-filen öppnas.
JSON-export (för nästlad data)
När du har recensioner nästlade under hotell bevarar JSON hierarkin:
1# Hierarkisk struktur
2hotel_data = {
3 "property_id": "d194317",
4 "name": "NH City Centre Amsterdam",
5 "rating": 4.0,
6 "reviews": [
7 {"title": "Great location", "rating": 5, "date": "2025-03-15", "text": "..."},
8 {"title": "Average stay", "rating": 3, "date": "2025-03-10", "text": "..."},
9 ]
10}
11# För platt analys, använd 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)
Tvåfilsmetoden för relationsdata
För stora datamängder använder jag två CSV-filer:
hotels.csv— En rad per objekt (platt)reviews.csv— En rad per recension, medproperty_idsom främmande nyckel
Det gör det enkelt att slå ihop i pandas, ladda in i en databas eller importera i BI-verktyg.
Om du inte vill hantera exportlogiken själv låter Thunderbit dig till Excel, Google Sheets, Airtable eller Notion — helt gratis och utan kod. Praktiskt när du behöver dela resultat med kollegor som inte är tekniska.
Tips för ansvarsfull och effektiv TripAdvisor-scraping
Ansvarsfull scraping i sex punkter:
- Kontrollera
robots.txt: TripAdvisors robots.txt blockerar AI-träningsbots som GPTBot, ClaudeBot m.fl. helt. Vanliga crawlers möter selektiva begränsningar på vissa sökvägar. Läs den påtripadvisor.com/robots.txt. - Lägg in pauser: 3–7 sekunder mellan förfrågningar är ett säkert intervall. Kör du snabbare än 10–15 förfrågningar per minut per IP triggas rate limiting.
- Skrapa bara offentlig data. Logga inte in för att komma åt begränsat innehåll.
- Lagra data säkert och följ GDPR/CCPA om du hanterar personuppgifter (t.ex. recensionsförfattares namn).
- Överväg TripAdvisors officiella API om du behöver data i kommersiell skala. ger tillgång till företagsuppgifter plus upp till 5 recensioner och 5 bilder per plats — begränsat, men lagligt och stabilt.
- Var medveten om den juridiska kontexten: skärpte förbud kopplade till användarvillkor för scraping inom EU. TripAdvisors användarvillkor förbjuder uttryckligen scraping. Skrapa ansvarsfullt och på egen risk.
Avslutning
Det är hela bilden.
- Requests + BeautifulSoup är den enklaste vägen. Den funkar för statiska listningssidor, kräver minimal setup och går snabbt. Börja här om du skrapar färre än 100 sidor och inte behöver JavaScript-renderat innehåll.
- Selenium klarar allt som requests inte kan: dynamiskt innehåll, "Read more"-knappar, cookie-banners. Det är 5x långsammare och mer resurskrävande, men det är det enda alternativet när du behöver interagera med sidan.
- Dold JSON / GraphQL är den renaste och snabbaste metoden. Den ger dig strukturerad data utan HTML-parsning, minskar antalet förfrågningar (och därmed behovet av proxies) och levererar data i analysvänliga format. Den kräver mer reverse engineering i början och ibland underhåll när TripAdvisor ändrar sin datastruktur.
Den återanvändbara funktionen scrape_tripadvisor() täcker hotell, restauranger och sevärdheter. Du borde inte behöva en andra guide.
Och om du mitt i läsningen bestämmer dig för att kod inte är din grej — eller om du bara behöver 50 hotell i ett kalkylblad till slutet av dagen — kan göra jobbet med två klick, AI-driven fältidentifiering, automatisk sidnumrering och gratis export till Excel eller Google Sheets. Ingen Python behövs.
Om du vill gå djupare har vi fler genomgångar om scraping på och vår .
Vanliga frågor
1. Är det lagligt att skrapa TripAdvisor?
TripAdvisors användarvillkor förbjuder uttryckligen scraping. Domstolar har dock i regel bedömt att scraping av offentligt tillgänglig data (som inte ligger bakom inloggning) inte bryter mot Computer Fraud and Abuse Act i USA. Med det sagt har EU-domstolens Ryanair-dom från 2025 stärkt villkorsbaserade begränsningar i Europa. Skrapa bara offentlig data, respektera robots.txt, publicera inte upphovsrättsskyddat innehåll igen och rådgör med jurist om du ska använda datan kommersiellt.
2. Kan jag skrapa TripAdvisor utan Python?
Ja. No-code-verktyg som kan skrapa TripAdvisor direkt i din webbläsare med AI-driven fältidentifiering och automatisk sidnumrering. Du kan också använda webbläsartillägg, Google Sheets-tillägg eller kommersiella scraping-API:er. Python ger mest kontroll och flexibilitet, men det är inte det enda alternativet.
3. Hur undviker jag att bli blockerad när jag skrapar TripAdvisor?
De viktigaste taktikerna: använd realistiska och konsekventa headers (särskilt User-Agent och Sec-CH-UA), rotera residential proxies (datacenter-IP blockeras direkt), lägg in slumpmässiga pauser på 3–7 sekunder mellan förfrågningar, använd den dolda JSON-metoden för att minimera totala antalet anrop, implementera retry-logik med exponentiell backoff och värm upp sessioner genom att besöka startsidan innan du skrapar djupare sidor.
4. Vilken data kan jag skrapa från TripAdvisor?
Hotell, restauranger och sevärdheter — inklusive namn, betyg, antal recensioner, prisintervall, adresser, koordinater, bekvämligheter (hotell), kökstyp (restauranger), turvaraktighet (sevärdheter) samt fullständig recensionstext med individuella betyg och datum. De dolda JSON- och GraphQL-metoderna ger rikast data per förfrågan.
5. Hur många sidor kan jag skrapa från TripAdvisor per dag?
Med en enda IP och rimliga pauser: ungefär 600–1 000 sidor per dag. Med 20 roterande residential proxies: cirka 200 000–300 000 sidor per dag med request-baserade metoder. Selenium är långsammare — räkna med 8 000–12 000 sidor per dag och proxy. Den dolda JSON-/GraphQL-metoden ger mest data per förfrågan, så du kan behöva betydligt färre sidor totalt för att få samma informationsmängd.
Läs mer