Skrapa TripAdvisor med Python (utan att bli blockerad)

Senast uppdaterad April 17, 2026
Skrapa TripAdvisor med Python (utan att bli blockerad)

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 1 miljard recensioner, mer än 8 miljoner företagslistningar och omkring 460 miljoner unika besökare per månad. Plattformen påverkar mer än $60 miljarder 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.

Thunderbit ä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:

  1. Öppna en TripAdvisor-listningssida (t.ex. sökresultat för "Hotels in Paris").
  2. 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.
  3. Klicka på "Scrape." Thunderbit hämtar data från alla listningar på sidan — och hanterar sidnumrering automatiskt om du behöver fler resultat.
  4. 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 Thunderbit Chrome Extension — 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 studie från Cornell University 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 ScienceDirect-studie 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ådeVem får nyttaVilken data behövs
Konkurrentanalys för hotellHotellkedjor, intäktsansvarigaBetyg, priser, antal recensioner, bekvämligheter
Marknadsresearch för restaurangerRestauranggrupper, livsmedelsvarumärkenKökstyp, prisnivåer, sentiment i recensioner
Spårning av trender för sevärdheterResearrangörer, turistorganisationerPopularitetsrankning, säsongsmönster
SentimentanalysForskare, dataanalytikerFullständig recensionstext, stjärnbetyg, datum
LeadgenereringSäljteam, resebyråerFö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 71,7 % av utvecklare inom web scraping 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:

MetodHastighetJS-stödMotståndskraft mot botsskyddKomplexitetBäst för
requests + BeautifulSoup⚡ Snabb (~120–200 sidor/min rått)❌ Inget⚠️ LågEnkelStatisk listningssidor, små projekt
Selenium / huvudlös webbläsare🐢 Långsam (~8–20 sidor/min)✅ Fullt⚠️ MedelMedelDynamiskt innehåll, klick på "Read more", cookie-banners
Dold JSON / GraphQL API⚡⚡ Snabbast (~200–600 sidor/min rått)N/A✅ HögreSvårStorskalig extrahering av recensioner/hotell
No-code (Thunderbit)⚡ Snabb✅ Inbyggt✅ InbyggtEnklastIcke-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:

pip 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-parsning
  • selenium (4.43.0) — Webbläsarautomatisering, kräver Python 3.10+
  • httpx (0.28.1) — Asynkron HTTP-klient
  • parsel (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):

python -m venv tripadvisor-scraper
source tripadvisor-scraper/bin/activate  # macOS/Linux
tripadvisor-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:

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"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):

from bs4 import BeautifulSoup

soup = BeautifulSoup(response.text, "html.parser")

# Hitta alla hotellkort
cards = soup.select('div[data-test-attribute="location-results-card"]')

hotels = []
for card in cards:
    # Hotellnamn
    title_el = card.select_one('div[data-automation="hotel-card-title"]')
    name = title_el.get_text(strip=True) if title_el else None

    # Länk till detaljsida
    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

    # Betyg
    rating_el = card.select_one('[data-automation="bubbleRatingValue"]')
    rating = rating_el.get_text(strip=True) if rating_el else None

    # Antal recensioner
    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"Found {len(hotels)} hotels on this page")
for h in hotels[:3]:
    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:

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):  # Första 5 sidorna
    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"Page {page + 1}: Got status {response.status_code}, stopping.")
        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"Page {page + 1}: {len(cards)} hotels found")
    time.sleep(random.uniform(3, 7))  # Slumpmässig paus för att undvika rate limiting

df = pd.DataFrame(all_hotels)
print(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 5x långsammare 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:

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")  # Använd nytt headless-läge (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)

# Ta bort webdriver-egenskapen från navigator
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
    "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
})

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

import time
import random

url = "https://www.tripadvisor.com/Hotels-g187147-Paris_Ile_de_France-Hotels.html"
driver.get(url)

# Vänta på att hotellkorten ska laddas
wait = WebDriverWait(driver, 15)
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div[data-test-attribute="location-results-card"]')))

# Hantera cookie-popupen (om den visas)
try:
    cookie_btn = driver.find_element(By.ID, "onetrust-accept-btn-handler")
    cookie_btn.click()
    time.sleep(1)
except:
    pass  # Ingen cookie-popup

# Extrahera hotelldata
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"Scraped {len(hotels)} hotels")
for h in hotels[:3]:
    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:

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)

# Klicka på alla "Read more"-knappar
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

# Extrahera recensioner
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")
        # Betyg kodas i klassnamn som "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"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:

# Med proxy utan autentisering
proxy = "http://your-proxy-address:port"
options.add_argument(f"--proxy-server={proxy}")

# 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.

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)

# Extrahera JSON-blocket pageManifest
match = re.search(r"pageManifest:({.+?})};", response.text)
if match:
    page_data = json.loads(match.group(1))
    print("Found pageManifest data")
    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:

from parsel import Selector

sel = Selector(text=response.text)

# Extrahera strukturerad JSON-LD-data
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"Name: {data.get('name')}")
        print(f"Rating: {data.get('aggregateRating', {}).get('ratingValue')}")
        print(f"Review Count: {data.get('aggregateRating', {}).get('reviewCount')}")
        print(f"Price Range: {data.get('priceRange')}")
        print(f"Address: {data.get('address', {}).get('streetAddress')}")
        print(f"Coordinates: {data.get('geo', {}).get('latitude')}, {data.get('geo', {}).get('longitude')}")
        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:

# Extrahera urqlCache för detaljerad recensionsdata
cache_match = re.search(r'"urqlCache"\s*:\s*({.+?})\s*,\s*"redux"', response.text)
if cache_match:
    cache_data = json.loads(cache_match.group(1))
    # Navigera i cachen för att hitta recensionsdata
    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"Found review cache entry: {key[:50]}...")
                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.

import httpx
import random
import string

def generate_request_id():
    """Generate the X-Requested-By header value"""
    random_chars = ''.join(random.choices(string.ascii_letters + string.digits, k=180))
    return f"TNI1625!{random_chars}"

# Sök efter hotell i Paris
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}")

För att hämta recensioner via 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"Total reviews: {total}")
        for r in reviews[:3]:
            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ältHotellRestaurangerSevärdheter
Namn
Betyg
Antal recensioner
Pris/prisintervallIbland
Adress
Kökstyp
Varaktighet/turtyp
Bekvämligheter
Koordinater

Bygga en återanvändbar scrape_tripadvisor()-funktion

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):
    """
    Skrapa TripAdvisor-listningar för hotell, restauranger eller sevärdheter.

    Args:
        category: "hotels", "restaurants" eller "attractions"
        location_id: TripAdvisors geo-ID (t.ex. "187147" för Paris)
        location_name: URL-vänligt namn (t.ex. "Paris_Ile_de_France")
        num_pages: Antal sidor att skrapa
    """
    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"  Page {page + 1}: Status {response.status_code}, stopping.")
            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"  Page {page + 1}: {len(cards)} items found")
        time.sleep(random.uniform(3, 7))

    return pd.DataFrame(all_items)

# Exempel på användning
print("=== Hotell i Paris ===")
hotels_df = scrape_tripadvisor("hotels", "187147", "Paris_Ile_de_France", num_pages=2)
print(hotels_df.head())

print("\n=== Restauranger i Rom ===")
restaurants_df = scrape_tripadvisor("restaurants", "187791", "Rome_Lazio", num_pages=2)
print(restaurants_df.head())

print("\n=== Sevärdheter i Barcelona ===")
attractions_df = scrape_tripadvisor("attractions", "187497", "Barcelona_Catalonia", num_pages=2)
print(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 5+ biljoner datapunkter per dag) tillsammans med Cloudflare WAF. Här är en diagnos-tabell för de vanligaste felen:

SymptomTrolig orsakÅtgärd
HTTP 403-svarSaknade eller misstänkta headers; Cloudflare JS-utmaningSätt realistiska User-Agent, Accept-Language, Referer och Sec-CH-UA-headers. Se till att de är konsekventa.
CAPTCHA-sida istället för dataRate limiting eller webbläsarfingeravtryckRotera residential proxies, lägg in slumpmässiga pauser (2–7 sekunder mellan förfrågningar)
Tom HTML eller blank sidkroppJavaScript renderas inte av requestsByt till Selenium eller extrahera från dold JSON i sidkällan
Delvisa recensioner / "Read more" expanderar inteInnehåll laddas vid klickhändelseAnvänd Seleniums .click() eller extrahera från inbäddat JSON-block
Recensioner bara på ett språkSaknar språkparameterLägg till ?filterLang=ALL i recensions-URL:en
Datan slutar laddas efter N sidorSessionsbaserad rate limitRotera sessioner, rensa cookies mellan batcher
HTTP 1020 Access DeniedIP/ASN blockerat av CloudflareByt från datacenter-proxy till residential proxies
Oändlig challenge loop (CAPTCHA)Trasig cookie-persistensVä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:

import time
import random
import requests

def fetch_with_retry(session, url, max_retries=4, base_delay=2, max_delay=60):
    """
    Hämta en URL med exponentiell backoff och jitter.
    Rotera User-Agent vid varje 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):
        # Rotera User-Agent vid retry
        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:
                # Respektera Retry-After om den finns
                retry_after = int(response.headers.get("Retry-After", base_delay * (2 ** attempt)))
                print(f"  Rate limited (429). Waiting {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"  Got {response.status_code}. Retry {attempt + 1}/{max_retries} in {wait:.1f}s...")
                time.sleep(wait)
                continue

            # Andra felkoder — försök inte igen
            print(f"  Unexpected status {response.status_code} for {url}")
            return response

        except requests.exceptions.Timeout:
            wait = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
            print(f"  Timeout. Retry {attempt + 1}/{max_retries} in {wait:.1f}s...")
            time.sleep(wait)

    print(f"  All {max_retries} retries exhausted for {url}")
    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:

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",
    # Lägg till fler residential proxies
]

def get_rotated_session():
    """Skapa en ny session med roterade headers och proxy."""
    session = requests.Session()

    # Välj ett slumpmässigt header-set
    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)

    # Välj en slumpmässig proxy
    if PROXY_LIST:
        proxy = random.choice(PROXY_LIST)
        session.proxies = {"http": proxy, "https": proxy}

    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)

import pandas as pd

df = pd.DataFrame(all_hotels)
df.to_csv("tripadvisor_hotels_paris.csv", index=False, encoding="utf-8-sig")
print(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:

# Hierarkisk struktur
hotel_data = {
    "property_id": "d194317",
    "name": "NH City Centre Amsterdam",
    "rating": 4.0,
    "reviews": [
        {"title": "Great location", "rating": 5, "date": "2025-03-15", "text": "..."},
        {"title": "Average stay", "rating": 3, "date": "2025-03-10", "text": "..."},
    ]
}

# För platt analys, använd 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)

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, med property_id som 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 exportera skrapad data direkt 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. Developer Portal 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: EU-domstolens Ryanair-dom (december 2025) 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 Thunderbits Chrome-tillägg 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å Thunderbit-bloggen och vår YouTube-kanal.

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 Thunderbit 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

Ke
Ke
CTO på Thunderbit | Senior data scientist & ML-expert Med nästan ett decenniums erfarenhet inom maskininlärning och datavetenskap är Ke Shen alumn från Columbia University och tidigare Senior Data Scientist på Walmart Labs. Med djup, av branschen erkänd expertis inom Python, R, Java och statistik delar han väl beprövade insikter om hur man förvandlar komplexa AI-algoritmer från teori till produktionsklar arkitektur.
Innehåll

Samla in en webbsida genom att bara fråga

Säg vad du behöver på enkel engelska. Eller ännu bättre, säg ingenting alls.

Prova Thunderbit gratis
Extrahera data med AI
Överför enkelt data till Google Sheets, Airtable eller Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week