في الأسبوع الماضي، حاولت أجمع تقييمات الفنادق وعدد المراجعات لحوالي 200 منشأة موزعة على ثلاث مدن أوروبية من TripAdvisor. أول سكربت كتبته — مجرد requests.get() بسيط مع ترويسات افتراضية — رجع لي رد 403 Forbidden في كل مرة. ولا بايت واحد من البيانات المفيدة.
TripAdvisor يُعد من أغنى مصادر البيانات العامة في قطاع السفر: أكثر من 1 مليار مراجعة، وأكثر من 8 ملايين إدراج تجاري، وحوالي 460 مليون زائر شهري فريد. وهو يؤثر في إنفاق سنوي على السفر يتجاوز 60 مليار دولار. لكن استخراج هذه البيانات برمجيًا؟ هنا تبدأ القصة الصعبة. TripAdvisor يستخدم DataDome لاكتشاف الروبوتات، وCloudflare WAF، وبصمات TLS، وتحديات JavaScript — يعني طبقة حماية متعددة توقف أغلب محاولات الاستخراج الساذجة قبل ما تبدأ أصلًا. هذا الدليل هو المرجع اللي كنت أتمنى ألاقيه: مقارنة مباشرة بين ثلاث طرق باستخدام Python (ومعها خيار بلا كود)، مع كود كامل لكل طريقة، وقسم مرتب لاستكشاف مشاكل الحظر، وأنماط قابلة لإعادة الاستخدام تشتغل مع الفنادق والمطاعم والمعالم السياحية. سواء كنت مبتدئ في Python أو مطور متمرس، فهو سيوفر عليك كثير من محاولات 403 المهدرة.
لا تريد كتابة كود؟ استخرج TripAdvisor بالطريقة الأسهل
أبغى أكون واضح من البداية. كثير من الناس اللي يبحثون عن "scrape TripAdvisor with Python" ما يكون هدفهم الحقيقي كتابة الكود بقدر ما هم يريدون البيانات نفسها — أسماء الفنادق، التقييمات، عدد المراجعات، الأسعار — في جدول وبأسرع وقت. إذا هذا هو اللي تحتاجه، فهناك طريق أقصر بكثير.
Thunderbit هو امتداد Chrome مدعوم بالذكاء الاصطناعي بنيناه، ويقدر يقرأ أي صفحة من TripAdvisor ويقترح الأعمدة المناسبة للاستخراج تلقائيًا. وسير العمل فعليًا يتم في خطوتين فقط:
- افتح صفحة قائمة في TripAdvisor (مثل نتائج البحث عن "Hotels in Paris").
- انقر على "AI Suggest Fields" في الشريط الجانبي لـ Thunderbit. سيقوم الذكاء الاصطناعي بمسح الصفحة واقتراح أعمدة مثل اسم الفندق، التقييم، عدد المراجعات، السعر، والموقع.
- انقر على "Scrape." سيستخرج Thunderbit البيانات من كل إدراج في الصفحة — ويتعامل مع الترقيم الصفحي تلقائيًا إذا احتجت نتائج أكثر.
- صدّر إلى Excel أو Google Sheets أو Airtable أو Notion. التصدير مجاني في كل خطة.
يعمل Thunderbit مع الفنادق والمطاعم والمعالم السياحية بدون أي تغييرات في الإعدادات — فالذكاء الاصطناعي يتكيف مع ما يظهر في الصفحة. وبالنسبة للنتائج متعددة الصفحات، يكتشف تلقائيًا أزرار "Next" والتمرير اللانهائي. وبما أنه يعمل داخل متصفح Chrome الحقيقي عندك، فهو يرث ملفات تعريف الارتباط الخاصة بجلساتك وبصمة المتصفح، وهذا يعطيه أفضلية طبيعية ضد أنظمة كشف الروبوتات.
تقدر تجربه عبر Thunderbit Chrome Extension — والخطة المجانية تمنحك 6 صفحات شهريًا، وهذا كافٍ لاختبار سير العمل.
إذا كنت تحتاج تحكم برمجي، أو منطق تحليل مخصص، أو تخطط لاستخراج أكثر من 10,000 صفحة، فـ Python هو الخيار المناسب. تابع القراءة.
لماذا استخراج TripAdvisor باستخدام Python؟
بيانات TripAdvisor لها أثر تجاري مباشر ويمكن قياسه. وجدت دراسة من Cornell University أن زيادة نقطة واحدة في مؤشر Global Review Index الخاص بالفندق من أصل 100 نقطة تؤدي إلى زيادة 0.89% في متوسط السعر اليومي، وارتفاع 1.42% في الإيراد لكل غرفة متاحة. كما أظهرت دراسة أخرى من ScienceDirect أن زيادة نجمة واحدة في تقييم TripAdvisor — إذا جاءت من عامل خارجي — تترجم إلى 55,000–75,000 دولار إضافية من الإيراد السنوي لفندق متوسط. المراجعات ليست مجرد أرقام شكلية، بل محرّكات حقيقية للإيرادات.
إليك كيف تستخدم الفرق المختلفة بيانات TripAdvisor:
| حالة الاستخدام | من يستفيد | البيانات المطلوبة |
|---|---|---|
| تحليل منافسي الفنادق | سلاسل الفنادق، مدراء الإيرادات | التقييمات، الأسعار، حجم المراجعات، وسائل الراحة |
| أبحاث سوق المطاعم | مجموعات المطاعم، العلامات الغذائية | أنواع المأكولات، نطاقات الأسعار، مشاعر المراجعات |
| تتبع اتجاهات المعالم | مشغلو الرحلات، هيئات السياحة | مؤشرات الشعبية، الأنماط الموسمية |
| تحليل المشاعر | الباحثون، محللو البيانات | نصوص المراجعات كاملة، عدد النجوم، التواريخ |
| توليد العملاء المحتملين | فرق المبيعات، وكالات السفر | أسماء الأنشطة التجارية، معلومات الاتصال، المواقع |
لماذا Python تحديدًا؟ ثلاثة أسباب. أولًا، المنظومة البيئية: BeautifulSoup وSelenium وPlaywright وScrapy وhttpx وpandas — لدى Python مكتبات أكثر نضجًا للاستخراج وتحليل البيانات مقارنة بأي لغة أخرى. ثانيًا، 71.7% من مطوري web scraping يستخدمون Python، وهذا يعني دعمًا مجتمعيًا أكبر، وإجابات أكثر على StackOverflow، وأدلة أحدث. ثالثًا، ميزة خط الأنابيب: يمكنك الاستخراج باستخدام BeautifulSoup، والتنظيف عبر pandas، وتحليل المشاعر بواسطة Hugging Face Transformers، وبناء لوحات بيانات — كل ذلك في لغة واحدة، من دون الحاجة للتنقل بين أدوات مختلفة.
ثلاث طرق لاستخراج TripAdvisor باستخدام Python (مقارنة)
كل دليل منافس عادةً يختار طريقة واحدة ويمضي معها. وهذا ما يفيدك كثيرًا عندما تحاول اتخاذ القرار قبل كتابة الكود. إليك جدول المقارنة الذي كنت أتمنى أن يقدّمه لي أحدهم:
| الطريقة | السرعة | دعم JavaScript | مقاومة الحظر | التعقيد | الأفضل لـ |
|---|---|---|---|---|---|
requests + BeautifulSoup | ⚡ سريع (~120–200 صفحة/دقيقة خام) | ❌ لا يوجد | ⚠️ منخفض | سهل | صفحات القوائم الثابتة، المشاريع الصغيرة |
| Selenium / متصفح بدون واجهة | 🐢 بطيء (~8–20 صفحة/دقيقة) | ✅ كامل | ⚠️ متوسط | متوسط | المحتوى الديناميكي، النقر على "Read more"، لافتات الكوكيز |
| JSON مخفي / GraphQL API | ⚡⚡ الأسرع (~200–600 صفحة/دقيقة خام) | غير مطلوب | ✅ أعلى | صعب | استخراج واسع النطاق للمراجعات/الفنادق |
| بلا كود (Thunderbit) | ⚡ سريع | ✅ مدمج | ✅ مدمج | الأسهل | غير المطورين، عمليات التصدير السريعة لمرة واحدة |
بعض الملاحظات المهمة. هذه السرعات الخام نظرية — حدود المعدل لدى TripAdvisor (حوالي 10–15 طلبًا في الدقيقة لكل IP) تقيد الإنتاجية الفعلية إلى نحو 10 صفحات/دقيقة لكل IP بغض النظر عن الطريقة. أسلوب JSON المخفي يمنحك أكبر قدر من البيانات في كل طلب، ما يعني عدد طلبات أقل وتعريضًا أقل لحدود المعدل. أما Selenium فهو أبطأ بخمس مرات تقريبًا من الطرق المعتمدة على الطلبات في المقاييس الواقعية، لكنه الخيار الوحيد عندما تحتاج إلى النقر على الأزرار أو عرض 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: استخراج TripAdvisor باستخدام Requests وBeautifulSoup
هذه هي أبسط طريقة. تعمل بشكل جيد مع صفحات القوائم (نتائج بحث الفنادق، قوائم المطاعم) حيث تكون البيانات المطلوبة موجودة داخل HTML الثابت. لا يوجد متصفح، ولا عرض JavaScript، واستهلاك منخفض للموارد.
فهم أنماط روابط TripAdvisor
تتبع روابط 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) ويُدرج في الرابط. كل صفحة تعرض 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 إلى الرابط.
إرسال الطلبات بترويسات واقعية
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 ورؤوس 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"وجدنا {len(hotels)} فندقًا في هذه الصفحة")
for h in hotels[:3]:
print(h)
ملاحظة حول المحددات: يستخدم TripAdvisor أسماء فئات CSS مشفرة مثل FGwzt وyyzcQ تتغير مع كل تحديث للموقع. أما سمات data-automation وdata-test-target فهي أكثر ثباتًا بكثير. قدّم دائمًا سمات البيانات على أسماء الفئات.
التعامل مع الترقيم الصفحي
لاستخراج عدة صفحات، كرّر عبر معامل الإزاحة مع تأخير لطيف بين الطلبات:
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 + 1}: حصلنا على الحالة {response.status_code}، سنوقف.")
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 + 1}: تم العثور على {len(cards)} فندقًا")
time.sleep(random.uniform(3, 7)) # تأخير عشوائي لتفادي حد المعدل
df = pd.DataFrame(all_hotels)
print(f"\nإجمالي الفنادق المستخرجة: {len(df)}")
الـ time.sleep(random.uniform(3, 7)) مهم جدًا. عتبة حد المعدل لدى TripAdvisor تقارب 10–15 طلبًا في الدقيقة لكل IP. السرعة الأعلى من ذلك تؤدي إلى CAPTCHAs أو أخطاء 429.
حدود هذه الطريقة
أين تنهار هذه الطريقة؟ يفشل أسلوب requests+BS4 عندما:
- TripAdvisor يقدم محتوى معروضًا عبر JavaScript (بعض صفحات النتائج تحتاج JS)
- نصوص المراجعات تكون مختصرة خلف زر "Read more"
- إجراءات الحماية تتصاعد إلى تحديات JavaScript أو CAPTCHAs
- تحتاج بيانات لا تظهر إلا بعد العرض من جهة العميل (مثل الأسعار أو التوفر)
في هذه الحالات، تحتاج إما إلى Selenium (الطريقة 2) أو إلى أسلوب JSON المخفي (الطريقة 3).
الطريقة 2: استخراج TripAdvisor باستخدام Selenium (متصفح بدون واجهة)
يشغّل Selenium متصفحًا حقيقيًا، وهذا يعني أنه يستطيع عرض JavaScript، والنقر على الأزرار، والتعامل مع لافتات الموافقة على الكوكيز، والتفاعل مع المحتوى الديناميكي. الثمن: أبطأ بنحو 5 مرات ويستهلك 300–500 ميغابايت من الذاكرة لكل نسخة متصفح.
إعداد 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؟ بالنسبة للاستخراج على نطاق صغير (أقل من 50 صفحة)، غالبًا ينجح هذا الإعداد مع بروكسيات سكنية. أما في الأحجام الأكبر، فقد تحتاج إلى undetected-chromedriver أو nodriver — لأن حماية DataDome لدى TripAdvisor تحلل أكثر من 1,000 إشارة لكل طلب، بما في ذلك بصمات TLS التي لا يستطيع Selenium العادي انتحالها.
استخراج نتائج بحث الفنادق باستخدام 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"تم استخراج {len(hotels)} فندقًا")
for h in hotels[:3]:
print(h)
استغرق هذا نحو 8 ثوانٍ لصفحة واحدة على جهازي — مقارنة بأقل من ثانية واحدة مع requests+BS4. هذا الفارق البالغ 8 مرات يتراكم بسرعة عندما تستخرج مئات الصفحات.
توسيع "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)
# انقر على كل أزرار "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"تم استخراج {len(reviews)} مراجعات")
إضافة تدوير البروكسيات إلى Selenium
للاستخراج المستمر، ستحتاج إلى تدوير البروكسيات. وبما أن selenium-wire تم إيقافه منذ يناير 2024، فاستخدم دعم البروكسي المدمج في Chrome:
# مع بروكسي لا يحتاج إلى مصادقة
proxy = "http://your-proxy-address:port"
options.add_argument(f"--proxy-server={proxy}")
# بالنسبة للبروكسيات التي تتطلب مصادقة، استخدم إضافة Chrome أو بروتوكول BiDi في Selenium 4
أما لتدوير البروكسيات برمجيًا، فأنشئ نسخة جديدة من المتصفح مع بروكسي مختلف لكل دفعة من الطلبات. قد لا يكون هذا أنيقًا، لكنه موثوق.
الطريقة 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("وجدنا بيانات pageManifest")
print(f"المفاتيح: {list(page_data.keys())[:10]}")
كيف تجد اسم المتغير بنفسك؟ افتح أي صفحة فندق في TripAdvisor داخل Chrome، ثم انقر بزر الماوس الأيمن → عرض مصدر الصفحة، وبعدها 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"الاسم: {data.get('name')}")
print(f"التقييم: {data.get('aggregateRating', {}).get('ratingValue')}")
print(f"عدد المراجعات: {data.get('aggregateRating', {}).get('reviewCount')}")
print(f"نطاق السعر: {data.get('priceRange')}")
print(f"العنوان: {data.get('address', {}).get('streetAddress')}")
print(f"الإحداثيات: {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"وجدنا إدخالًا في cache للمراجعات: {key[:50]}...")
break
قد تتغير المسارات الدقيقة للـ JSON أحيانًا عندما يحدّث TripAdvisor واجهة الواجهة الأمامية، لكن البنية العامة — JSON-LD للبيانات الملخّصة وurqlCache للبيانات التفصيلية — بقيت مستقرة لسنوات.
عكس هندسة GraphQL لدى 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: {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}")
for r in reviews[:3]:
print(f" [{r['rating']}/5] {r['title']} - {r['createdDate']}")
تنبيه مهم: قيم preRegisteredQueryId (مثل 84b17ed122fbdbd4 للبحث وef1a9f94012220d3 للمراجعات) قد تتعطل عندما يعيد TripAdvisor النشر. وعندها ستفشل طلباتك بصمت. ستحتاج إلى إعادة اكتشاف معرفات الاستعلام عبر مراقبة طلبات الشبكة في أدوات المطور بالمتصفح.
لماذا تقلل هذه الطريقة الحاجة إلى البروكسيات
الحساب بسيط. مع requests+BS4، استخراج 100 صفحة تفاصيل فندق يتطلب 100 طلب. أما مع أسلوب JSON المخفي، فإن كل طلب يعيد كل البيانات التي تحتاجها من تحميل صفحة واحدة — من دون طلبات إضافية لتوسيع المراجعات أو تحميل المحتوى الديناميكي. ومع GraphQL، يمكن لنداء API واحد أن يعيد 20 مراجعة دفعة واحدة. عدد طلبات أقل = تعرّض أقل لحدود المعدل = حاجة أقل لتدوير البروكسيات. بالنسبة للمشاريع الصغيرة إلى المتوسطة (أقل من 1,000 صفحة)، قد لا تحتاج إلى بروكسيات أصلًا إذا أضفت تأخيرات معقولة.
استخراج الفنادق والمطاعم والمعالم عبر سكربت واحد قابل لإعادة الاستخدام
أربعة من كل خمسة أدلة منافسة تركز فقط على الفنادق. لكن TripAdvisor لديه ثلاث فئات محتوى أساسية، وأنماط الروابط والحقول تختلف بينها. إليك كيف تبني دالة واحدة تتعامل مع الثلاثة.
الحقول المتاحة حسب الفئة
| الحقل | الفنادق | المطاعم | المعالم السياحية |
|---|---|---|---|
| الاسم | ✅ | ✅ | ✅ |
| التقييم | ✅ | ✅ | ✅ |
| عدد المراجعات | ✅ | ✅ | ✅ |
| السعر/نطاق السعر | ✅ | ✅ | أحيانًا |
| العنوان | ✅ | ✅ | ✅ |
| نوع المطبخ | ❌ | ✅ | ❌ |
| المدة/نوع الجولة | ❌ | ❌ | ✅ |
| وسائل الراحة | ✅ | ❌ | ❌ |
| الإحداثيات | ✅ | ✅ | ✅ |
بناء دالة 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):
"""
استخراج قوائم TripAdvisor عبر الفنادق أو المطاعم أو المعالم السياحية.
Args:
category: "hotels" أو "restaurants" أو "attractions"
location_id: معرّف 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 + 1}: الحالة {response.status_code}، سنوقف.")
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 + 1}: تم العثور على {len(cards)} عنصرًا")
time.sleep(random.uniform(3, 7))
return pd.DataFrame(all_items)
# أمثلة استخدام
print("=== فنادق باريس ===")
hotels_df = scrape_tripadvisor("hotels", "187147", "Paris_Ile_de_France", num_pages=2)
print(hotels_df.head())
print("\n=== مطاعم روما ===")
restaurants_df = scrape_tripadvisor("restaurants", "187791", "Rome_Lazio", num_pages=2)
print(restaurants_df.head())
print("\n=== معالم برشلونة ===")
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 | ترويسات مفقودة أو مشبوهة؛ تحدي JavaScript من Cloudflare | استخدم ترويسات واقعية مثل User-Agent وAccept-Language وReferer وSec-CH-UA. وتأكد من اتساق الترويسات. |
| صفحة CAPTCHA بدلًا من البيانات | حد المعدل أو بصمة المتصفح | دوّر البروكسيات السكنية، وأضف تأخيرات عشوائية (2–7 ثوانٍ بين الطلبات) |
| HTML فارغ أو جسم صفحة فارغ | JavaScript لم يُعرض بواسطة requests | انتقل إلى Selenium أو استخرج من JSON المخفي في مصدر الصفحة |
| مراجعات جزئية / زر "Read more" لا يتوسع | المحتوى يُحمّل عند النقر | استخدم .click() في Selenium أو استخرج من كتلة JSON المضمّنة |
| المراجعات تظهر بلغة واحدة فقط | معامل اللغة مفقود | أضف ?filterLang=ALL إلى رابط المراجعات |
| توقف البيانات بعد عدد معيّن من الصفحات | حد معدل مبني على الجلسة | دوّر الجلسات، وامسح الكوكيز بين الدفعات |
| HTTP 1020 Access Denied | تم حظر IP/ASN بواسطة Cloudflare | انتقل من بروكسيات مراكز البيانات إلى البروكسيات السكنية |
| حلقة تحدي لا نهائية (CAPTCHA مستمر) | فشل في الاحتفاظ بالكوكيز | ابدأ الجلسة بزيارة الصفحة الرئيسية أولًا؛ واحتفظ بملف الكوكيز |
منطق إعادة المحاولة مع التراجع الأسي
لا تقدم أي مقالة منافسة هذا الكود فعليًا. إليك دالة إعادة محاولة قابلة لإعادة الاستخدام:
import time
import random
import requests
def fetch_with_retry(session, url, max_retries=4, base_delay=2, max_delay=60):
"""
جلب عنوان URL مع التراجع الأسي والتذبذب.
يدوّر 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" تم تقييد الطلب (429). سننتظر {retry_after} ثانية...")
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" حصلنا على {response.status_code}. إعادة المحاولة {attempt + 1}/{max_retries} خلال {wait:.1f} ثانية...")
time.sleep(wait)
continue
# رموز أخرى — لا تعيد المحاولة
print(f" الحالة غير المتوقعة {response.status_code} للرابط {url}")
return response
except requests.exceptions.Timeout:
wait = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
print(f" انتهت المهلة. إعادة المحاولة {attempt + 1}/{max_retries} خلال {wait:.1f} ثانية...")
time.sleep(wait)
print(f" تم استنفاد جميع المحاولات {max_retries} للرابط {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",
# أضف المزيد من البروكسيات السكنية
]
def get_rotated_session():
"""إنشاء جلسة جديدة مع ترويسات وبروكسي مدوّرين."""
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)
# اختر بروكسيًا عشوائيًا
if PROXY_LIST:
proxy = random.choice(PROXY_LIST)
session.proxies = {"http": proxy, "https": proxy}
return session
نوع البروكسي مهم. تُحظر بروكسيات مراكز البيانات بسرعة كبيرة من TripAdvisor (HTTP 1020 Access Denied). البروكسيات السكنية ضرورية للاستخراج المستمر — فهي تمر عبر مزوّدي إنترنت منزليين وتبدو كمستخدمين حقيقيين. توقّع أن تدفع ما بين 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"تم تصدير {len(df)} صفًا إلى 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كمفتاح خارجي
هذا يجعل الدمج في pandas سهلًا، والتحميل إلى قاعدة بيانات، أو الاستيراد إلى أدوات BI.
إذا لم تكن تريد التعامل مع أي من منطق التصدير هذا، فـ Thunderbit يتيح لك تصدير البيانات المستخرجة مباشرة إلى Excel أو Google Sheets أو Airtable أو Notion — مجانًا، ومن دون كود. مفيد عندما تحتاج إلى مشاركة النتائج مع زملاء غير تقنيين.
نصائح لاستخراج TripAdvisor بطريقة مسؤولة وفعالة
الاستخراج المسؤول في ست نقاط:
- تحقق من
robots.txt: يمنع ملف robots.txt الخاص بـ TripAdvisor روبوتات تدريب الذكاء الاصطناعي (مثل GPTBot وClaudeBot) بالكامل. أما العناكب التقليدية فتواجه قيودًا انتقائية على بعض المسارات. راجعه عندtripadvisor.com/robots.txt. - أضف تأخيرات: 3–7 ثوانٍ بين الطلبات نطاق آمن. السرعة الأعلى من 10–15 طلبًا في الدقيقة لكل IP تؤدي إلى تقييد المعدل.
- استخرج البيانات العامة فقط. لا تسجل الدخول للوصول إلى محتوى محظور.
- خزّن البيانات بأمان والتزم بـ GDPR/CCPA إذا كنت تتعامل مع معلومات شخصية (مثل أسماء المراجعين).
- فكّر في واجهة TripAdvisor الرسمية إذا كنت تحتاج بيانات على نطاق تجاري كبير. يوفّر Developer Portal الوصول إلى تفاصيل الأنشطة التجارية بالإضافة إلى 5 مراجعات و5 صور كحد أقصى لكل موقع — محدود، لكنه قانوني ومستقر.
- انتبه للسياق القانوني: حكم محكمة الاتحاد الأوروبي في قضية Ryanair (ديسمبر 2025) عزّز القيود القائمة على شروط الخدمة عبر الاتحاد الأوروبي. كما أن شروط خدمة TripAdvisor تحظر الاستخراج صراحةً. استخرج البيانات بمسؤولية وعلى مسؤوليتك القانونية.
الخلاصة
هذه هي الصورة الكاملة.
- Requests + BeautifulSoup هو المسار الأبسط. يعمل مع صفحات القوائم الثابتة، ويتطلب إعدادًا بسيطًا جدًا، وسريع. ابدأ به إذا كنت تستخرج أقل من 100 صفحة ولا تحتاج إلى محتوى يُعرض عبر JavaScript.
- Selenium يتعامل مع كل ما يعجز عنه requests: المحتوى الديناميكي، وأزرار "Read more"، ولافتات الكوكيز. إنه أبطأ بخمس مرات وأكثر استهلاكًا للموارد، لكنه الخيار الوحيد عندما تحتاج إلى التفاعل مع الصفحة.
- JSON المخفي / GraphQL هو الأسلوب الأنظف والأسرع. يمنحك بيانات منظمة من دون تحليل HTML، ويقلل عدد الطلبات وبالتالي الحاجة إلى البروكسيات، ويعيد البيانات بصيغ جاهزة للتحليل. لكنه يتطلب بعض عكس الهندسة مسبقًا، وصيانة دورية عندما يغيّر TripAdvisor بنيته.
الدالة القابلة لإعادة الاستخدام scrape_tripadvisor() تغطي الفنادق والمطاعم والمعالم السياحية. ما تحتاج شرحًا إضافيًا.
وإذا قررت أثناء الشرح أن البرمجة ليست ما تريده — أو كنت فقط تحتاج 50 فندقًا في جدول بيانات قبل نهاية اليوم — فيمكن لامتداد Chrome الخاص بـ Thunderbit أن يفعل ذلك في نقرَتين، مع اكتشاف الحقول بالذكاء الاصطناعي، وترقيم صفحي تلقائي، وتصدير مجاني إلى Excel أو Google Sheets. من دون حاجة إلى Python.
إذا أردت التعمق أكثر، فلدينا شروحات إضافية على مدونة Thunderbit وعلى قناتنا في YouTube.
الأسئلة الشائعة
1. هل من القانوني استخراج TripAdvisor؟
شروط خدمة TripAdvisor تحظر الاستخراج صراحةً. لكن المحاكم عمومًا رأت أن استخراج البيانات المتاحة للعامة — غير المحمية بتسجيل دخول — لا ينتهك قانون الاحتيال وإساءة استخدام الحاسوب في الولايات المتحدة. ومع ذلك، فإن حكم محكمة الاتحاد الأوروبي في قضية Ryanair لعام 2025 شدد القيود المبنية على شروط الخدمة داخل أوروبا. استخرج فقط البيانات العامة، واحترم robots.txt، ولا تعِد نشر المحتوى المحمي بحقوق النشر، واستشر مستشارًا قانونيًا إذا كنت تستخدم البيانات تجاريًا.
2. هل يمكنني استخراج TripAdvisor من دون Python؟
نعم. يمكن لأدوات بلا كود مثل Thunderbit استخراج TripAdvisor مباشرة من المتصفح مع اكتشاف حقول مدعوم بالذكاء الاصطناعي وترقيم صفحي تلقائي. كما يمكنك استخدام إضافات المتصفح، أو إضافات Google Sheets، أو واجهات scraping تجارية. تمنحك Python أكبر قدر من التحكم والمرونة، لكنها ليست الخيار الوحيد.
3. كيف أتجنب الحظر أثناء استخراج TripAdvisor؟
الأساليب الأساسية: استخدم ترويسات واقعية ومتسقة، خصوصًا User-Agent وSec-CH-UA، ودوّر البروكسيات السكنية (فإن بروكسيات مراكز البيانات تُحظر بسرعة)، وأضف تأخيرات عشوائية من 3–7 ثوانٍ بين الطلبات، واستخدم أسلوب JSON المخفي لتقليل عدد الطلبات، وطبّق منطق إعادة المحاولة بالتراجع الأسي، وابدأ الجلسة بزيارة الصفحة الرئيسية قبل التعمق في الصفحات.
4. ما البيانات التي يمكنني استخراجها من TripAdvisor؟
الفنادق والمطاعم والمعالم السياحية — بما في ذلك الأسماء، والتقييمات، وعدد المراجعات، ونطاقات الأسعار، والعناوين، والإحداثيات، ووسائل الراحة (الفنادق)، وأنواع المأكولات (المطاعم)، ومدد الجولات (المعالم)، ونصوص المراجعات الكاملة مع تقييمات فردية وتواريخ. أسلوب JSON المخفي وGraphQL يعيدان أغنى البيانات في كل طلب.
5. كم صفحة يمكنني استخراجها من TripAdvisor في اليوم؟
مع IP واحد وتأخيرات معقولة: حوالي 600–1,000 صفحة يوميًا. مع 20 بروكسيًا سكنيًا دوّارًا: تقريبًا 200,000–300,000 صفحة يوميًا باستخدام أساليب تعتمد على الطلبات. أما Selenium فهو أبطأ — توقع 8,000–12,000 صفحة يوميًا لكل بروكسي. أسلوب JSON المخفي/GraphQL يمنحك أكبر قدر من البيانات في كل طلب، لذا قد تحتاج إلى عدد صفحات أقل بكثير للحصول على نفس كمية المعلومات.
معرفة أكثر


