Dùng Python để Scrape TripAdvisor (Không Lo Bị Chặn)

Cập nhật lần cuối vào April 17, 2026

Tuần trước, tôi đã thử lấy dữ liệu xếp hạng khách sạn và số lượng đánh giá cho khoảng 200 cơ sở lưu trú ở ba thành phố châu Âu từ TripAdvisor. Script đầu tiên của tôi — một đoạn requests.get() cơ bản với header mặc định — trả về đúng một màn hình 403 Forbidden trên tất cả mọi request. Không lấy được dù chỉ một byte dữ liệu hữu ích.

TripAdvisor là một trong những nguồn dữ liệu công khai giàu giá trị nhất trong ngành du lịch: hơn , hơn 8 triệu danh sách doanh nghiệp và khoảng 460 triệu lượt truy cập duy nhất mỗi tháng. Nền tảng này ảnh hưởng đến hơn chi tiêu du lịch hằng năm. Nhưng để lấy dữ liệu đó bằng code thì không hề đơn giản. TripAdvisor dùng DataDome để phát hiện bot, Cloudflare WAF, TLS fingerprinting và các thử thách JavaScript — một hệ thống phòng thủ nhiều lớp có thể chặn phần lớn các cách scrape ngây thơ ngay từ đầu. Hướng dẫn này là tài nguyên duy nhất mà tôi ước gì mình đã có sớm hơn: so sánh trực diện ba cách scrape bằng Python (kèm một lựa chọn no-code), mã nguồn đầy đủ cho từng cách, phần xử lý sự cố chống bot có cấu trúc rõ ràng, và các pattern có thể tái sử dụng cho khách sạn, nhà hàng lẫn điểm tham quan. Dù bạn mới học Python hay đã là developer nhiều kinh nghiệm, bài này sẽ giúp bạn tiết kiệm rất nhiều lần gặp lỗi 403 vô ích.

Không Muốn Viết Code? Scrape TripAdvisor Theo Cách Dễ Nhất

Tôi muốn nói thẳng một điều: rất nhiều người tìm “scrape TripAdvisor with Python” thực ra không quá quan tâm đến việc viết code. Họ chỉ cần dữ liệu — tên khách sạn, điểm đánh giá, số review, giá cả — đưa vào bảng tính thật nhanh. Nếu bạn cũng vậy, có một con đường ngắn hơn nhiều.

là một tiện ích Chrome ứng dụng AI mà chúng tôi xây dựng, có thể đọc bất kỳ trang TripAdvisor nào và tự động gợi ý đúng các cột cần trích xuất. Quy trình thực sự chỉ có hai cú nhấp:

  1. Mở một trang danh sách trên TripAdvisor (ví dụ: kết quả tìm kiếm “Hotels in Paris”).
  2. Nhấp “AI Suggest Fields” trong thanh bên của Thunderbit. AI sẽ quét trang và đề xuất các cột như Hotel Name, Rating, Review Count, Price và Location.
  3. Nhấp “Scrape.” Thunderbit sẽ trích xuất dữ liệu từ mọi mục trên trang — và tự xử lý phân trang nếu bạn cần thêm kết quả.
  4. Xuất dữ liệu sang Excel, Google Sheets, Airtable hoặc Notion. Xuất file là miễn phí ở mọi gói.

Thunderbit hoạt động trên khách sạn, nhà hàng và điểm tham quan mà không cần chỉnh cấu hình — AI sẽ tự thích ứng với nội dung trên trang. Với kết quả có phân trang, công cụ tự nhận diện nút “Next” và cả cuộn vô hạn. Và vì chạy ngay trong Chrome thật của bạn, nó kế thừa cookie phiên làm việc và browser fingerprint của bạn, nên có lợi thế tự nhiên khi đối đầu với cơ chế chống bot.

Bạn có thể thử bằng — bản miễn phí cho phép 6 trang/tháng, đủ để kiểm tra quy trình.

Nếu bạn cần kiểm soát bằng code, logic phân tích tùy chỉnh, hoặc định scrape hơn 10.000 trang, thì Python vẫn là lựa chọn phù hợp. Hãy tiếp tục đọc.

Vì Sao Nên Scrape TripAdvisor Bằng Python?

Dữ liệu TripAdvisor có tác động kinh doanh trực tiếp và đo lường được. Một nghiên cứu từ cho thấy khi Global Review Index của một khách sạn tăng 1 điểm trên thang 100, giá phòng trung bình tăng 0,89% và doanh thu trên mỗi phòng khả dụng tăng 1,42%. Một nghiên cứu khác trên chỉ ra rằng việc tăng thêm 1 sao trên TripAdvisor do yếu tố bên ngoài có thể mang lại thêm 55.000–75.000 USD doanh thu hằng năm cho một khách sạn trung bình. Review không chỉ là chỉ số “cho đẹp” — chúng là động lực tạo doanh thu.

Đây là cách các nhóm khác nhau dùng dữ liệu TripAdvisor:

Trường hợp sử dụngAi hưởng lợiDữ liệu cần có
Phân tích đối thủ khách sạnChuỗi khách sạn, quản lý doanh thuXếp hạng, giá, khối lượng review, tiện nghi
Nghiên cứu thị trường nhà hàngChuỗi nhà hàng, thương hiệu F&BLoại món ăn, khoảng giá, cảm nhận review
Theo dõi xu hướng điểm tham quanCông ty lữ hành, cơ quan du lịchThứ hạng độ phổ biến, mô hình mùa vụ
Phân tích cảm xúcNhà nghiên cứu, chuyên viên dữ liệuNội dung review đầy đủ, số sao, ngày tháng
Tìm kiếm leadĐội sales, đại lý du lịchTên doanh nghiệp, thông tin liên hệ, vị trí

Tại sao lại là Python? Có ba lý do. Thứ nhất là hệ sinh thái: BeautifulSoup, Selenium, Playwright, Scrapy, httpx, pandas — Python có thư viện scrape và phân tích dữ liệu trưởng thành hơn bất kỳ ngôn ngữ nào khác. Thứ hai, đang dùng Python, nghĩa là có cộng đồng hỗ trợ lớn hơn, nhiều câu trả lời hơn trên StackOverflow và tài liệu cập nhật hơn. Thứ ba là lợi thế của pipeline: bạn có thể scrape bằng BeautifulSoup, làm sạch bằng pandas, chạy phân tích cảm xúc với Hugging Face Transformers, rồi dựng dashboard — tất cả trong cùng một ngôn ngữ. Không cần chuyển ngữ cảnh liên tục.

Ba Cách Scrape TripAdvisor Bằng Python (So Sánh Trực Diện)

Mỗi hướng dẫn cạnh tranh thường chỉ chọn một cách rồi đào sâu vào nó. Điều đó không hữu ích khi bạn đang phải quyết định trước khi viết code. Đây là bảng so sánh mà tôi ước ai đó đã đưa cho tôi từ đầu:

Cách tiếp cậnTốc độHỗ trợ JSKhả năng chống botĐộ phức tạpPhù hợp nhất cho
requests + BeautifulSoup⚡ Nhanh (~120–200 trang/phút thô)❌ Không có⚠️ ThấpDễTrang danh sách tĩnh, dự án quy mô nhỏ
Selenium / Headless Browser🐢 Chậm (~8–20 trang/phút)✅ Đầy đủ⚠️ Trung bìnhTrung bìnhNội dung động, nút “Read more”, banner cookie
Hidden JSON / GraphQL API⚡⚡ Nhanh nhất (~200–600 trang/phút thô)N/A✅ Cao hơnKhóTrích xuất review/khách sạn quy mô lớn
No-code (Thunderbit)⚡ Nhanh✅ Tích hợp sẵn✅ Tích hợp sẵnDễ nhấtNgười không code, xuất dữ liệu nhanh một lần

Có vài lưu ý quan trọng. Tốc độ thô ở trên chỉ là lý thuyết — giới hạn tốc độ của TripAdvisor (~10–15 request mỗi phút trên mỗi IP) sẽ giới hạn thông lượng thực tế xuống khoảng 10 trang/phút/IP bất kể cách làm nào. Phương pháp hidden JSON cho bạn nhiều dữ liệu nhất trên mỗi request, nên tổng số request ít hơn và ít bị rate limit hơn. Selenium chậm hơn các cách dựa trên request khoảng 5 lần trong benchmark thực tế, nhưng lại là lựa chọn duy nhất khi bạn cần bấm nút hoặc render JavaScript.

Phần còn lại của hướng dẫn này sẽ đi qua cả ba cách bằng Python với mã đầy đủ. Hãy chọn cách phù hợp với tình huống của bạn, hoặc kết hợp chúng lại (tôi thường dùng requests+BS4 cho trang danh sách và hidden JSON cho trang chi tiết).

Thiết Lập Môi Trường Python

Trước khi bắt đầu, hãy chuẩn bị môi trường. Bạn cần Python 3.10+ (tôi khuyên dùng 3.12 hoặc 3.13 — tất cả các package lớn đều hỗ trợ ổn định, chưa ghi nhận vấn đề nào).

Cài tất cả cùng lúc:

1pip install requests beautifulsoup4 selenium httpx parsel pandas curl-cffi

Ghi chú về package:

  • requests (2.33.1) — gửi HTTP request, yêu cầu Python 3.10+
  • beautifulsoup4 (4.14.3) — phân tích HTML
  • selenium (4.43.0) — tự động hóa trình duyệt, yêu cầu Python 3.10+
  • httpx (0.28.1) — HTTP client bất đồng bộ
  • parsel (1.11.0) — selector CSS/XPath (nhẹ hơn BS4)
  • pandas (3.0.2) — xuất dữ liệu, yêu cầu Python 3.11+
  • curl_cffi (0.15.0) — giả lập TLS fingerprint (rất quan trọng để vượt Cloudflare)

ChromeDriver: Nếu bạn dùng Selenium, tin tốt là từ Selenium 4.6+, Selenium Manager sẽ tự động tải và lưu cache đúng file ChromeDriver. Không cần cài thủ công. Công cụ này tự khớp phiên bản động, nên bạn không phải lo lệch version Chrome.

Môi trường ảo (khuyến nghị):

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

Cách 1: Scrape TripAdvisor Bằng Requests và BeautifulSoup

Đây là cách đơn giản nhất. Nó hoạt động tốt khi scrape các trang danh sách (kết quả tìm kiếm khách sạn, danh sách nhà hàng) nơi dữ liệu bạn cần đã có sẵn trong HTML tĩnh. Không cần trình duyệt, không cần render JavaScript, tiêu tốn tài nguyên rất ít.

Hiểu Cấu Trúc URL Của TripAdvisor

URL của TripAdvisor có mẫu khá dễ đoán theo từng danh mục:

  • Hotels: https://www.tripadvisor.com/Hotels-g{locationId}-{Location_Name}-Hotels.html
  • Restaurants: https://www.tripadvisor.com/Restaurants-g{locationId}-{Location_Name}.html
  • Attractions: https://www.tripadvisor.com/Attractions-g{locationId}-Activities-{Location_Name}.html

Phân trang dùng tham số oa (offset anchors) được chèn vào URL. Mỗi trang hiển thị 30 kết quả:

  • Trang 1: URL gốc (không có oa)
  • Trang 2: Hotels-g187768-oa30-Italy-Hotels.html
  • Trang 3: Hotels-g187768-oa60-Italy-Hotels.html

Với trang review, tham số offset là or và tăng theo bước 10:

  • Trang 1: Reviews-or0-Hotel_Name.html
  • Trang 2: Reviews-or10-Hotel_Name.html

Để lấy review ở tất cả ngôn ngữ, hãy thêm ?filterLang=ALL vào URL.

Gửi Request Với Header Giống Thực Tế

TripAdvisor kiểm tra header rất gắt. Request với header mặc định của Python thường bị chặn ngay lập tức. Bạn cần giả lập một trình duyệt Chrome thật:

1import requests
2import time
3import random
4session = requests.Session()
5> This paragraph contains content that cannot be parsed and has been skipped.
6session.headers.update(headers)
7url = "https://www.tripadvisor.com/Hotels-g187147-Paris_Ile_de_France-Hotels.html"
8response = session.get(url)
9print(f"Status: {response.status_code}")
10print(f"Content length: {len(response.text)} characters")

Chi tiết quan trọng: TripAdvisor kiểm tra sự nhất quán giữa User-Agent và các header Client Hints Sec-CH-UA. Nếu bạn khai là Chrome 135 trong User-Agent nhưng Sec-CH-UA lại nói Chrome 120, bạn sẽ bị đánh dấu là bất thường. Hãy luôn xoay cả bộ header cùng lúc, không xoay từng header riêng lẻ.

Phân Tích Danh Sách Bằng BeautifulSoup

Khi đã nhận được response thành công, hãy trích xuất dữ liệu bằng BeautifulSoup. TripAdvisor dùng các thuộc tính data-automationdata-test-attribute, ổn định hơn nhiều so với tên class CSS (vốn thay đổi thường xuyên):

1from bs4 import BeautifulSoup
2soup = BeautifulSoup(response.text, "html.parser")
3# Tìm tất cả thẻ card khách sạn
4cards = soup.select('div[data-test-attribute="location-results-card"]')
5hotels = []
6for card in cards:
7    # Tên khách sạn
8    title_el = card.select_one('div[data-automation="hotel-card-title"]')
9    name = title_el.get_text(strip=True) if title_el else None
10    # Link tới trang chi tiết
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    # Điểm đánh giá
14    rating_el = card.select_one('[data-automation="bubbleRatingValue"]')
15    rating = rating_el.get_text(strip=True) if rating_el else None
16    # Số review
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> This paragraph contains content that cannot be parsed and has been skipped.
20print(f"Found {len(hotels)} hotels on this page")
21for h in hotels[:3]:
22    print(h)

Lưu ý về selector: TripAdvisor dùng các class CSS bị làm rối (như FGwzt, yyzcQ) và chúng thay đổi sau mỗi lần cập nhật site. Các thuộc tính data-automationdata-test-target ổn định hơn nhiều. Hãy luôn ưu tiên data attribute thay vì class name.

Xử Lý Phân Trang

Để scrape nhiều trang, hãy lặp qua tham số offset với độ trễ lịch sự giữa các request:

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):  # 5 trang đầu tiên
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> This paragraph contains content that cannot be parsed and has been skipped.
21    print(f"Page {page + 1}: {len(cards)} hotels found")
22    time.sleep(random.uniform(3, 7))  # Chèn độ trễ ngẫu nhiên để tránh rate limit
23df = pd.DataFrame(all_hotels)
24print(f"\nTotal hotels scraped: {len(df)}")

time.sleep(random.uniform(3, 7)) rất quan trọng. Ngưỡng rate limit của TripAdvisor vào khoảng 10–15 request mỗi phút trên mỗi IP. Nếu đi nhanh hơn mức đó, bạn sẽ bị CAPTCHA hoặc lỗi 429.

Hạn Chế Của Cách Này

Khi nào cách requests+BS4 sẽ thất bại? Khi:

  • TripAdvisor phục vụ nội dung được render bằng JavaScript (một số trang kết quả tìm kiếm cần JS)
  • Review dài bị cắt sau nút “Read more”
  • Cơ chế chống bot nâng cấp lên thử thách JavaScript hoặc CAPTCHA
  • Bạn cần dữ liệu chỉ xuất hiện sau khi render phía client (giá, tình trạng còn phòng)

Trong các trường hợp này, bạn cần Selenium (Cách 2) hoặc phương pháp hidden JSON (Cách 3).

Cách 2: Scrape TripAdvisor Bằng Selenium (Headless Browser)

Selenium khởi chạy một trình duyệt thật, nên có thể render JavaScript, bấm nút, xử lý banner đồng ý cookie và tương tác với nội dung động. Cái giá phải trả: nó chậm hơn khoảng và dùng khoảng 300–500MB RAM cho mỗi instance trình duyệt.

Cấu Hình Selenium Với Thiết Lập Chống Phát Hiện

Mặc định, Selenium rất dễ bị phát hiện. Hệ thống fingerprint của TripAdvisor sẽ nhận ra ngay. Bạn cần tắt các dấu hiệu tự động hóa:

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")  # Dùng chế độ headless mới (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> This paragraph contains content that cannot be parsed and has been skipped.
15**Đã đủ cho TripAdvisor chưa?** Với scrape quy mô nhỏ (dưới 50 trang), thiết lập này kết hợp với residential proxy thường hoạt động. Với khối lượng lớn hơn, bạn có thể cần `undetected-chromedriver` hoặc `nodriver` — lớp bảo vệ DataDome của TripAdvisor phân tích hơn 1.000 tín hiệu trên mỗi request, bao gồm cả TLS fingerprint mà Selenium thuần không giả mạo được.
16### Scrape Kết Quả Tìm Kiếm Khách Sạn Bằng Selenium
17```python
18import time
19import random
20url = "https://www.tripadvisor.com/Hotels-g187147-Paris_Ile_de_France-Hotels.html"
21driver.get(url)
22# Chờ thẻ khách sạn tải xong
23wait = WebDriverWait(driver, 15)
24wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div[data-test-attribute="location-results-card"]')))
25# Xử lý popup cookie consent (nếu xuất hiện)
26try:
27    cookie_btn = driver.find_element(By.ID, "onetrust-accept-btn-handler")
28    cookie_btn.click()
29    time.sleep(1)
30except:
31    pass  # Không có popup cookie
32# Trích xuất dữ liệu khách sạn
33cards = driver.find_elements(By.CSS_SELECTOR, 'div[data-test-attribute="location-results-card"]')
34hotels = []
35for card in cards:
36    try:
37        name = card.find_element(By.CSS_SELECTOR, 'div[data-automation="hotel-card-title"]').text
38    except:
39        name = None
40    try:
41        rating = card.find_element(By.CSS_SELECTOR, '[data-automation="bubbleRatingValue"]').text
42    except:
43        rating = None
44    try:
45        reviews = card.find_element(By.CSS_SELECTOR, '[data-automation="bubbleReviewCount"]').text
46    except:
47        reviews = None
48> This paragraph contains content that cannot be parsed and has been skipped.
49print(f"Scraped {len(hotels)} hotels")
50for h in hotels[:3]:
51    print(h)

Trên máy của tôi, thao tác này mất khoảng 8 giây cho một trang — so với chưa tới 1 giây khi dùng requests+BS4. Chênh lệch 8 lần đó sẽ tăng rất nhanh nếu bạn scrape hàng trăm trang.

Mở Rộng “Read More” Và Lấy Đầy Đủ Review

Trang review thường cắt ngắn nội dung dài sau nút “Read more”. Selenium có thể bấm vào đó:

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# Nhấp tất cả nút “Read more”
6read_more_buttons = driver.find_elements(By.XPATH, '//button//*[contains(text(), "Read more")]/..')
7for btn in read_more_buttons:
8    try:
9        driver.execute_script("arguments[0].click();", btn)
10        time.sleep(0.3)
11    except:
12        pass
13# Trích xuất review
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        # Rating được mã hóa trong class như "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> This paragraph contains content that cannot be parsed and has been skipped.
36print(f"Scraped {len(reviews)} reviews")

Thêm Xoay Proxy Cho Selenium

Nếu scrape lâu dài, bạn sẽ cần xoay proxy. Vì selenium-wire đã bị ngừng duy trì từ tháng 1/2024, hãy dùng hỗ trợ proxy có sẵn của Chrome:

1# Với proxy không cần xác thực
2proxy = "http://your-proxy-address:port"
3options.add_argument(f"--proxy-server={proxy}")
4# Với proxy có xác thực, dùng extension Chrome hoặc BiDi protocol của Selenium 4

Để xoay proxy theo chương trình, hãy tạo một instance driver mới với proxy khác cho mỗi lô request. Không quá thanh lịch, nhưng ổn định.

Cách 3: Hidden JSON (Bỏ Qua Luôn Việc Phân Tích HTML)

Phần lớn hướng dẫn bỏ qua cách này hoàn toàn, rất đáng tiếc — vì đây là cách nhanh và sạch nhất trong ba cách. TripAdvisor nhúng dữ liệu có cấu trúc dạng JSON trực tiếp trong HTML trang — bên trong các thẻ <script> dưới dạng biến JavaScript như pageManifesturqlCache. Trích xuất JSON này sẽ cho bạn dữ liệu sạch hơn (rating ở dạng số, ngày ở định dạng ISO) với ít request hơn và không cần render JavaScript.

Tìm JSON Nhúng Trong Source Trang

Ý tưởng chính rất đơn giản: bạn dùng requests.get() để lấy trang, rồi trích JSON từ HTML thô mà không cần render JavaScript.

1import requests
2import re
3import json
4> This paragraph contains content that cannot be parsed and has been skipped.
5url = "https://www.tripadvisor.com/Hotel_Review-g188590-d194317-Reviews-NH_City_Centre_Amsterdam.html"
6response = requests.get(url, headers=headers)
7> This paragraph contains content that cannot be parsed and has been skipped.
8**Cách tự tìm tên biến:** Mở bất kỳ trang khách sạn TripAdvisor nào trong Chrome, nhấp chuột phải → View Page Source, rồi Ctrl+F tìm `pageManifest` hoặc `urqlCache` hoặc `aggregateRating`. Dữ liệu ở đó, chỉ chờ được parse.
9### Phân Tích JSON Và Trích Xuất Dữ Liệu Có Cấu Trúc
10TripAdvisor còn nhúng dữ liệu schema.org `application/ld+json` — cách này thậm chí dễ hơn để lấy:
11```python
12from parsel import Selector
13sel = Selector(text=response.text)
14# Trích structured data JSON-LD
15json_ld_scripts = sel.xpath("//script[@type='application/ld+json']/text()").getall()
16for script in json_ld_scripts:
17    data = json.loads(script)
18    if isinstance(data, dict) and data.get("@type") in ["Hotel", "Restaurant", "TouristAttraction"]:
19        print(f"Name: {data.get('name')}")
20        print(f"Rating: {data.get('aggregateRating', {}).get('ratingValue')}")
21        print(f"Review Count: {data.get('aggregateRating', {}).get('reviewCount')}")
22        print(f"Price Range: {data.get('priceRange')}")
23        print(f"Address: {data.get('address', {}).get('streetAddress')}")
24        print(f"Coordinates: {data.get('geo', {}).get('latitude')}, {data.get('geo', {}).get('longitude')}")
25        break

Dữ liệu JSON-LD được nhúng trong HTML tĩnh và KHÔNG cần render JavaScript. Nó cung cấp tên cơ sở, điểm đánh giá tổng hợp, số review, địa chỉ, tọa độ, khoảng giá và URL ảnh — tất cả mà không cần phân tích một thẻ HTML nào.

Nếu cần dữ liệu phong phú hơn (review riêng lẻ, phân bổ đánh giá, danh sách tiện nghi), bạn cần object urqlCache:

1# Trích urqlCache cho dữ liệu review chi tiết
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    # Duyệt cache để tìm dữ liệu review
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

Đường dẫn JSON cụ thể đôi khi thay đổi khi TripAdvisor cập nhật frontend, nhưng cấu trúc chung — JSON-LD cho dữ liệu tóm tắt, urqlCache cho dữ liệu chi tiết — đã khá ổn định trong nhiều năm.

Phân Tích Ngược GraphQL API Của TripAdvisor (Nâng Cao)

Nếu cần trích xuất quy mô lớn, các endpoint GraphQL của TripAdvisor trả về dữ liệu có cấu trúc trực tiếp. Đây là cách nhanh nhất nhưng cần bảo trì nhiều nhất.

1import httpx
2import random
3import string
4def generate_request_id():
5    """Tạo giá trị cho header X-Requested-By"""
6    random_chars = ''.join(random.choices(string.ascii_letters + string.digits, k=180))
7    return f"TNI1625!{random_chars}"
8> This paragraph contains content that cannot be parsed and has been skipped.
9> This paragraph contains content that cannot be parsed and has been skipped.
10with httpx.Client() as client:
11    response = client.post(
12        "https://www.tripadvisor.com/data/graphql/ids",
13        json=search_payload,
14        headers=graphql_headers
15    )
16    if response.status_code == 200:
17        results = response.json()
18        print(json.dumps(results, indent=2)[:1000])
19    else:
20        print(f"GraphQL request failed: {response.status_code}")

Lấy review qua 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']}")

Lưu ý quan trọng: Các giá trị preRegisteredQueryId (như 84b17ed122fbdbd4 cho tìm kiếm và ef1a9f94012220d3 cho review) có thể hỏng khi TripAdvisor triển khai lại. Khi đó, request của bạn có thể thất bại âm thầm. Bạn sẽ cần tìm lại query ID bằng cách theo dõi request mạng trong DevTools của trình duyệt.

Vì Sao Cách Này Giảm Nhu Cầu Dùng Proxy

Công thức rất đơn giản. Với requests+BS4, scrape 100 trang chi tiết khách sạn cần 100 request. Với hidden JSON, mỗi request trả về toàn bộ dữ liệu bạn cần từ một lần tải trang duy nhất — không cần thêm request để mở review hoặc tải nội dung động. Với GraphQL, một API call có thể trả về 20 review cùng lúc. Ít request hơn = ít tiếp xúc với rate limiting hơn = ít cần xoay proxy hơn. Với các dự án nhỏ đến trung bình (dưới 1.000 trang), bạn có thể không cần proxy nếu thêm độ trễ hợp lý.

Scrape Khách Sạn, Nhà Hàng Và Điểm Tham Quan Bằng Một Script Có Thể Tái Sử Dụng

Bốn trên năm hướng dẫn cạnh tranh chỉ nói về khách sạn. Nhưng TripAdvisor có ba nhóm nội dung chính, và mẫu URL cùng các trường dữ liệu khác nhau giữa chúng. Đây là cách xây một hàm xử lý được cả ba.

Các Trường Dữ Liệu Theo Từng Danh Mục

TrườngKhách sạnNhà hàngĐiểm tham quan
Tên
Điểm đánh giá
Số review
Giá/Khung giáĐôi khi
Địa chỉ
Loại ẩm thực
Thời lượng/Loại tour
Tiện nghi
Tọa độ

Xây Dựng Hàm scrape_tripadvisor() Tái Sử Dụng

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    Scrape danh sách trên TripAdvisor cho khách sạn, nhà hàng hoặc điểm tham quan.
11> This paragraph contains content that cannot be parsed and has been skipped.
12> This paragraph contains content that cannot be parsed and has been skipped.
13> This paragraph contains content that cannot be parsed and has been skipped.
14    session = requests.Session()
15    session.headers.update(headers)
16    all_items = []
17    for page in range(num_pages):
18        offset = page * 30
19        if page == 0:
20            url = first_page_patterns[category].format(geo=location_id, name=location_name)
21        else:
22            url = url_patterns[category].format(geo=location_id, offset=offset, name=location_name)
23        response = session.get(url)
24        if response.status_code != 200:
25            print(f"  Page {page + 1}: Status {response.status_code}, stopping.")
26            break
27        soup = BeautifulSoup(response.text, "html.parser")
28        cards = soup.select('div[data-test-attribute="location-results-card"]')
29> This paragraph contains content that cannot be parsed and has been skipped.
30        print(f"  Page {page + 1}: {len(cards)} items found")
31        time.sleep(random.uniform(3, 7))
32    return pd.DataFrame(all_items)
33# Ví dụ sử dụng
34print("=== Hotels in Paris ===")
35hotels_df = scrape_tripadvisor("hotels", "187147", "Paris_Ile_de_France", num_pages=2)
36print(hotels_df.head())
37print("\n=== Restaurants in Rome ===")
38restaurants_df = scrape_tripadvisor("restaurants", "187791", "Rome_Lazio", num_pages=2)
39print(restaurants_df.head())
40print("\n=== Attractions in Barcelona ===")
41attractions_df = scrape_tripadvisor("attractions", "187497", "Barcelona_Catalonia", num_pages=2)
42print(attractions_df.head())

Một hàm, ba danh mục, không lặp code. Nếu TripAdvisor đổi selector, bạn chỉ cần sửa ở một chỗ.

Làm Gì Khi TripAdvisor Chặn Bạn (Xử Lý Sự Cố Anti-Bot)

Đây là phần tôi cần nhất khi mới bắt đầu scrape TripAdvisor, và cũng là phần mà không hướng dẫn cạnh tranh nào trình bày có hệ thống. TripAdvisor dùng đồng thời DataDome (phân tích hơn mỗi ngày) và Cloudflare WAF. Dưới đây là bảng chẩn đoán cho các lỗi phổ biến nhất:

This paragraph contains content that cannot be parsed and has been skipped.

Logic Thử Lại Với Exponential Backoff

Không bài viết cạnh tranh nào thực sự cho bạn đoạn code này. Đây là một hàm retry có thể tái sử dụng:

1import time
2import random
3import requests
4def fetch_with_retry(session, url, max_retries=4, base_delay=2, max_delay=60):
5    """
6    Lấy URL với exponential backoff và jitter.
7    Xoay User-Agent ở mỗi lần 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        # Xoay User-Agent khi retry
17        if attempt &gt; 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                # Tôn trọng header Retry-After nếu có
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> This paragraph contains content that cannot be parsed and has been skipped.
30            # Mã lỗi khác — không retry
31            print(f"  Unexpected status {response.status_code} for {url}")
32            return response
33> This paragraph contains content that cannot be parsed and has been skipped.
34    print(f"  All {max_retries} retries exhausted for {url}")
35    return None

Xoay Header, Proxy Và Session

Với scraping lâu dài, hãy duy trì một pool header và xoay chúng cùng lúc:

1import random
2> This paragraph contains content that cannot be parsed and has been skipped.
3PROXY_LIST = [
4    "http://user:pass@residential-proxy-1:port",
5    "http://user:pass@residential-proxy-2:port",
6    # Thêm nhiều residential proxy nữa
7]
8def get_rotated_session():
9    """Tạo session mới với header và proxy đã xoay."""
10    session = requests.Session()
11> This paragraph contains content that cannot be parsed and has been skipped.
12> This paragraph contains content that cannot be parsed and has been skipped.
13    return session

Loại proxy rất quan trọng. Proxy datacenter thường bị TripAdvisor chặn gần như ngay lập tức (HTTP 1020 Access Denied). Residential proxy là bắt buộc nếu muốn scrape ổn định — chúng đi qua các ISP tiêu dùng và nhìn giống người dùng thật. Hãy kỳ vọng chi phí khoảng 2,50–8,40 USD/GB tùy nhà cung cấp.

Xuất Và Lưu Dữ Liệu TripAdvisor Đã Scrape

Khi đã có dữ liệu, đưa nó vào định dạng hữu dụng là khá đơn giản.

Xuất CSV (Phổ Biến Nhất)

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ất quan trọng — nó giúp Excel hiển thị đúng ký tự không phải Latin (dấu tiếng Pháp, ký tự tiếng Trung, v.v.) khi mở file CSV.

Xuất JSON (Cho Dữ Liệu Lồng Nhau)

Khi review nằm bên dưới từng khách sạn, JSON sẽ giữ nguyên cấu trúc phân cấp:

1# Cấu trúc phân cấp
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# Với phân tích dạng phẳng, dùng 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)

Cách Hai File Cho Dữ Liệu Quan Hệ

Với bộ dữ liệu lớn, tôi thường dùng hai file CSV:

  • hotels.csv — Một dòng cho mỗi cơ sở lưu trú (dạng phẳng)
  • reviews.csv — Một dòng cho mỗi review, có property_id làm khóa ngoại

Cách này giúp dễ join trong pandas, nạp vào database hoặc import vào công cụ BI.

Nếu bạn không muốn xử lý logic xuất file, Thunderbit cho phép bạn sang Excel, Google Sheets, Airtable hoặc Notion — miễn phí, không cần code. Rất hữu ích khi bạn cần chia sẻ kết quả với đồng đội không rành kỹ thuật.

Mẹo Scrape TripAdvisor Có Trách Nhiệm Và Hiệu Quả

Scraping có trách nhiệm trong sáu ý ngắn gọn:

  • Kiểm tra robots.txt: TripAdvisor chặn hoàn toàn bot huấn luyện AI (GPTBot, ClaudeBot, v.v.). Các crawler thông thường sẽ gặp giới hạn theo từng đường dẫn. Xem tại tripadvisor.com/robots.txt.
  • Thêm độ trễ: 3–7 giây giữa các request là mức an toàn. Nhanh hơn 10–15 request/phút/IP sẽ kích hoạt rate limiting.
  • Chỉ scrape dữ liệu công khai. Đừng đăng nhập để truy cập nội dung bị giới hạn.
  • Lưu trữ dữ liệu an toàn và tuân thủ GDPR/CCPA nếu xử lý thông tin cá nhân (tên người đánh giá, v.v.).
  • Cân nhắc API chính thức của TripAdvisor nếu bạn cần dữ liệu quy mô thương mại. cung cấp thông tin doanh nghiệp cùng tối đa 5 review và 5 ảnh cho mỗi địa điểm — hạn chế nhưng hợp pháp và ổn định.
  • Lưu ý bối cảnh pháp lý: đã siết chặt các hạn chế scrape dựa trên điều khoản sử dụng trong toàn EU. Điều khoản dịch vụ của TripAdvisor nêu rõ cấm scraping. Hãy scrape có trách nhiệm và tự chịu rủi ro pháp lý.

Kết Luận

Đó là bức tranh đầy đủ.

  • Requests + BeautifulSoup là con đường đơn giản nhất. Cách này phù hợp với trang danh sách tĩnh, thiết lập nhẹ nhàng và rất nhanh. Hãy bắt đầu ở đây nếu bạn scrape dưới 100 trang và không cần nội dung render bằng JavaScript.
  • Selenium xử lý mọi thứ mà requests không làm được: nội dung động, nút “Read more”, banner cookie. Nó chậm hơn 5 lần và tốn tài nguyên, nhưng là lựa chọn duy nhất khi bạn cần tương tác với trang.
  • Hidden JSON / GraphQL là cách sạch và nhanh nhất. Nó cho dữ liệu có cấu trúc mà không cần parse HTML, giảm số request (và vì vậy giảm nhu cầu dùng proxy), đồng thời trả về dữ liệu ở định dạng sẵn sàng phân tích. Đổi lại, nó đòi hỏi reverse-engineering nhiều hơn lúc đầu và đôi khi phải bảo trì khi TripAdvisor thay đổi cấu trúc dữ liệu.

Hàm scrape_tripadvisor() tái sử dụng có thể xử lý khách sạn, nhà hàng và điểm tham quan. Bạn sẽ không cần một hướng dẫn thứ hai.

Và nếu giữa chừng bạn nhận ra mình không hợp viết code — hoặc chỉ cần 50 khách sạn trong bảng tính trước cuối ngày — có thể làm việc đó chỉ trong hai cú nhấp với nhận diện trường bằng AI, phân trang tự động và xuất miễn phí sang Excel hoặc Google Sheets. Không cần Python.

Nếu muốn tìm hiểu sâu hơn, chúng tôi có thêm nhiều bài hướng dẫn scraping trên .

FAQs

1. Scrape TripAdvisor có hợp pháp không?

Điều khoản dịch vụ của TripAdvisor cấm rõ ràng việc scraping. Tuy nhiên, tòa án nói chung đã cho rằng việc scrape dữ liệu công khai (không nằm sau đăng nhập) không vi phạm Computer Fraud and Abuse Act ở Mỹ. Dù vậy, phán quyết Ryanair của Tòa án EU năm 2025 đã siết chặt các hạn chế dựa trên điều khoản sử dụng tại châu Âu. Hãy chỉ scrape dữ liệu công khai, tôn trọng robots.txt, không đăng lại nội dung có bản quyền, và tham khảo tư vấn pháp lý nếu bạn dùng dữ liệu cho mục đích thương mại.

2. Có thể scrape TripAdvisor mà không dùng Python không?

Có. Các công cụ no-code như có thể scrape TripAdvisor trực tiếp từ trình duyệt của bạn với AI nhận diện trường và phân trang tự động. Bạn cũng có thể dùng browser extension, add-on cho Google Sheets hoặc các API scraping thương mại. Python cho bạn mức kiểm soát và linh hoạt cao nhất, nhưng không phải lựa chọn duy nhất.

3. Làm sao tránh bị chặn khi scrape TripAdvisor?

Các kỹ thuật quan trọng: dùng header thực tế và nhất quán (đặc biệt là User-AgentSec-CH-UA), xoay residential proxy (IP datacenter thường bị chặn ngay), chèn độ trễ ngẫu nhiên 3–7 giây giữa các request, dùng phương pháp hidden JSON để giảm tổng số request, triển khai retry logic với exponential backoff, và “làm nóng” session bằng cách vào homepage trước khi scrape các trang sâu.

4. Tôi có thể scrape những dữ liệu gì từ TripAdvisor?

Khách sạn, nhà hàng và điểm tham quan — bao gồm tên, điểm đánh giá, số review, khoảng giá, địa chỉ, tọa độ, tiện nghi (khách sạn), loại ẩm thực (nhà hàng), thời lượng tour (điểm tham quan), và toàn bộ nội dung review cùng điểm số, ngày tháng của từng review. Cách hidden JSON và GraphQL trả về dữ liệu giàu nhất trên mỗi request.

5. Mỗi ngày có thể scrape bao nhiêu trang TripAdvisor?

Với một IP và độ trễ hợp lý: khoảng 600–1.000 trang mỗi ngày. Với 20 residential proxy xoay vòng: khoảng 200.000–300.000 trang mỗi ngày khi dùng cách dựa trên request. Selenium chậm hơn — hãy kỳ vọng khoảng 8.000–12.000 trang mỗi ngày trên mỗi proxy. Hidden JSON/GraphQL cho bạn nhiều dữ liệu hơn trên mỗi request, nên có thể cần ít trang tổng cộng hơn để lấy cùng lượng thông tin.

Tìm hiểu thêm

Thử Thunderbit

Lấy leads và dữ liệu khác chỉ với 2 cú nhấp. Vận hành bằng AI.

Nhận Thunderbit Miễn phí