استخراج بيانات TripAdvisor باستخدام Python (من دون التعرض للحظر)

آخر تحديث في April 17, 2026
استخراج بيانات TripAdvisor باستخدام Python (من دون التعرض للحظر)

في الأسبوع الماضي، حاولت أجمع تقييمات الفنادق وعدد المراجعات لحوالي 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 ويقترح الأعمدة المناسبة للاستخراج تلقائيًا. وسير العمل فعليًا يتم في خطوتين فقط:

  1. افتح صفحة قائمة في TripAdvisor (مثل نتائج البحث عن "Hotels in Paris").
  2. انقر على "AI Suggest Fields" في الشريط الجانبي لـ Thunderbit. سيقوم الذكاء الاصطناعي بمسح الصفحة واقتراح أعمدة مثل اسم الفندق، التقييم، عدد المراجعات، السعر، والموقع.
  3. انقر على "Scrape." سيستخرج Thunderbit البيانات من كل إدراج في الصفحة — ويتعامل مع الترقيم الصفحي تلقائيًا إذا احتجت نتائج أكثر.
  4. صدّر إلى 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) — تحليل HTML
  • selenium (4.43.0) — أتمتة المتصفح، ويتطلب Python 3.10+
  • httpx (0.28.1) — عميل HTTP غير متزامن
  • parsel (1.11.0) — محددات CSS/XPath (أخف من BS4)
  • pandas (3.0.2) — تصدير البيانات، ويتطلب Python 3.11+
  • curl_cffi (0.15.0) — محاكاة بصمة TLS (مهم لتجاوز 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 يمنحك أكبر قدر من البيانات في كل طلب، لذا قد تحتاج إلى عدد صفحات أقل بكثير للحصول على نفس كمية المعلومات.

معرفة أكثر

Ke
Ke
المدير التقني في Thunderbit | عالم بيانات أول وخبير في تعلّم الآلة بخبرة تقارب عقدًا من الزمن في تعلّم الآلة وعلم البيانات، كيه شين خريج جامعة كولومبيا وكان سابقًا عالم بيانات أول في Walmart Labs. وبفضل خبرته العميقة المعترف بها من قبل الأقران في Python وR وJava والإحصاء، يشارك رؤى مجرّبة حول نقل خوارزميات الذكاء الاصطناعي المعقدة من النظرية إلى بنية جاهزة للإنتاج.
جدول المحتويات
Thunderbit · وكيل بيانات الويب بالذكاء الاصطناعي

Extract data from any page in 1 click

موثوق به من قبل أكثر من 250,000 مستخدم
الخطة المجانية متاحة
استخرج البيانات باستخدام الذكاء الاصطناعي
انقل البيانات بسهولة إلى Google Sheets أو Airtable أو Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week