בשבוע שעבר ניסיתי לשלוף מ-TripAdvisor דירוגי מלונות ומספרי ביקורות עבור כ-200 נכסים בשלוש ערים אירופיות. הסקריפט הראשון שלי — requests.get() בסיסי עם כותרות ברירת מחדל — החזיר 403 Forbidden מהודר בכל בקשה ובקשה. אפילו לא בייט אחד של נתונים שימושיים.
TripAdvisor הוא אחד ממקורות הנתונים הציבוריים העשירים ביותר בתעשיית התיירות: מעל מיליארד ביקורות, יותר מ-8 מיליון עסקים רשומים, וכ-460 מיליון מבקרים ייחודיים בחודש. הוא משפיע על הוצאות תיירות של יותר מ-60 מיליארד דולר בשנה. אבל להשיג את הנתונים האלה בצורה תוכניתית? כאן מתחילים הקשיים. TripAdvisor משתמש בזיהוי בוטים של DataDome, ב-Cloudflare WAF, בטביעת אצבע של TLS ובאתגרי JavaScript — מערך הגנה רב-שכבתי שחוסם את רוב ניסיונות ה-scraping הנאיביים עוד לפני שהם מתחילים. המדריך הזה הוא בדיוק המשאב שהייתי רוצה שיהיה לי: השוואה חזיתית של שלוש גישות scraping ב-Python (ועוד אפשרות ללא קוד), קוד מלא לכל אחת מהן, פרק מובנה לפתרון בעיות אנטי-בוט, ותבניות לשימוש חוזר שעובדות על מלונות, מסעדות ואטרקציות. בין אם אתם מתחילים ב-Python ובין אם אתם מפתחים מנוסים, זה אמור לחסוך לכם המון שגיאות 403 מיותרות.
לא רוצים לכתוב קוד? כך תבצעו Scraping ל-TripAdvisor בדרך הקלה
אני רוצה להיות כן איתכם. הרבה אנשים שמחפשים "scrape TripAdvisor with Python" לא באמת מחויבים לרעיון של כתיבת קוד. הם פשוט רוצים את הנתונים — שמות מלונות, דירוגים, מספרי ביקורות, מחירים — בגיליון אלקטרוני, ומהר. אם זה נשמע לכם מוכר, יש דרך הרבה יותר קצרה.
Thunderbit הוא תוסף Chrome מבוסס AI שבנינו, שיודע לקרוא כל עמוד של TripAdvisor ולהציע אוטומטית את העמודות הנכונות לחילוץ. תהליך העבודה הוא באמת שתי קליקים:
- פתחו עמוד רישומים של TripAdvisor (למשל תוצאות חיפוש של "Hotels in Paris").
- לחצו על "AI Suggest Fields" בסרגל הצד של Thunderbit. ה-AI סורק את העמוד ומציע עמודות כמו שם מלון, דירוג, מספר ביקורות, מחיר ומיקום.
- לחצו על "Scrape". Thunderbit מחלץ נתונים מכל רישום בעמוד — ומטפל בעימוד (pagination) אוטומטית אם אתם צריכים עוד תוצאות.
- ייצאו ל-Excel, Google Sheets, Airtable או Notion. הייצוא חינמי בכל התוכניות.
Thunderbit עובד על מלונות, מסעדות ואטרקציות בלי שום שינוי בהגדרות — ה-AI מסתגל לכל מה שיש בעמוד. עבור תוצאות מעומדות הוא מזהה אוטומטית כפתורי "Next" וגלילה אינסופית. ומכיוון שהוא רץ בתוך דפדפן ה-Chrome האמיתי שלכם, הוא יורש את עוגיות הסשן ואת טביעת האצבע של הדפדפן שלכם — מה שנותן לו יתרון טבעי מול מנגנוני זיהוי בוטים.
אתם יכולים לנסות אותו עם תוסף Thunderbit ל-Chrome — המסלול החינמי נותן 6 עמודים בחודש, מספיק כדי לבחון את התהליך.
אם אתם צריכים שליטה תוכניתית, לוגיקת פענוח מותאמת אישית, או שאתם מתכננים לבצע scraping ל-10,000 עמודים ומעלה — Python היא הדרך הנכונה. המשיכו לקרוא.
למה לבצע Scraping ל-TripAdvisor עם Python?
לנתוני TripAdvisor יש השפעה עסקית ישירה ומדידה. מחקר של אוניברסיטת קורנל מצא שעלייה של נקודה אחת במדד הביקורות הגלובלי (Global Review Index) של מלון, בסולם של 100 נקודות, מובילה לעלייה של 0.89% בתעריף היומי הממוצע ולעלייה של 1.42% בהכנסה לחדר זמין. מחקר נפרד ב-ScienceDirect הראה שעלייה אקסוגנית של כוכב אחד בדירוג TripAdvisor מתורגמת ל-55,000–75,000 דולר הכנסה שנתית נוספת עבור מלון ממוצע. ביקורות הן לא סתם מדד יוקרתי — הן מנוע הכנסות.
כך צוותים שונים משתמשים בנתוני TripAdvisor:
| תרחיש שימוש | מי מרוויח | הנתונים הנדרשים |
|---|---|---|
| ניתוח מתחרים בענף המלונאות | רשתות מלונות, מנהלי הכנסות | דירוגים, מחירים, נפח ביקורות, שירותים |
| מחקר שוק למסעדות | קבוצות מסעדות, מותגי מזון | סוגי מטבח, טווחי מחירים, סנטימנט בביקורות |
| מעקב אחר מגמות באטרקציות | מפעילי טיולים, לשכות תיירות | דירוגי פופולריות, דפוסים עונתיים |
| ניתוח סנטימנט | חוקרים, אנליסטים | טקסט ביקורות מלא, דירוגי כוכבים, תאריכים |
| גיוס לידים | צוותי מכירות, סוכנויות נסיעות | שמות עסקים, פרטי קשר, מיקומים |
למה דווקא Python? שלוש סיבות. ראשית, האקוסיסטם: BeautifulSoup, Selenium, Playwright, Scrapy, httpx, pandas — ל-Python יש ספריות scraping וניתוח נתונים בשלות יותר מכל שפה אחרת. שנית, 71.7% ממפתחי ה-web scraping משתמשים ב-Python, ומכאן שיש יותר תמיכה קהילתית, יותר תשובות ב-StackOverflow ויותר מדריכים מעודכנים. שלישית, יתרון הצינור (pipeline): אפשר לבצע scraping עם BeautifulSoup, לנקות עם pandas, להריץ ניתוח סנטימנט עם Hugging Face Transformers ולבנות דשבורדים — הכול בשפה אחת. בלי מעברים בין סביבות.
שלוש דרכים לבצע Scraping ל-TripAdvisor עם Python (בהשוואה)
כל מדריך מתחרה בוחר גישה אחת ורץ איתה. זה לא עוזר כשאתם מנסים להחליט לפני שאתם כותבים קוד. הנה טבלת ההשוואה שהייתי רוצה שמישהו היה נותן לי:
| גישה | מהירות | תמיכה ב-JS | עמידות מול אנטי-בוט | מורכבות | הכי מתאים ל |
|---|---|---|---|---|---|
requests + BeautifulSoup | ⚡ מהיר (כ-120–200 עמודים/דקה בתיאוריה) | ❌ אין | ⚠️ נמוכה | קל | עמודי רישומים סטטיים, פרויקטים קטנים |
| Selenium / דפדפן Headless | 🐢 איטי (כ-8–20 עמודים/דקה) | ✅ מלאה | ⚠️ בינונית | בינוני | תוכן דינמי, לחיצות "Read more", באנרי עוגיות |
| JSON נסתר / GraphQL API | ⚡⚡ הכי מהיר (כ-200–600 עמודים/דקה בתיאוריה) | לא רלוונטי | ✅ גבוהה יותר | קשה | חילוץ ביקורות/מלונות בקנה מידה גדול |
| ללא קוד (Thunderbit) | ⚡ מהיר | ✅ מובנית | ✅ מובנית | הכי קל | לא-מפתחים, ייצוא חד-פעמי מהיר |
כמה הסתייגויות חשובות. המהירויות הגולמיות האלה הן תיאורטיות — מגבלות הקצב של TripAdvisor (כ-10–15 בקשות לדקה לכל IP) מצמצמות את התפוקה בפועל לכ-10 עמודים בדקה לכל IP, בלי קשר לגישה שבחרתם. שיטת ה-JSON הנסתר נותנת לכם הכי הרבה נתונים לכל בקשה, ומכאן פחות בקשות בסך הכול ופחות חשיפה למגבלות קצב. Selenium איטי פי 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) — פענוח HTMLselenium(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 (קריטי לעקיפת 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: Scraping ל-TripAdvisor עם Requests ו-BeautifulSoup
זו הגישה הפשוטה ביותר. היא עובדת היטב ל-scraping של עמודי רישומים (תוצאות חיפוש מלונות, רשימות מסעדות), שבהם הנתונים שאתם צריכים קיימים ב-HTML הסטטי. בלי דפדפן, בלי רינדור JavaScript, עם שימוש מינימלי במשאבים.
להבין את תבניות ה-URL של TripAdvisor
כתובות ה-URL של TripAdvisor עוקבות אחר תבניות צפויות לפי קטגוריה:
- מלונות:
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 (offset anchors) שמשובץ בתוך ה-URL. כל עמוד מציג 30 תוצאות:
- עמוד 1: כתובת הבסיס (בלי פרמטר
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
כדי לקבל ביקורות בכל השפות, הוסיפו ?filterLang=ALL בסוף ה-URL.
שליחת בקשות עם כותרות ריאליסטיות
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-Agent וה-Client Hints מסוג Sec-CH-UA שלכם עקביות ביניהן. אם אתם מצהירים שאתם Chrome 135 ב-User-Agent אבל ה-Sec-CH-UA שלכם אומר Chrome 120, תסומנו כחשודים. תמיד סובבו סטים שלמים של כותרות ביחד, לא כותרות בודדות.
פענוח רישומים עם BeautifulSoup
ברגע שקיבלתם תגובה מוצלחת, חלצו את הנתונים בעזרת BeautifulSoup. TripAdvisor משתמש במאפייני data-automation ו-data-test-attribute, שיציבים יותר משמות מחלקות CSS (שמשתנים לעיתים קרובות):
from bs4 import BeautifulSoup
soup = BeautifulSoup(response.text, "html.parser")
# מציאת כל כרטיסי רישום המלונות
cards = soup.select('div[data-test-attribute="location-results-card"]')
hotels = []
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
# קישור לעמוד הפירוט
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_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
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 משתמש בשמות מחלקות CSS מעורפלים (כמו FGwzt, yyzcQ) שמשתנים בכל עדכון של האתר. המאפיינים data-automation ו-data-test-target יציבים בהרבה. תמיד העדיפו מאפייני data על פני שמות מחלקות.
טיפול בעימוד
כדי לבצע scraping לכמה עמודים, רוצו בלולאה על פרמטר ההיסט עם השהיה מנומסת בין הבקשות:
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): # 5 העמודים הראשונים
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)) # השהיה אקראית כדי להימנע ממגבלות קצב
df = pd.DataFrame(all_hotels)
print(f"\nTotal hotels scraped: {len(df)}")
השורה time.sleep(random.uniform(3, 7)) חשובה. סף מגבלת הקצב של TripAdvisor הוא בערך 10–15 בקשות לדקה לכל IP. מעבר לזה תקבלו CAPTCHA או שגיאות 429.
המגבלות של הגישה הזו
איפה זה מתפרק? גישת requests+BS4 נכשלת כאשר:
- TripAdvisor מגיש תוכן שמרונדר ב-JavaScript (חלק מעמודי תוצאות החיפוש דורשים JS)
- טקסט הביקורת קטוע מאחורי כפתורי "Read more"
- אמצעי האנטי-בוט מסלימים לאתגרי JavaScript או CAPTCHA
- אתם צריכים נתונים שמופיעים רק אחרי רינדור בצד הלקוח (מחירים, זמינות)
בתרחישים האלה תצטרכו או Selenium (גישה 2) או את שיטת ה-JSON הנסתר (גישה 3).
גישה 2: Scraping ל-TripAdvisor עם Selenium (דפדפן Headless)
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") # שימוש במצב headless החדש (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)
# הסרת המאפיין webdriver מאובייקט navigator
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
})
האם זה מספיק ל-TripAdvisor? ל-scraping בקנה מידה קטן (מתחת ל-50 עמודים), ההגדרה הזו יחד עם proxies ביתיים בדרך כלל עובדת. לנפחים גדולים יותר ייתכן שתצטרכו את undetected-chromedriver או nodriver — הגנת DataDome של TripAdvisor מנתחת מעל 1,000 אותות בכל בקשה, כולל טביעות אצבע של TLS ש-Selenium רגיל לא מסוגל לזייף.
Scraping לתוצאות חיפוש מלונות עם Selenium
import time
import random
url = "https://www.tripadvisor.com/Hotels-g187147-Paris_Ile_de_France-Hotels.html"
driver.get(url)
# המתנה לטעינת כרטיסי המלונות
wait = WebDriverWait(driver, 15)
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div[data-test-attribute="location-results-card"]')))
# טיפול בחלונית ההסכמה לעוגיות (אם היא מופיעה)
try:
cookie_btn = driver.find_element(By.ID, "onetrust-accept-btn-handler")
cookie_btn.click()
time.sleep(1)
except:
pass # אין חלונית עוגיות
# חילוץ נתוני המלונות
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. הפער הזה, פי 8, מצטבר מהר מאוד כשאתם סורקים מאות עמודים.
הרחבת "Read More" ו-Scraping של ביקורות מלאות
עמודי ביקורות קוטעים ביקורות ארוכות מאחורי כפתור "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)
# לחיצה על כל כפתורי "Read more"
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
# חילוץ הביקורות
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")
# הדירוג מקודד במחלקה כמו "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")
הוספת סבב Proxy ל-Selenium
ל-scraping מתמשך תצטרכו סבב proxy. מכיוון ש-selenium-wire הוצא משימוש מאז ינואר 2024, השתמשו בתמיכת ה-proxy המובנית של Chrome:
# עם proxy שלא דורש אימות
proxy = "http://your-proxy-address:port"
options.add_argument(f"--proxy-server={proxy}")
# ל-proxies עם אימות, השתמשו בתוסף Chrome או בפרוטוקול BiDi של Selenium 4
כדי לסובב proxies בצורה תוכניתית, צרו מופע driver חדש עם proxy אחר לכל אצווה של בקשות. זה לא אלגנטי, אבל זה אמין.
גישה 3: שיטת ה-JSON הנסתר (לוותר על פענוח HTML לגמרי)
רוב המדריכים מדלגים על הגישה הזו לחלוטין, וחבל — היא המהירה והנקייה מבין השלוש. TripAdvisor משבץ נתונים מובְנים בתור JSON ישירות בתוך עמודי ה-HTML שלו — בתוך תגיות <script> כמשתני JavaScript כמו pageManifest ו-urqlCache. חילוץ ה-JSON הזה נותן לכם נתונים נקיים יותר (דירוגים כמספרים, תאריכים בפורמט ISO) עם פחות בקשות ובלי צורך ברינדור JavaScript.
איתור ה-JSON המשובץ בקוד המקור של העמוד
התובנה המרכזית: אפשר להשתמש ב-requests.get() פשוט כדי להביא את העמוד, ואז לחלץ את ה-JSON מה-HTML הגולמי בלי לרנדר 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)
# חילוץ בלוק ה-JSON של 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]}")
איך למצוא את שם המשתנה בעצמכם: פתחו כל עמוד מלון של TripAdvisor ב-Chrome, לחצו קליק ימני ← View Page Source, ואז Ctrl+F על pageManifest או urqlCache או aggregateRating. הנתונים שם, מחכים שיפענחו אותם.
פענוח ה-JSON וחילוץ נתונים מובְנים
TripAdvisor גם משבץ נתוני schema.org מסוג application/ld+json, שקל עוד יותר לחלץ:
from parsel import Selector
sel = Selector(text=response.text)
# חילוץ נתונים מובְנים מסוג JSON-LD
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. הם נותנים לכם את שם הנכס, את הדירוג המצטבר, את מספר הביקורות, את הכתובת, את הקואורדינטות, את טווח המחירים ואת כתובות התמונות — הכול בלי לפענח ולו תגית HTML אחת.
לנתונים עשירים יותר (ביקורות בודדות, פילוח דירוגים, רשימות שירותים) תצטרכו את אובייקט ה-urqlCache:
# חילוץ urqlCache לנתוני ביקורות מפורטים
cache_match = re.search(r'"urqlCache"\s*:\s*({.+?})\s*,\s*"redux"', response.text)
if cache_match:
cache_data = json.loads(cache_match.group(1))
# ניווט במטמון כדי למצוא את נתוני הביקורות
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 לנתונים מפורטים — יציב כבר שנים.
הנדסה לאחור של ה-GraphQL API של TripAdvisor (מתקדם)
לחילוץ בקנה מידה גדול, נקודות הקצה של GraphQL ב-TripAdvisor מחזירות נתונים מובְנים ישירות. זו השיטה המהירה ביותר, אבל היא דורשת את התחזוקה הרבה ביותר.
import httpx
import random
import string
def generate_request_id():
"""יצירת הערך של הכותרת X-Requested-By"""
random_chars = ''.join(random.choices(string.ascii_letters + string.digits, k=180))
return f"TNI1625!{random_chars}"
# חיפוש מלונות בפריז
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 מבצע פריסה מחדש. כשזה קורה, הבקשות שלכם ייכשלו בשקט. תצטרכו לגלות מחדש את מזהי השאילתות באמצעות מעקב אחר בקשות הרשת ב-DevTools של הדפדפן.
למה השיטה הזו מפחיתה את הצורך ב-Proxies
החשבון פשוט. עם requests+BS4, scraping ל-100 עמודי פירוט של מלונות דורש 100 בקשות. עם שיטת ה-JSON הנסתר, כל בקשה מחזירה את כל הנתונים שאתם צריכים מטעינת עמוד אחת — בלי בקשות נוספות להרחבת ביקורות או לטעינת תוכן דינמי. עם GraphQL, קריאת API אחת יכולה להחזיר 20 ביקורות בבת אחת. פחות בקשות = פחות חשיפה למגבלות קצב = פחות צורך בסבב proxy. בפרויקטים קטנים עד בינוניים (מתחת ל-1,000 עמודים), ייתכן שלא תצטרכו proxies בכלל אם תוסיפו השהיות הגיוניות.
Scraping למלונות, מסעדות ואטרקציות בסקריפט אחד לשימוש חוזר
ארבעה מתוך חמישה מדריכים מתחרים מכסים רק מלונות. אבל ל-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 בקטגוריות מלונות, מסעדות או אטרקציות.
Args:
category: "hotels", "restaurants" או "attractions"
location_id: מזהה geo של TripAdvisor (לדוגמה, "187147" לפריז)
location_name: שם ידידותי ל-URL (לדוגמה, "Paris_Ile_de_France")
num_pages: מספר העמודים לסריקה
"""
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)
# דוגמאות שימוש
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 חוסם אתכם (פתרון בעיות אנטי-בוט)
זה הפרק שהכי הייתי צריך כשהתחלתי לבצע scraping ל-TripAdvisor, וזה הפרק שאף מדריך מתחרה לא מספק בצורה מסודרת. TripAdvisor משתמש ב-DataDome (שמנתח מעל 5 טריליון נקודות נתונים ביום) וב-Cloudflare WAF יחד. הנה טבלת אבחון למצבי הכשל הנפוצים ביותר:
| תסמין | הסיבה הסבירה | הפתרון |
|---|---|---|
| תגובת HTTP 403 | כותרות חסרות או חשודות; אתגר JS של Cloudflare | הגדירו כותרות User-Agent, Accept-Language, Referer ו-Sec-CH-UA ריאליסטיות. ודאו עקביות בין הכותרות. |
| עמוד CAPTCHA במקום נתונים | מגבלת קצב או טביעת אצבע של דפדפן | סובבו proxies ביתיים, הוסיפו השהיות אקראיות (2–7 שניות בין בקשות) |
| HTML ריק או גוף עמוד ריק | ה-JavaScript לא מרונדר על ידי requests | עברו ל-Selenium או חלצו מה-JSON הנסתר בקוד המקור של העמוד |
| ביקורות חלקיות / "Read more" לא נפתח | התוכן נטען באירוע לחיצה | השתמשו ב-.click() של Selenium או חלצו מבלוק ה-JSON המשובץ |
| ביקורות בשפה אחת בלבד | חסר פרמטר שפה | הוסיפו ?filterLang=ALL בסוף כתובת עמוד הביקורות |
| הנתונים מפסיקים להיטען אחרי N עמודים | מגבלת קצב מבוססת סשן | סובבו סשנים, נקו עוגיות בין אצוות |
| HTTP 1020 Access Denied | ה-IP/ASN נחסם על ידי Cloudflare | עברו מ-proxies של דאטה-סנטר ל-proxies ביתיים |
| לולאת אתגרים (CAPTCHA אינסופי) | שמירת עוגיות שבורה | חממו סשנים בכניסה לעמוד הבית תחילה; שמרו על cookie jar |
לוגיקת ניסיון חוזר עם השהיה מעריכית
אף מאמר מתחרה לא מציג בפועל את הקוד הזה. הנה פונקציית retry לשימוש חוזר:
import time
import random
import requests
def fetch_with_retry(session, url, max_retries=4, base_delay=2, max_delay=60):
"""
שליפת URL עם השהיה מעריכית (exponential backoff) ורעש אקראי.
מסובבת User-Agent בכל ניסיון חוזר.
"""
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):
# סבב User-Agent בניסיון חוזר
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:
# כיבוד כותרת Retry-After אם היא קיימת
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
# קודי שגיאה אחרים — לא לנסות שוב
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
סבב כותרות, Proxies וסשנים
ל-scraping מתמשך, החזיקו מאגר של סטי כותרות וסובבו אותם כיחידה אחת:
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",
# הוסיפו עוד proxies ביתיים
]
def get_rotated_session():
"""יצירת סשן חדש עם כותרות ו-proxy מסובבים."""
session = requests.Session()
# בחירת סט כותרות אקראי
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)
# בחירת proxy אקראי
if PROXY_LIST:
proxy = random.choice(PROXY_LIST)
session.proxies = {"http": proxy, "https": proxy}
return session
סוג ה-proxy קובע. proxies של דאטה-סנטר נחסמים כמעט מיידית על ידי TripAdvisor (HTTP 1020 Access Denied). proxies ביתיים הם חובה ל-scraping מתמשך — הם מנתבים דרך ספקי אינטרנט ביתיים ואי אפשר להבחין בינם לבין משתמשים אמיתיים. צפו לשלם 2.50–8.40 דולר ל-GB, תלוי בספק.
ייצוא ואחסון של נתוני 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 יציג נכון תווים שאינם לטיניים (סימנים דיאקריטיים בצרפתית, תווים סיניים וכדומה) בפתיחת ה-CSV.
ייצוא ל-JSON (לנתונים מקוננים)
כשיש לכם ביקורות מקוננות מתחת למלונות, JSON משמר את ההיררכיה:
# מבנה היררכי
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": "..."},
]
}
# לניתוח שטוח, השתמשו ב-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כמפתח זר
כך קל לבצע join ב-pandas, לטעון למסד נתונים או לייבא לכלי BI.
אם אתם לא רוצים להתעסק עם כל לוגיקת הייצוא הזו, Thunderbit מאפשר לכם לייצא נתונים שנאספו ישירות ל-Excel, Google Sheets, Airtable או Notion — הכול בחינם, הכול בלי קוד. שימושי כשאתם צריכים לשתף תוצאות עם עמיתים לא-טכניים.
טיפים ל-Scraping אחראי ויעיל של TripAdvisor
Scraping אחראי בשש נקודות:
- בדקו את
robots.txt: קובץ ה-robots.txt של TripAdvisor חוסם לחלוטין בוטים לאימון AI (GPTBot, ClaudeBot וכדומה). זחלנים סטנדרטיים נתקלים במגבלות סלקטיביות על נתיבים. בדקו אותו בכתובתtripadvisor.com/robots.txt. - הוסיפו השהיות: 3–7 שניות בין בקשות זה טווח בטוח. מעל 10–15 בקשות לדקה לכל IP מפעיל מגבלות קצב.
- אספו רק נתונים ציבוריים. אל תתחברו לחשבון כדי לגשת לתוכן מוגבל.
- אחסנו נתונים בצורה מאובטחת וצייתו ל-GDPR/CCPA אם אתם מטפלים במידע אישי (שמות מבקרים וכדומה).
- שקלו את ה-API הרשמי של TripAdvisor אם אתם צריכים נתונים בהיקף מסחרי. פורטל המפתחים מציע גישה לפרטי עסקים וכן עד 5 ביקורות ו-5 תמונות לכל מיקום — מוגבל, אבל חוקי ויציב.
- היו מודעים להקשר המשפטי: פסיקת בית הדין האירופי בעניין Ryanair (דצמבר 2025) חיזקה איסורי scraping המבוססים על תנאי שימוש ברחבי האיחוד האירופי. תנאי השימוש של TripAdvisor אוסרים scraping במפורש. בצעו scraping באחריות ועל אחריותכם בלבד.
לסיכום
זו התמונה המלאה.
- Requests + BeautifulSoup היא הדרך הפשוטה ביותר. היא עובדת לעמודי רישומים סטטיים, דורשת הכנה מינימלית והיא מהירה. התחילו כאן אם אתם סורקים פחות מ-100 עמודים ולא צריכים תוכן שמרונדר ב-JavaScript.
- Selenium מטפל בכל מה ש-requests לא יכול: תוכן דינמי, כפתורי "Read more", באנרי עוגיות. הוא איטי פי 5 וכבד במשאבים, אבל הוא האפשרות היחידה כשצריך לתקשר עם העמוד.
- JSON נסתר / GraphQL היא הגישה הנקייה והמהירה ביותר. היא נותנת לכם נתונים מובְנים בלי לפענח HTML, מצמצמת את מספר הבקשות (ולכן את הצורך ב-proxies), ומחזירה נתונים בפורמטים מוכנים לניתוח. היא דורשת יותר הנדסה לאחור בהתחלה ותחזוקה מדי פעם כש-TripAdvisor משנה את מבנה הנתונים שלו.
הפונקציה scrape_tripadvisor() לשימוש חוזר מכסה מלונות, מסעדות ואטרקציות. לא אמור להיות לכם צורך במדריך שני.
ואם באמצע המדריך תחליטו שכתיבת קוד היא לא בשבילכם — או שאתם פשוט צריכים 50 מלונות בגיליון אלקטרוני עד סוף היום — תוסף Chrome של Thunderbit יעשה את זה בשתי קליקים, עם זיהוי שדות מבוסס AI, עימוד אוטומטי וייצוא חינמי ל-Excel או Google Sheets. בלי Python.
אם אתם רוצים להעמיק, יש לנו עוד מדריכי scraping בבלוג של Thunderbit ובערוץ ה-YouTube שלנו.
שאלות נפוצות
1. האם חוקי לבצע scraping ל-TripAdvisor?
תנאי השימוש של TripAdvisor אוסרים scraping במפורש. עם זאת, בתי משפט קבעו באופן כללי ש-scraping של נתונים זמינים לציבור (שאינם מאחורי התחברות) אינו מפר את חוק ההונאה והשימוש לרעה במחשבים (CFAA) בארצות הברית. יחד עם זאת, פסיקת בית הדין האירופי בעניין Ryanair משנת 2025 חיזקה את המגבלות המבוססות על תנאי שימוש באירופה. אספו רק נתונים ציבוריים, כבדו את robots.txt, אל תפרסמו מחדש תוכן המוגן בזכויות יוצרים, והתייעצו עם עורך דין אם אתם עושים בנתונים שימוש מסחרי.
2. האם אפשר לבצע scraping ל-TripAdvisor בלי Python?
כן. כלים ללא קוד כמו Thunderbit יכולים לבצע scraping ל-TripAdvisor ישירות מהדפדפן שלכם, עם זיהוי שדות מבוסס AI ועימוד אוטומטי. אפשר גם להשתמש בתוספי דפדפן, בתוספות ל-Google Sheets או ב-API מסחריים ל-scraping. Python נותנת לכם את השליטה והגמישות הרבות ביותר, אבל היא לא האפשרות היחידה.
3. איך נמנעים מחסימה בזמן scraping ל-TripAdvisor?
הטקטיקות המרכזיות: השתמשו בכותרות ריאליסטיות ועקביות (במיוחד User-Agent ו-Sec-CH-UA), סובבו proxies ביתיים (כתובות IP של דאטה-סנטר נחסמות מיידית), הוסיפו השהיות אקראיות של 3–7 שניות בין בקשות, השתמשו בשיטת ה-JSON הנסתר כדי לצמצם את סך הבקשות, ממשו לוגיקת ניסיון חוזר עם השהיה מעריכית, וחממו סשנים בכניסה לעמוד הבית לפני סריקת עמודים עמוקים.
4. אילו נתונים אפשר לאסוף מ-TripAdvisor?
מלונות, מסעדות ואטרקציות — כולל שמות, דירוגים, מספרי ביקורות, טווחי מחירים, כתובות, קואורדינטות, שירותים ומתקנים (מלונות), סוגי מטבח (מסעדות), משכי סיורים (אטרקציות), וטקסט ביקורות מלא עם דירוגים ותאריכים בודדים. גישות ה-JSON הנסתר וה-GraphQL מחזירות את הנתונים העשירים ביותר לכל בקשה.
5. כמה עמודים אפשר לאסוף מ-TripAdvisor ביום?
עם IP יחיד והשהיות הגיוניות: בערך 600–1,000 עמודים ביום. עם 20 proxies ביתיים מתחלפים: כ-200,000–300,000 עמודים ביום בגישות מבוססות-בקשות. Selenium איטי יותר — צפו ל-8,000–12,000 עמודים ביום לכל proxy. גישת ה-JSON הנסתר/GraphQL נותנת לכם הכי הרבה נתונים לכל בקשה, כך שייתכן שתצטרכו הרבה פחות עמודים בסך הכול כדי לקבל את אותה כמות מידע.
למידע נוסף


