איך לגרד את TripAdvisor עם Python (בלי להיחסם)

עודכן לאחרונה ב- April 17, 2026
איך לגרד את TripAdvisor עם Python (בלי להיחסם)

지난주에 유럽 세 도시의 약 200개 숙소에 대해 TripAdvisor에서 호텔 평점과 리뷰 수를 뽑아내려고 했습니다. 제 첫 번째 스크립트 — 기본 헤더만 쓴 단순한 requests.get() — 는 매번 깔끔하게 403 Forbidden만 돌려줬습니다. 쓸 만한 데이터는 단 1바이트도 없었습니다.

TripAdvisor는 여행 업계에서 가장 풍부한 공개 데이터 소스 중 하나입니다: 10억 개 이상의 리뷰, 800만 개가 넘는 비즈니스 기록, 월간 고유 방문자 약 4억 6천만 명을 보유하고 있습니다. 또 연간 여행 지출 600억 달러 이상에 영향을 줍니다. 그런데 이 데이터를 프로그램으로 뽑아내려면? 여기서부터 일이 꼬이기 시작합니다. TripAdvisor는 DataDome 기반 봇 감지, Cloudflare WAF, TLS fingerprinting, JavaScript 챌린지를 함께 사용합니다. 즉, 여러 겹의 방어막이 기본적인 스크래핑 시도조차 시작되기 전에 대부분 막아버립니다. 이 가이드는 제가 예전에 꼭 갖고 싶었던 자료입니다. Python으로 하는 세 가지 스크래핑 방식과 코드 없는 옵션까지 직접 비교하고, 각 방식의 전체 코드, 안티봇 대응 문제 해결 섹션, 그리고 호텔·레스토랑·관광지에 재사용 가능한 템플릿까지 정리했습니다. Python 초보든 숙련 개발자든, 불필요한 403 오류를 훨씬 덜 보게 해줄 겁니다.

코드를 쓰고 싶지 않다면? TripAdvisor를 쉽게 긁는 방법도 있습니다

이 부분은 솔직하게 말하고 싶습니다. “scrape TripAdvisor with Python”을 검색하는 사람들 중 상당수는 사실 코딩 자체가 목적이 아닙니다. 그들은 호텔 이름, 평점, 리뷰 수, 가격 같은 데이터를 스프레드시트에 빠르게 넣고 싶을 뿐입니다. 이런 경우라면 훨씬 짧은 길이 있습니다.

Thunderbit은 저희가 만든 AI 기반 Chrome 확장 프로그램으로, TripAdvisor의 어떤 페이지든 읽고 자동으로 필요한 컬럼을 제안합니다. 흐름은 정말 두 번 클릭이면 끝납니다:

  1. TripAdvisor 목록 페이지를 엽니다 (예: “Hotels in Paris” 검색 결과).
  2. Thunderbit 사이드바에서 “AI Suggest Fields”를 클릭합니다. AI가 페이지를 스캔해서 Hotel Name, Rating, Review Count, Price, Location 같은 컬럼을 제안합니다.
  3. “Scrape”를 클릭합니다. Thunderbit가 현재 페이지의 모든 항목 데이터를 추출하고, 추가 결과가 있으면 페이지네이션도 자동으로 처리합니다.
  4. Excel, Google Sheets, Airtable 또는 Notion으로 내보냅니다. 모든 요금제에서 내보내기는 무료입니다.

Thunderbit는 별도 설정 없이 호텔, 레스토랑, 관광지 모두에서 작동합니다. AI가 페이지에 보이는 형태에 맞춰 자동으로 적응합니다. 페이지네이션 결과에서는 “Next” 버튼과 무한 스크롤도 알아서 인식합니다. 그리고 실제 Chrome 브라우저 안에서 동작하기 때문에 쿠키와 브라우저 지문을 그대로 이어받아, 봇 감지에 자연스럽게 더 강합니다.

Thunderbit Chrome 확장 프로그램으로 직접 써볼 수 있습니다. 무료 요금제에는 월 6페이지가 포함되어 있어, 테스트하기엔 충분합니다.

프로그래밍 제어가 필요하거나, 커스텀 파싱 로직이 필요하거나, 10,000개 이상의 페이지를 긁을 계획이라면 Python이 맞습니다. 계속 읽어보세요.

왜 Python으로 TripAdvisor를 긁어야 할까요?

TripAdvisor 데이터는 직접적이고 측정 가능한 비즈니스 효과를 냅니다. Cornell University 연구에 따르면 호텔의 Global Review Index가 100점 만점 기준으로 1점 오르면 평균 일일 요금은 0.89% 상승하고, 이용 가능 객실당 수익은 1.42% 증가합니다. 또 ScienceDirect의 다른 연구는 TripAdvisor 외부 평점이 별 1개 오르면 평균 호텔의 연간 매출이 55,000~75,000달러 증가한다고 보여줍니다. 리뷰는 단순한 자존심 지표가 아니라, 실제로 매출을 움직이는 요소입니다.

각 팀은 TripAdvisor 데이터를 이렇게 활용합니다:

사용 사례누가 이득을 보나어떤 데이터가 필요한가
호텔 경쟁 분석호텔 체인, 수익 관리 팀평점, 가격, 리뷰 수, 편의시설
레스토랑 시장 조사외식 그룹, F&B 브랜드요리 종류, 가격대, 리뷰 감성
관광지 트렌드 추적관광 운영사, 관광청인기 순위, 계절성 패턴
감성 분석연구자, 데이터 분석가전체 리뷰 텍스트, 별점, 날짜
리드 생성영업팀, 여행사사업체 이름, 연락처, 위치

그렇다면 왜 하필 Python일까요? 이유는 세 가지입니다. 첫째, 생태계입니다. BeautifulSoup, Selenium, Playwright, Scrapy, httpx, pandas까지, Python에는 다른 어떤 언어보다 성숙한 스크래핑과 데이터 처리 라이브러리가 있습니다. 둘째, 웹 스크래핑 개발자의 71.7%가 Python을 사용합니다. 즉, 커뮤니티 지원이 더 많고, StackOverflow 답변도 더 많고, 최신 가이드도 더 많습니다. 셋째, 작업 흐름이 자연스럽습니다. BeautifulSoup로 긁고, pandas로 정리하고, Hugging Face Transformers로 감성 분석을 돌리고, 대시보드까지 같은 언어로 이어갈 수 있습니다. 컨텍스트 전환이 없습니다.

Python으로 TripAdvisor를 긁는 3가지 방법 비교

대부분의 튜토리얼은 한 가지 방법만 밀어붙입니다. 그런데 실제로는 코드를 쓰기 전에 어떤 방식을 쓸지 결정하는 게 더 중요합니다. 제가 예전부터 갖고 싶었던 비교표는 아래와 같습니다:

방식속도JS 지원Anti-Bot 저항력난이도가장 적합한 경우
requests + BeautifulSoup⚡ 빠름 (~120–200페이지/분, 원시 속도)❌ 없음⚠️ 낮음쉬움정적 목록 페이지, 작은 프로젝트
Selenium / Headless 브라우저🐢 느림 (~8–20페이지/분)✅ 완전 지원⚠️ 중간보통동적 콘텐츠, “Read more” 클릭, 쿠키 배너
숨겨진 JSON / GraphQL API⚡⚡ 가장 빠름 (~200–600페이지/분, 원시 속도)해당 없음✅ 더 높음어려움대규모 호텔/리뷰 추출
노코드(Thunderbit)⚡ 빠름✅ 내장✅ 내장가장 쉬움비개발자, 빠른 일회성 내보내기

몇 가지 중요한 단서가 있습니다. 위 속도는 이론상의 원시 속도입니다. TripAdvisor의 속도 제한은 실제로 IP당 분당 약 10~15요청 수준이라, 어떤 방식을 쓰든 현실적인 처리량은 IP당 분당 약 10페이지 정도로 제한됩니다. 숨겨진 JSON 방식은 요청 한 번당 더 많은 정보를 가져오므로, 총 요청 수가 적고 속도 제한에 덜 걸립니다. Selenium은 실제 테스트에서 requests 기반 방식보다 약 5배 느리지만, 버튼 클릭이나 JavaScript 로딩이 필요한 경우에는 사실상 유일한 선택지입니다.

이후 내용에서는 Python으로 세 가지 방식을 모두 다룹니다. 자신에게 맞는 방법을 고르거나, 여러 방식을 섞어도 됩니다. 저도 목록 페이지는 requests+BS4, 상세 페이지는 숨겨진 JSON을 자주 함께 씁니다.

Python 환경 준비

시작하기 전에 환경부터 준비하겠습니다. Python 3.10+가 필요합니다. 저는 3.12나 3.13을 권장합니다. 주요 패키지들이 문제 없이 지원합니다.

한 번에 설치하려면:

pip install requests beautifulsoup4 selenium httpx parsel pandas curl-cffi

패키지 메모:

  • requests (2.33.1) — HTTP 요청, Python 3.10+ 필요
  • beautifulsoup4 (4.14.3) — HTML 파싱
  • selenium (4.43.0) — 브라우저 자동화, Python 3.10+ 필요
  • httpx (0.28.1) — 비동기 HTTP 클라이언트
  • parsel (1.11.0) — CSS/XPath 셀렉터, BS4보다 편한 경우가 많음
  • pandas (3.0.2) — 데이터 내보내기, Python 3.11+ 필요
  • curl_cffi (0.15.0) — TLS fingerprint 위장, Cloudflare 우회에 중요

ChromeDriver: Selenium을 쓴다면 좋은 소식이 있습니다. Selenium 4.6+부터는 Selenium Manager가 적절한 ChromeDriver를 자동으로 내려받고 관리합니다. 수동 설치가 필요 없습니다. 브라우저 버전과의 호환도 동적으로 맞춰주기 때문에, Chrome 버전 문제를 크게 신경 쓰지 않아도 됩니다.

격리된 작업 환경(권장):

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

방법 1: Requests와 BeautifulSoup로 TripAdvisor 긁기

가장 단순한 방식입니다. 필요한 정보가 이미 정적 HTML 안에 들어 있는 목록 페이지, 예를 들어 호텔 검색 결과나 레스토랑 목록에 잘 맞습니다. 브라우저도 필요 없고, JavaScript 렌더링도 없고, 리소스 사용도 적습니다.

TripAdvisor URL 패턴 이해하기

TripAdvisor URL은 카테고리별로 비교적 예측 가능한 패턴을 따릅니다:

  • 호텔: https://www.tripadvisor.com/Hotels-g{locationId}-{Location_Name}-Hotels.html
  • 레스토랑: https://www.tripadvisor.com/Restaurants-g{locationId}-{Location_Name}.html
  • 관광지: https://www.tripadvisor.com/Attractions-g{locationId}-Activities-{Location_Name}.html

페이지네이션은 oa 오프셋 파라미터를 URL에 붙여 처리합니다. 한 페이지에는 30개 결과가 보입니다:

  • 1페이지: 기본 URL (oa 없음)
  • 2페이지: Hotels-g187768-oa30-Italy-Hotels.html
  • 3페이지: Hotels-g187768-oa60-Italy-Hotels.html

리뷰 페이지에서는 이동 파라미터가 or이며 10개 단위로 증가합니다:

  • 1페이지: Reviews-or0-Hotel_Name.html
  • 2페이지: Reviews-or10-Hotel_Name.html

모든 언어의 리뷰를 보려면 URL 뒤에 ?filterLang=ALL을 붙이세요.

진짜 브라우저처럼 보이는 헤더로 요청 보내기

TripAdvisor는 헤더를 꽤 공격적으로 검사합니다. Python 기본 헤더로 보내면 바로 막히는 경우가 많습니다. 실제 Chrome 브라우저처럼 보이게 해야 합니다:

import requests
import time
import random

session = requests.Session()

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Accept-Encoding": "gzip, deflate, br",
    "Referer": "https://www.tripadvisor.com/",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "none",
    "Sec-CH-UA": '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
    "Sec-CH-UA-Mobile": "?0",
    "Sec-CH-UA-Platform": '"Windows"',
}

session.headers.update(headers)

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

print(f"Status: {response.status_code}")
print(f"Content length: {len(response.text)} characters")

중요한 점: TripAdvisor는 User-AgentSec-CH-UA Client Hints가 서로 일치하는지 확인합니다. 예를 들어 User-Agent는 Chrome 135인데 Sec-CH-UA에는 Chrome 120이 들어 있으면 수상하게 보입니다. 헤더는 하나씩 따로가 아니라, 세트로 함께 돌리는 게 좋습니다.

BeautifulSoup로 카드 파싱하기

응답을 받았다면 BeautifulSoup로 데이터를 뽑을 수 있습니다. TripAdvisor는 자주 바뀌는 CSS 클래스명보다 data-automationdata-test-attribute를 더 많이 씁니다.

from bs4 import BeautifulSoup

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

# Find all hotel listing cards
cards = soup.select('div[data-test-attribute="location-results-card"]')

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

    # Link to detail page
    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

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

    # Review count
    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)

셀렉터에 대한 주의: TripAdvisor는 FGwzt, yyzcQ처럼 보기에는 무의미한 클래스명을 자주 바꿉니다. 반면 data-automation, data-test-target은 훨씬 안정적입니다. 가능하면 항상 클래스보다 data attribute를 우선하세요.

페이지네이션 처리하기

여러 페이지를 긁으려면, 요청 사이에 예의 바른 대기 시간을 두고 오프셋을 순회하면 됩니다:

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):  # First 5 pages
    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))  # Random delay to avoid rate limiting

df = pd.DataFrame(all_hotels)
print(f"\nTotal hotels scraped: {len(df)}")

time.sleep(random.uniform(3, 7))는 중요합니다. TripAdvisor의 속도 제한은 대략 IP당 분당 10~15요청 정도입니다. 이 속도를 넘기면 CAPTCHA나 429 오류가 발생할 수 있습니다.

이 방식의 한계

requests+BS4가 잘 안 되는 경우는 다음과 같습니다:

  • TripAdvisor가 JavaScript로 렌더링되는 콘텐츠를 보낼 때
  • 긴 리뷰 텍스트가 “Read more” 버튼 뒤에 숨겨져 있을 때
  • 봇 방어가 JavaScript 챌린지나 CAPTCHA로 올라갈 때
  • 가격, 재고처럼 클라이언트 렌더링 후에만 나타나는 데이터가 필요할 때

이런 경우에는 Selenium(방법 2)이나 숨겨진 JSON 방식(방법 3)이 필요합니다.

방법 2: Selenium으로 TripAdvisor 긁기(헤드리스 브라우저)

Selenium은 실제 브라우저를 실행하므로 JavaScript 렌더링, 버튼 클릭, 쿠키 동의 배너 처리, 동적 콘텐츠와의 상호작용이 가능합니다. 대가도 있습니다. 실제로는 약 5배 느리고 브라우저 인스턴스당 300~500MB RAM을 사용합니다.

탐지 회피 설정으로 Selenium 구성하기

기본 Selenium은 쉽게 탐지됩니다. TripAdvisor의 지문 인식에 바로 걸립니다. 자동화 플래그를 꺼야 합니다:

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")  # Use new headless mode (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)

# Remove webdriver property from navigator
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
    "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
})

이걸로 충분할까요? 소규모 스크래핑(50페이지 미만)에서는, 여기에 Residential 프록시를 더하면 대개 잘 작동합니다. 더 큰 규모라면 undetected-chromedrivernodriver가 필요할 수 있습니다. TripAdvisor의 DataDome 방어는 요청당 1,000개 이상의 신호를 분석하며, Selenium 기본값으로는 흉내 내기 어려운 TLS fingerprint까지 봅니다.

Selenium으로 호텔 검색 결과 긁기

import time
import random

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

# Wait for hotel cards to load
wait = WebDriverWait(driver, 15)
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div[data-test-attribute="location-results-card"]')))

# Handle cookie consent popup (if it appears)
try:
    cookie_btn = driver.find_element(By.ID, "onetrust-accept-btn-handler")
    cookie_btn.click()
    time.sleep(1)
except:
    pass  # No cookie popup

# Extract hotel data
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)

제 로컬 환경에서는 이 페이지 하나를 긁는 데 약 8초가 걸렸습니다. requests+BS4로는 1초도 안 걸렸던 것과 비교하면 차이가 큽니다. 수백 페이지를 긁으면 이 차이는 금방 커집니다.

“Read more” 펼치기와 전체 리뷰 긁기

리뷰 페이지는 긴 리뷰를 “Read more” 뒤에 숨겨둡니다. Selenium은 이를 클릭할 수 있습니다:

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)

# Click all "Read more" buttons
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

# Extract reviews
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")
        # Rating encoded in class like "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")

Selenium에 프록시 회전 추가하기

지속적으로 긁으려면 프록시 회전이 필요합니다. selenium-wire는 2024년 1월 이후 사실상 비권장 상태이므로, Chrome의 기본 프록시 지원을 사용하세요:

# With authentication-free proxy
proxy = "http://your-proxy-address:port"
options.add_argument(f"--proxy-server={proxy}")

# For proxies with authentication, use a Chrome extension or Selenium 4's BiDi protocol

프록시를 자동 회전하려면, 배치마다 다른 프록시를 넣은 새 driver 인스턴스를 만드세요. 우아하진 않지만 안정적입니다.

방법 3: 숨겨진 JSON 방식(HTML 파싱 자체를 건너뛰기)

대부분의 튜토리얼이 아예 건너뛰는 방식인데, 사실 가장 빠르고 깔끔합니다. TripAdvisor는 페이지 안에 JSON 형태의 구조화 데이터를 <script> 태그와 pageManifest, urqlCache 같은 JavaScript 변수로 심어둡니다. 이 JSON을 추출하면 더 깨끗한 데이터(숫자형 평점, ISO 형식 날짜)를 얻을 수 있고, 요청 수도 적고 JavaScript 렌더링도 필요 없습니다.

소스 코드에서 JSON 찾기

핵심은 이겁니다. requests.get()만으로 페이지를 받아오고, JavaScript를 렌더링하지 않은 채 원시 HTML에서 JSON만 추출하면 됩니다.

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)

# Extract the pageManifest JSON blob
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]}")

직접 변수명을 찾는 방법: Chrome에서 TripAdvisor 호텔 페이지를 열고, 마우스 오른쪽 클릭 → View Page Source → pageManifest, urqlCache, aggregateRating를 Ctrl+F로 검색해 보세요. 데이터는 거기에 있습니다.

JSON 파싱과 구조화 데이터 추출

TripAdvisor는 application/ld+json 형식의 schema.org 데이터를 넣어두기도 합니다. 이건 훨씬 쉽게 추출할 수 있습니다:

from parsel import Selector

sel = Selector(text=response.text)

# Extract JSON-LD structured 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는 정적 HTML 안에 들어 있어서 JavaScript 렌더링이 필요 없습니다. 이름, 종합 평점, 리뷰 수, 주소, 좌표, 가격대, 이미지 URL까지 한 번에 얻을 수 있고, HTML 태그를 하나도 파싱하지 않아도 됩니다.

더 풍부한 데이터, 예를 들면 개별 리뷰, 세부 평점, 편의시설 목록이 필요하면 urqlCache 오브젝트를 봐야 합니다:

# Extract urqlCache for detailed review data
cache_match = re.search(r'"urqlCache"\s*:\s*({.+?})\s*,\s*"redux"', response.text)
if cache_match:
    cache_data = json.loads(cache_match.group(1))
    # Navigate the cache to find review data
    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

정확한 JSON 경로는 TripAdvisor가 프런트엔드를 업데이트할 때마다 조금씩 바뀌지만, 큰 구조는 오래 유지됩니다. 요약 데이터는 JSON-LD, 상세 데이터는 urqlCache라는 점은 몇 년째 비슷합니다.

TripAdvisor의 GraphQL 인터페이스 역공학하기(고급)

대규모 추출에서는 TripAdvisor의 GraphQL 엔드포인트가 구조화된 데이터를 바로 돌려줍니다. 가장 빠른 방식이지만, 유지보수도 가장 까다롭습니다.

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}"

# Search for hotels in 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}")

리뷰를 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']}")

중요한 주의사항: preRegisteredQueryId 값들(예: 검색용 84b17ed122fbdbd4, 리뷰용 ef1a9f94012220d3)은 TripAdvisor가 redeploy하면 깨질 수 있습니다. 그러면 요청이 조용히 실패합니다. 그때는 브라우저 DevTools의 네트워크 요청을 추적해서 다시 찾아야 합니다.

이 방식이 프록시 필요성을 줄이는 이유

수학은 단순합니다. requests+BS4로 호텔 상세 페이지 100개를 긁으려면 100번 요청해야 합니다. 숨겨진 JSON 방식에서는 한 번의 요청으로 각 페이지의 필요한 데이터를 다 얻을 수 있고, 추가 리뷰 확장 요청도 줄어듭니다. GraphQL은 한 번에 20개 리뷰를 돌려주기도 합니다. 요청이 적을수록 속도 제한에 덜 걸리고, 프록시 회전 필요성도 줄어듭니다. 작은~중간 규모 프로젝트(1,000페이지 미만)라면, 합리적인 지연만 두어도 프록시 없이 가능할 때가 많습니다.

재사용 가능한 하나의 스크립트로 호텔, 레스토랑, 관광지 긁기

대부분의 튜토리얼은 호텔만 다룹니다. 하지만 TripAdvisor에는 세 가지 핵심 카테고리가 있고, URL 패턴과 데이터 필드도 서로 다릅니다. 하나의 함수로 모두 처리하는 방법은 이렇습니다.

카테고리별 사용 가능한 필드

필드호텔레스토랑관광지
이름
평점
리뷰 수
가격/가격대경우에 따라
주소
요리 종류
소요 시간/투어 유형
편의시설
좌표

재사용 가능한 scrape_tripadvisor() 함수 만들기

import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import random
import re
import json

def scrape_tripadvisor(category, location_id, location_name, num_pages=3):
    """
    Scrape TripAdvisor listings across hotels, restaurants, or attractions.

    Args:
        category: "hotels", "restaurants", or "attractions"
        location_id: TripAdvisor geo ID (e.g., "187147" for Paris)
        location_name: URL-friendly name (e.g., "Paris_Ile_de_France")
        num_pages: Number of pages to scrape
    """
    url_patterns = {
        "hotels": "https://www.tripadvisor.com/Hotels-g{geo}-oa{offset}-{name}-Hotels.html",
        "restaurants": "https://www.tripadvisor.com/Restaurants-g{geo}-oa{offset}-{name}.html",
        "attractions": "https://www.tripadvisor.com/Attractions-g{geo}-oa{offset}-Activities-{name}.html",
    }

    first_page_patterns = {
        "hotels": "https://www.tripadvisor.com/Hotels-g{geo}-{name}-Hotels.html",
        "restaurants": "https://www.tripadvisor.com/Restaurants-g{geo}-{name}.html",
        "attractions": "https://www.tripadvisor.com/Attractions-g{geo}-Activities-{name}.html",
    }

    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
        "Accept-Language": "en-US,en;q=0.9",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Referer": "https://www.tripadvisor.com/",
        "Sec-CH-UA": '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
        "Sec-CH-UA-Mobile": "?0",
        "Sec-CH-UA-Platform": '"Windows"',
    }

    session = requests.Session()
    session.headers.update(headers)

    all_items = []

    for page in range(num_pages):
        offset = page * 30
        if page == 0:
            url = first_page_patterns[category].format(geo=location_id, name=location_name)
        else:
            url = url_patterns[category].format(geo=location_id, offset=offset, name=location_name)

        response = session.get(url)
        if response.status_code != 200:
            print(f"  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)

# Usage examples
print("=== Hotels in Paris ===")
hotels_df = scrape_tripadvisor("hotels", "187147", "Paris_Ile_de_France", num_pages=2)
print(hotels_df.head())

print("\n=== Restaurants in Rome ===")
restaurants_df = scrape_tripadvisor("restaurants", "187791", "Rome_Lazio", num_pages=2)
print(restaurants_df.head())

print("\n=== Attractions in Barcelona ===")
attractions_df = scrape_tripadvisor("attractions", "187497", "Barcelona_Catalonia", num_pages=2)
print(attractions_df.head())

하나의 함수로 세 카테고리를 모두 다룹니다. 코드 중복도 없습니다. TripAdvisor가 셀렉터를 바꾸더라도 한 곳만 수정하면 됩니다.

TripAdvisor가 막을 때 대처하는 법(안티봇 문제 해결)

제가 TripAdvisor를 처음 긁을 때 가장 필요했던 섹션이지만, 대부분의 경쟁 가이드는 이 부분을 체계적으로 설명하지 않습니다. TripAdvisor는 DataDome(하루 5조 개 이상의 데이터 포인트를 분석)와 Cloudflare WAF를 함께 사용합니다. 흔한 문제를 진단해 보면 아래와 같습니다:

증상가능한 원인해결책
HTTP 403 응답누락되었거나 의심스러운 헤더; Cloudflare JS 챌린지현실적인 User-Agent, Accept-Language, Referer, Sec-CH-UA를 설정하고 서로 일치시키세요.
데이터 대신 CAPTCHA 페이지가 뜸속도 제한 또는 브라우저 fingerprintingResidential 프록시를 돌리고, 요청 사이에 2~7초의 랜덤 지연을 넣으세요.
HTML이 비어 있거나 본문이 없음requests에서 JavaScript가 렌더링되지 않음Selenium으로 바꾸거나, 소스 코드 속 JSON을 추출하세요.
리뷰가 일부만 보이거나 “Read more”가 안 열림내용이 클릭 후 로딩됨Selenium의 .click()을 쓰거나, 임베디드 JSON blob에서 직접 추출하세요.
리뷰가 한 언어로만 나옴언어 파라미터 누락리뷰 URL에 ?filterLang=ALL을 붙이세요.
N페이지 이후 데이터 로딩이 멈춤세션 기반 속도 제한세션을 바꾸고, 배치 사이에 쿠키를 정리하세요.
HTTP 1020 Access DeniedIP/ASN이 Cloudflare에 의해 차단됨데이터센터 프록시 대신 residential 프록시를 쓰세요.
챌린지 루프(CAPTCHA 무한 반복)쿠키 저장이 깨짐먼저 홈피를 방문해 세션을 예열하고, cookie jar를 유지하세요.

Exponential Backoff를 이용한 Retry 로직

대부분의 글에서는 이 코드를 제대로 보여주지 않습니다. 재사용 가능한 retry 함수는 아래와 같습니다:

import time
import random
import requests

def fetch_with_retry(session, url, max_retries=4, base_delay=2, max_delay=60):
    """
    Fetch a URL with exponential backoff and jitter.
    Rotates User-Agent on each retry.
    """
    user_agents = [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
    ]

    for attempt in range(max_retries):
        # Rotate User-Agent on 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:
                # Respect Retry-After header if present
                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

            # Other error codes — don't retry
            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

헤더, 프록시, 세션 회전

지속적인 스크래핑에서는 헤더 세트를 저장해 두고 함께 돌리세요:

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",
    # Add more residential proxies
]

def get_rotated_session():
    """Create a new session with rotated headers and proxy."""
    session = requests.Session()

    # Pick a random 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)

    # Pick a random proxy
    if PROXY_LIST:
        proxy = random.choice(PROXY_LIST)
        session.proxies = {"http": proxy, "https": proxy}

    return session

프록시 종류가 중요합니다. 데이터센터 프록시는 TripAdvisor에 거의 즉시 차단됩니다(HTTP 1020 Access Denied). 지속적인 스크래핑에는 Residential 프록시가 사실상 필수입니다. 일반 사용자처럼 보이기 때문에 더 잘 통과합니다. 보통 제공업체에 따라 GB당 2.50달러~8.40달러 정도를 예상하면 됩니다.

긁어낸 TripAdvisor 데이터 내보내기와 저장하기

데이터를 얻었다면, 원하는 형태로 정리하는 일은 비교적 쉽습니다.

CSV 내보내기(가장 흔함)

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'는 중요합니다. Excel에서 프랑스어 악센트, 중국어 문자 같은 비라틴 문자가 깨지지 않고 보이도록 해줍니다.

JSON 내보내기(중첩 데이터용)

호텔 아래에 리뷰가 중첩된 경우에는 JSON이 계층 구조를 그대로 보존합니다:

# Hierarchical structure
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": "..."},
    ]
}

# For flat analysis, use 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)

관계형 데이터용 두 파일 전략

대규모 데이터셋이라면 저는 보통 두 개의 CSV를 씁니다:

  • hotels.csv — 숙소당 한 줄, 평평한 구조
  • reviews.csv — 리뷰당 한 줄, property_id를 외래 키로 사용

이렇게 하면 pandas에서 join하기 쉽고, 데이터베이스에 넣기도 쉽고, BI 도구로 가져가기도 쉽습니다.

이런 내보내기 로직을 직접 다루고 싶지 않다면, Thunderbit를 쓰면 Excel, Google Sheets, Airtable 또는 Notion으로 바로 내보낼 수 있습니다. 전부 무료고, 전부 코드 없이 됩니다. 비기술 팀과 결과를 바로 공유할 때 특히 유용합니다.

TripAdvisor를 책임감 있고 효율적으로 긁는 팁

책임감 있는 스크래핑을 위한 여섯 가지 원칙:

  • robots.txt를 확인하세요: TripAdvisor의 robots.txt는 GPTBot, ClaudeBot 같은 AI 학습용 봇을 완전히 막습니다. 일반 크롤러는 경로별로 선택적 제한을 받습니다. tripadvisor.com/robots.txt를 직접 확인해 보세요.
  • 지연을 넣으세요: 요청 사이 37초는 안전한 범위입니다. IP당 분당 1015요청을 넘기면 속도 제한에 걸릴 수 있습니다.
  • 공개 정보만 긁으세요. 로그인으로만 보이는 콘텐츠에는 접근하지 마세요.
  • 개인정보를 다룬다면 데이터를 안전하게 저장하고 GDPR/CCPA를 준수하세요.
  • 대규모 상업용 데이터가 필요하다면 TripAdvisor 공식 API를 고려하세요. Developer Portal은 비즈니스 상세 정보와 위치당 최대 리뷰 5개, 사진 5개까지 제공합니다. 제한은 있지만, 합법적이고 안정적입니다.
  • 법적 맥락도 인식하세요: 2025년 12월 EU 법원의 Ryanair 판결은 EU에서 이용약관 기반 스크래핑 제한을 강화했습니다. TripAdvisor의 이용약관은 스크래핑을 명시적으로 금지합니다. 책임 있게, 그리고 본인 책임 하에 스크래핑하세요.

결론

전체 그림은 이렇습니다.

  • Requests + BeautifulSoup는 가장 단순한 경로입니다. 정적 목록 페이지에 잘 맞고, 설정이 적게 들고, 빠릅니다. 100페이지 미만을 긁고 JavaScript 렌더링이 필요 없다면 여기서 시작하세요.
  • Selenium은 requests가 못 하는 모든 것을 처리합니다: 동적 콘텐츠, “Read more” 버튼, 쿠키 배너. 5배 느리고 자원도 더 먹지만, 페이지와 상호작용해야 할 때는 사실상 필수입니다.
  • 숨겨진 JSON / GraphQL은 가장 깔끔하고 빠른 방식입니다. HTML을 파싱하지 않아도 구조화된 데이터를 얻을 수 있고, 요청 수가 줄어 프록시 부담도 적고, 분석 가능한 형태의 데이터를 바로 줍니다. 대신 초반 역공학이 조금 필요하고, TripAdvisor가 데이터 구조를 바꾸면 가끔 유지보수가 필요합니다.

재사용 가능한 scrape_tripadvisor() 함수는 호텔, 레스토랑, 관광지를 모두 커버합니다. 사실 더 이상 별도 가이드가 필요하지 않을 정도입니다.

그리고 읽다가 “사실 코딩은 내 일이 아닌데” 싶거나, 오늘 안으로 스프레드시트에 호텔 50개만 있으면 된다면, Thunderbit Chrome 확장 프로그램이 AI 기반 필드 인식, 자동 페이지네이션, Excel 또는 Google Sheets로의 무료 내보내기를 두 번 클릭만으로 처리해 줍니다. Python 없이도 가능합니다.

더 깊이 배우고 싶다면, Thunderbit 블로그YouTube 채널에도 추가 가이드가 있습니다.

자주 묻는 질문

1. TripAdvisor를 긁는 건 합법인가요?

TripAdvisor의 이용약관은 스크래핑을 명시적으로 금지합니다. 다만 미국 법원은 보통 로그인 뒤에 있지 않은 공개 데이터의 스크래핑이 Computer Fraud and Abuse Act를 위반하지는 않는다고 봐 왔습니다. 하지만 2025년 EU Ryanair 판결은 유럽에서 이용약관 기반 제한을 더 강하게 만들었습니다. 공개 정보만 긁고, robots.txt를 지키고, 저작권이 있는 콘텐츠를 재게시하지 말고, 상업적 이용이라면 법률 자문을 받는 것이 좋습니다.

2. Python 없이도 TripAdvisor를 긁을 수 있나요?

네. Thunderbit 같은 노코드 도구는 AI 기반 필드 감지와 자동 페이지네이션을 통해 브라우저에서 바로 TripAdvisor를 긁을 수 있습니다. 브라우저 확장 프로그램, Google Sheets 애드온, 상용 스크래핑 API도 있습니다. Python이 가장 유연하고 제어력이 높지만, 유일한 방법은 아닙니다.

3. TripAdvisor에서 차단되지 않으려면 어떻게 해야 하나요?

핵심은 현실적이고 일관된 헤더 사용(특히 User-AgentSec-CH-UA), Residential 프록시 회전(Data center 프록시는 즉시 막히기 쉬움), 요청 사이 3~7초의 무작위 지연, 총 요청 수를 줄이는 숨겨진 JSON 방식 사용, exponential backoff 기반 retry, 그리고 깊은 페이지를 긁기 전에 홈페이지만 먼저 방문해 세션을 예열하는 것입니다.

4. TripAdvisor에서 어떤 데이터를 긁을 수 있나요?

호텔, 레스토랑, 관광지의 이름, 평점, 리뷰 수, 가격대, 주소, 좌표, 편의시설(호텔), 요리 종류(레스토랑), 투어 시간(관광지), 개별 리뷰 텍스트, 별점, 날짜까지 가능합니다. 숨겨진 JSON 방식과 GraphQL 방식이 한 요청당 가장 풍부한 데이터를 돌려줍니다.

5. 하루에 TripAdvisor 페이지를 얼마나 긁을 수 있나요?

단일 IP에 합리적인 지연을 넣는다면 하루 약 6001,000페이지 정도입니다. 20개의 Residential 프록시를 돌리면, requests 기반 방식으로는 하루 약 200,000300,000페이지까지도 가능합니다. Selenium은 더 느려서 프록시당 하루 8,000~12,000페이지 정도를 기대하면 됩니다. 숨겨진 JSON/GraphQL은 요청당 더 많은 정보를 반환하므로, 같은 양의 정보를 얻는 데 필요한 페이지 수가 훨씬 적을 수 있습니다.

더 알아보기

Ke
Ke
CTO ב-Thunderbit | מדען נתונים בכיר ומומחה ב-ML עם כמעט עשור של ניסיון בלמידת מכונה ובמדע הנתונים, קה שן הוא בוגר אוניברסיטת קולומביה ולשעבר מדען נתונים בכיר ב-Walmart Labs. עם מומחיות עמוקה ומוכרת בקרב עמיתים ב-Python, R, Java וסטטיסטיקה, הוא משתף תובנות מוכחות-בקרב על המעבר מאלגוריתמי AI מורכבים מתיאוריה לארכיטקטורה ברמת production.
תוכן עניינים

גרדו דף אינטרנט פשוט על ידי בקשה

תגידו מה צריך באנגלית פשוטה. או אפילו לא צריך להגיד כלום.

נסו את Thunderbit חינם
חילוץ נתונים באמצעות AI
העבירו נתונים בקלות ל-Google Sheets, Airtable או Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week