استخراج بيانات Google Flights باستخدام Python: من الكود إلى تنبيهات الأسعار

آخر تحديث في July 8, 2026
استخراج بيانات Google Flights باستخدام Python: من الكود إلى تنبيهات الأسعار

أوقفت Google واجهة Flights API في عام 2018، لكن أسعار الرحلات لا تتوقف عن التغيّر — حتى 17 مرة خلال 48 ساعة لخط داخلي واحد فقط. وإذا كنت تريد وصولًا برمجيًا إلى هذه البيانات، فالمعالجة عبر الـ scraping هي عمليًا الخيار الوحيد المتاح.

قضيت وقتًا طويلًا في اختبار طرق مختلفة لجلب بيانات الرحلات من Google، وقد تغيّر المشهد بشكل كبير — خصوصًا بعد أن أطلقت Google نظام SearchGuard في يناير 2025. في هذا الدليل، سأريك كيف تبني scraper يعمل فعلًا باستخدام Python وPlaywright لجلب نتائج Google Flights، وكيف تتعامل مع وسائل الحماية ضد الروبوتات التي تُربك معظم المستخدمين، ثم كيف تحول ذلك كله إلى أداة تتبع أسعار مؤتمتة مع تنبيهات. وإذا كنت تفضّل تجاوز الكود بالكامل، فسأعرض أيضًا حلاً بدون برمجة باستخدام Thunderbit يوصلك لنفس النتيجة في نحو دقيقتين.

لماذا تستخرج بيانات Google Flights باستخدام Python؟

Google Flights يسيطر على البحث عن الرحلات. وظهوره على الهواتف في الولايات المتحدة قفز إلى 47.6%، متجاوزًا كل وكالات السفر الإلكترونية الكبرى. وتُقدَّر قيمة سوق metasearch للسفر خلفه بنحو $8.33 مليار في 2024، مع نمو سنوي مركب يبلغ 30.2%. ومع ذلك، ومنذ أن أُغلِقت واجهة QPX Express API نهائيًا في 10 أبريل 2018، لم يعد هناك طريق رسمي للوصول إلى هذه البيانات برمجيًا.

وفي الوقت نفسه، تتذبذب أسعار الرحلات حتى 50% لنفس المسار، مع فرق متوسط يقارب 20 دولارًا بين أدنى وأعلى سعر. وتستخدم شركات مثل Delta ما يصل إلى 77 شريحة سعرية للتسعير الديناميكي. أما متوسط سعر الرحلة ذهابًا وإيابًا في الولايات المتحدة في أوائل 2026 فيبلغ 408 دولارات، مع ارتفاع أسعار التذاكر بنسبة 14.9% على أساس سنوي.

منصة مهيمنة، بلا API، وأسعار شديدة التقلّب. لهذا أصبح استخراج بيانات Google Flights باستخدام Python واحدًا من أكثر المشاريع رواجًا على GitHub وفي منتديات السفر أيضًا.

إليك من يستفيد من ذلك وكيف:

نوع المستخدمحالة الاستخدامالفائدة الرئيسية
المسافرون الأفرادتتبع أسعار مسارات محددة بمرور الوقتتوفير 50 دولارًا لكل رحلة في المتوسط
وكالات السفرذكاء تسعيري تنافسيمراقبة فورية لتكافؤ الأسعار
فرق السفر في الشركاتتحسين التكاليف عبر المساراتتوفير 10–30% في سفر الشركات
المطورونبناء تطبيقات مقارنة الأسعاروصول برمجي إلى بيانات الأسعار
الباحثونتحليل تقلبات تسعير شركات الطيرانأبحاث أكاديمية وسوقية

المستخدمون في المنتديات صريحون جدًا بشأن سبب لجوئهم إلى scraping: عبارة من نوع "Google Flights API توقفت، وعليّ استخدام web scraping بدلًا من ذلك" تتكرر كثيرًا. والعائد على الاستثمار حقيقي — Hopper تدّعي دقة تنبؤ تصل إلى 95% عبر تحليل أكثر من 5 مليارات عرض سعر يوميًا، بينما تُظهر بيانات Expedia لعام 2026 أن الحجز قبل 8 إلى 15 يومًا قد يوفر نحو $25 على الرحلات الداخلية.

ما البيانات التي يمكنك استخراجها من Google Flights؟

تحتوي صفحة نتائج Google Flights على مجموعة غنية بشكل مفاجئ من الحقول. إليك ما يتوفر عادةً:

  • اسم شركة الطيران (والشعار)
  • وقت المغادرة ورمز المطار
  • وقت الوصول ورمز المطار
  • إجمالي مدة الرحلة
  • عدد التوقفات وتفاصيل الترانزيت (المطار، المدة، وهل هو مبيت أم لا)
  • سعر التذكرة (بحسب العملة)
  • انبعاثات CO2 (kg CO2e، مع نسبة المقارنة مقابل الرحلات المعتادة)
  • درجة السفر، رقم الرحلة، وطراز الطائرة
  • تفاصيل مساحة الأرجل
  • الإضافات (Wi‑Fi، مقابس كهرباء، بث الوسائط)
  • مؤشر مستوى السعر (منخفض / معتاد / مرتفع)
  • تحذيرات التأخير ("غالبًا ما يتأخر لأكثر من 30 دقيقة")

تختلف البيانات المتاحة حسب المسار والتاريخ ونوع التذكرة (ذهاب فقط أم ذهاب وعودة). إليك شكل سجل رحلة واحد بعد استخراجه بصيغة JSON:

{
  "search_date": "2026-04-16",
  "route": "SFO-JFK",
  "departure_date": "2026-05-15",
  "flights": [
    {
      "airline": "United Airlines",
      "flight_number": "UA123",
      "departure_time": "08:00",
      "departure_airport": "SFO",
      "arrival_time": "16:35",
      "arrival_airport": "JFK",
      "duration_minutes": 335,
      "stops": 0,
      "price_usd": 287,
      "price_level": "low",
      "co2_kg": 156,
      "co2_vs_typical": "-12%",
      "travel_class": "Economy"
    }
  ]
}

إعداد بيئة Python الخاصة بك

قبل أن نكتب أي كود scraping، تحتاج إلى بعض الأشياء الأساسية.

المتطلبات المسبقة:

  • مستوى الصعوبة: متوسط
  • الوقت المطلوب: من 1 إلى 2 ساعة للشرح الكامل
  • ما تحتاجه: Python 3.7+، معرفة أساسية بـ Python، ومتصفح مبني على Chrome

تثبيت المكتبات المطلوبة

سنستخدم Playwright لأتمتة المتصفح (لأن Google Flights يعتمد 100% على JavaScript — والطلبات البسيطة عبر HTTP لا تعطي شيئًا مفيدًا)، بالإضافة إلى بعض الأدوات المساعدة:

pip install playwright playwright-stealth pandas
playwright install chromium
  • Playwright — أتمتة المتصفح بدون واجهة، يتعامل مع عرض JavaScript، ويمتلك آليات انتظار مدمجة
  • playwright-stealth — يقلل إشارات اكتشاف الروبوتات الشائعة
  • pandas — لتحليل البيانات وتصدير CSV لاحقًا

لماذا Playwright بدل Selenium أو requests

Google Flights لن يعمل باستخدام requests مع BeautifulSoup وحدهما — فالمحتوى يُعرض بالكامل عبر JavaScript. تحتاج إلى متصفح حقيقي.

الميزةPlaywrightSeleniumrequests + BS4
عرض JavaScriptدعم كاملدعم كامللا يوجد
السرعةأسرع بنسبة 42% إجمالًاخط أساسغير مناسب لهذا الاستخدام
دعم asyncمدمجتسلسلي فقطغير مناسب
استهلاك الذاكرةأقل بنسبة 30%أعلىمنخفض جدًا
تجاوز اكتشاف الروبوتاتجيد (مع stealth)أسهل في الاكتشافغير مناسب

Playwright أسرع، أحدث، ويدعم async بشكل أفضل. وبالنسبة لـ Google Flights تحديدًا، فهو الخيار الأوضح.

خطوة بخطوة: كيف تستخرج بيانات Google Flights باستخدام Python

هذا هو قلب الدليل. سنبني scraper خطوة خطوة.

google-flights-scraping-workflow.webp

الخطوة 1: تعريف هياكل البيانات

ابدأ بتنظيم معلمات البحث وبيانات الرحلة باستخدام dataclasses في Python. هذا يجعل الكود أنظف وأسهل في التوسعة لاحقًا.

from dataclasses import dataclass, field
from typing import Optional, List

@dataclass
class SearchParams:
    origin: str          # e.g., "SFO"
    destination: str     # e.g., "JFK"
    departure_date: str   # e.g., "2026-05-15"
    return_date: Optional[str] = None
    trip_type: str = "one-way"  # "one-way" or "round-trip"
    travel_class: str = "economy"

@dataclass
class FlightData:
    airline: str = ""
    departure_time: str = ""
    arrival_time: str = ""
    duration: str = ""
    stops: str = ""
    price: str = ""
    co2_emissions: str = ""

كل حقل هنا يقابل مباشرة ما سنستخرجه من الصفحة. وجود هذا الهيكل من البداية يمنعك من تمرير قواميس عشوائية وغير مرتبة لاحقًا.

الخطوة 2: فهم بنية رابط Google Flights

Google Flights يشفّر معلمات البحث باستخدام Protobuf مُشفّر بـ Base64 داخل معامل URL اسمه tfs. يمكنك إما تفكيك هذا الترميز أو اختيار الطريقة الأبسط: إنشاء رابط استعلام بلغة طبيعية.

أبسط طريقة هي استخدام صيغة البحث التالية:

https://www.google.com/travel/flights?q=flights+from+SFO+to+JFK+on+2026-05-15&curr=USD

ولتحكم أكبر، يمكنك بناء الرابط برمجيًا:

def build_flights_url(origin: str, destination: str, date: str) -> str:
    base = "https://www.google.com/travel/flights"
    query = f"flights from {origin} to {destination} on {date}"
    return f"{base}?q={query.replace(' ', '+')}&curr=USD"

أما الخيار البديل — وهو عكس هندسة ترميز Protobuf — فيمنحك تحكمًا أدق، لكنه ينهار عندما تغيّر Google التنسيق الداخلي. مكتبات مثل fast-flights على GitHub تستخدم فك ترميز Protobuf لتجاوز تحليل HTML بالكامل، لكنها طريقة أكثر تقدمًا.

الخطوة 3: تشغيل المتصفح والانتقال إلى Google Flights

إليك إعداد Playwright. نحن نستخدم playwright-stealth لتقليل احتمالات الاكتشاف منذ البداية.

import asyncio
from playwright.async_api import async_playwright
from playwright_stealth import Stealth

async def scrape_flights(params: SearchParams) -> List[FlightData]:
    async with Stealth().use_async(async_playwright()) as pw:
        browser = await pw.chromium.launch(
            headless=True,
            args=[
                "--disable-blink-features=AutomationControlled",
                "--disable-dev-shm-usage",
                "--no-first-run",
            ]
        )
        context = await browser.new_context(
            viewport={"width": 1920, "height": 1080},
            user_agent=(
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/125.0.0.0 Safari/537.36"
            ),
            locale="en-US",
            timezone_id="America/New_York",
        )
        # Pre-set cookie consent to skip the popup
        await context.add_cookies([{
            "name": "SOCS",
            "value": "CAESHwgBEhJnd3NfMjAyNTAyMjctMF9SQzIaBXpoLUNOIAEaBgiAy6O-Bg",
            "domain": ".google.com",
            "path": "/"
        }])
        page = await context.new_page()

نحن نشغل الوضع headless للإنتاج (بدّله إلى headless=False عند التصحيح)، ونضبط viewport وuser agent بشكل واقعي، كما نثبت cookie SOCS مسبقًا لتجاوز نافذة الموافقة — وسأفصّل ذلك أكثر في قسم الحماية ضد الروبوتات.

الخطوة 4: الانتقال إلى نتائج البحث

حمّل الرابط الذي أنشأته وانتظر ظهور نتائج الرحلات:

        url = build_flights_url(
            params.origin, params.destination, params.departure_date
        )
        await page.goto(url, wait_until="networkidle")

        # Wait for flight results to load
        await page.wait_for_selector(
            "li.pIav2d", timeout=15000
        )

إذا ظهر لك timeout هنا، فغالبًا السبب إما أن نافذة الموافقة حجبت الصفحة (راجع حل cookie في الخطوة 3) أو أن Google يعرض CAPTCHA. سنتناول الحالتين في قسم anti-bot.

الخطوة 5: تحميل كل النتائج

Google Flights يخفي نتائج إضافية خلف زر "Show more flights". عليك النقر عليه عدة مرات إلى أن تصبح كل الرحلات مرئية:

        # Click "Show more flights" until all results are loaded
        while True:
            try:
                more_button = page.locator(
                    'button:has-text("Show more flights")'
                )
                if await more_button.is_visible(timeout=3000):
                    await more_button.click()
                    await page.wait_for_timeout(2000)
                else:
                    break
            except Exception:
                break

هذه الحلقة تنقر الزر، تنتظر ثانيتين لظهور نتائج جديدة، ثم تتوقف عندما يختفي الزر. وفي اختباري، معظم المسارات تحتوي على 1 إلى 3 صفحات من النتائج.

الخطوة 6: استخراج بيانات الرحلات باستخدام CSS selectors

الآن نحلل بيانات الرحلات الفعلية من الصفحة المحمّلة. هذه هي الـ selectors (تم التحقق منها حتى أبريل 2026 — وسأشرح لاحقًا لماذا هذا التاريخ مهم):

        flights = []
        cards = await page.query_selector_all("li.pIav2d")

        for card in cards:
            flight = FlightData()

            # Airline name
            airline_el = await card.query_selector(
                "div.sSHqwe span:not([class])"
            )
            if airline_el:
                flight.airline = (await airline_el.inner_text()).strip()

            # Departure time
            dep_el = await card.query_selector(
                'span[aria-label*="Departure time"]'
            )
            if dep_el:
                flight.departure_time = (await dep_el.inner_text()).strip()

            # Arrival time
            arr_el = await card.query_selector(
                'span[aria-label*="Arrival time"]'
            )
            if arr_el:
                flight.arrival_time = (await arr_el.inner_text()).strip()

            # Duration
            dur_el = await card.query_selector("div.gvkrdb")
            if dur_el:
                flight.duration = (await dur_el.inner_text()).strip()

            # Stops
            stops_el = await card.query_selector("div.EfT7Ae span")
            if stops_el:
                flight.stops = (await stops_el.inner_text()).strip()

            # Price
            price_el = await card.query_selector(
                "div.FpEdX span"
            )
            if price_el:
                flight.price = (await price_el.inner_text()).strip()

            # CO2 emissions
            co2_el = await card.query_selector("div.O7CXue")
            if co2_el:
                flight.co2_emissions = (
                    await co2_el.get_attribute("aria-label") or ""
                ).strip()

            flights.append(flight)

        await browser.close()
        return flights

تحذير مهم: أسماء الفئات مثل pIav2d وsSHqwe وFpEdX تُولَّد بواسطة Closure Compiler الخاص بـ Google وقد تتغير في أي إصدار. أما محددات aria-label فهي أكثر ثباتًا. وسأعرض لاحقًا استراتيجية صيانة كاملة.

الخطوة 7: حفظ النتائج بصيغة JSON أو CSV

أخيرًا، احفظ البيانات المستخرجة مع طابع زمني (وهذا مهم جدًا لاحقًا لتتبع الأسعار):

import json
from datetime import datetime
from dataclasses import asdict

async def main():
    params = SearchParams(
        origin="SFO",
        destination="JFK",
        departure_date="2026-05-15"
    )
    flights = await scrape_flights(params)

    output = {
        "search_date": datetime.now().isoformat(),
        "params": asdict(params),
        "flights": [asdict(f) for f in flights],
    }

    with open("flights.json", "w") as f:
        json.dump(output, f, indent=2)

    # Also save as CSV
    import pandas as pd
    df = pd.DataFrame([asdict(f) for f in flights])
    df["search_date"] = datetime.now().isoformat()
    df["route"] = f"{params.origin}-{params.destination}"
    df.to_csv("flights.csv", index=False)

    print(f"Scraped {len(flights)} flights")

asyncio.run(main())

شغّل هذا وستحصل على ملفي flights.json وflights.csv بنتائجك. وفي اختباراتنا، فإن بحث SFO-JFK يعيد عادةً 30 إلى 80 خيارًا للرحلات ويستغرق نحو 15 إلى 20 ثانية.

دليل النجاة من الحماية ضد الروبوتات عند استخراج Google Flights

معظم الشروحات تتوقف هنا. ومعظم أدوات scraping تفشل هنا. أطلقت Google SearchGuard في يناير 2025، ما كسر تقريبًا كل أدوات scraping الخاصة بنتائج البحث بين ليلة وضحاها. وتصفه Google بأنه "نتاج عشرات الآلاف من ساعات العمل وملايين الدولارات من الاستثمار". وتم تصنيف صعوبة استخراج Google Flights عند 4/5.

ولا توجد مقالة منافسة تشرح هذا بعمق، رغم أنه السبب الأول في توقف أدوات scrape عند المستخدمين. إليك ما تواجهه وكيف تتعامل معه.

anti-bot-survival-guide.webp

فواصل زمنية عشوائية بين الطلبات

أبسط وسيلة لتقليل أثر rate limiting. سطران من الكود، وفعالية متوسطة:

import time
import random

time.sleep(random.uniform(3, 7))

أضف هذا بين عمليات التنقل بين الصفحات. الفواصل الثابتة (مثل 5 ثوانٍ بالضبط كل مرة) تُعد إشارة خطر — اجعلها عشوائية.

تدوير User-Agent

استخدام نفس user-agent في كل طلب يسهّل اكتشافك. بدّل بين مجموعة من القيم:

import random

USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/125.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 Safari/605.1.15",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0",
]

user_agent = random.choice(USER_AGENTS)

تجاوز اكتشاف headless

تتحقق Google من متغير navigator.webdriver ومن إشارات أتمتة أخرى. مكتبة playwright-stealth تعالج معظم هذه الأمور، لكن عليك أيضًا ضبط وسائط التشغيل المذكورة في الخطوة 3. وأهم الأعلام هي:

args=[
    "--disable-blink-features=AutomationControlled",
    "--disable-dev-shm-usage",
    "--no-first-run",
]

هذا يساعدك على تجاوز الاكتشافات الأساسية. أما SearchGuard فيذهب أعمق — يراقب سرعة حركة الماوس، وتوقيت ضغطات المفاتيح، وأنماط التمرير — لكن بالنسبة لاستخراج كميات متوسطة، فإن وضع stealth مع فواصل زمنية واقعية يكون غالبًا كافيًا.

تدوير البروكسي: مراكز البيانات مقابل residential

إذا أردت تنفيذ أكثر من عدد قليل من عمليات البحث، فستحتاج إلى بروكسيات. والفرق بينها مهم:

الميزةبروكسيات مراكز البياناتالبروكسيات السكنية
السرعة100–1,000 Mbps10–100 Mbps
معدل النجاح (Google)20–40%85–95%
التكلفة0.10–0.50 دولار/IP شهريًا5–15 دولارًا/GB
خطر الاكتشافمرتفعمنخفض جدًا

البروكسيات السكنية أرخص بنحو 180 مرة لكل طلب ناجح عند استخراج المواقع المحمية. أسعار 2026 لدى المزودين: Smartproxy تبدأ من 7 دولارات/GB، وBright Data بسعر 8.40 دولارات/GB، وOxylabs بسعر 8 دولارات/GB.

أضف بروكسيًا إلى Playwright بهذا الشكل:

browser = await pw.chromium.launch(
    proxy={"server": "http://proxy-host:port",
           "username": "user", "password": "pass"}
)

التعامل مع نافذة الموافقة على الكوكيز

يذكر المستخدمون باستمرار أن نافذة "I agree to terms" هي العائق الرئيسي: "أولًا ستعرض لك Google نافذة 'I agree to terms and conditions'." وأفضل حل هو ضبط cookie SOCS مسبقًا (كما في الخطوة 3). وإذا لم ينجح ذلك، فمرّر عبر النافذة بالنقر:

try:
    accept_btn = page.locator('button:has-text("Accept all")')
    if await accept_btn.is_visible(timeout=3000):
        await accept_btn.click()
        await page.wait_for_timeout(1000)
except Exception:
    pass  # No popup present

ملاحظة: نص الزر يختلف حسب اللغة — "Alle akzeptieren" في الألمانية، و"Tout accepter" في الفرنسية.

مرجع سريع لمكافحة الروبوتات

التقنيةالصعوبةالفعاليةهل تحتاج كودًا؟
فواصل عشوائية (2–7 ثوانٍ)منخفضةمتوسطةسطران
تدوير user-agentمنخفضةمتوسطة5 أسطر
تجاوز اكتشاف headlessمتوسطةعاليةوسائط تشغيل Playwright
إضافة playwright-stealthمتوسطة60–80% على المواقع الأساسيةpip install
تدوير البروكسي (مراكز بيانات)متوسطةمتوسطةإعدادات
تدوير البروكسي (سكنية)متوسطةنجاح 85–95%إعدادات
ضبط cookie الموافقة مسبقًا (SOCS)منخفضةضروريسطر واحد

ولمعدلات آمنة يُنصح بها: اجعل الفاصل 10–20 ثانية بين الطلبات مع تدوير IP. عتبات Google تقريبية عند 100 طلب/دقيقة لكل IP قبل ظهور 429، ويمكن أن تؤدي الأحجام المستمرة فوق 1,000 طلب/يوم لكل IP إلى حظر مؤقت.

لماذا تستمر محددات Google Flights في الانكسار (وكيف تصلحها)

هذه هي المشكلة رقم 1 بفارق كبير. تمتلئ النقاشات في المنتديات بتعليقات من نوع "كل ما أسترجعه هو 14 قائمة فارغة." كل شرح يعطيك selectors، لكن لا يشرح لماذا تنهار.

لماذا تتغير محددات Google Flights؟

الأمر يعود إلى ثلاثة أسباب:

  1. إخفاء عبر Closure Compiler. تستخدم Google Closure Stylesheets لإنشاء أسماء فئات مثل BVAVmf وYMlIz عبر goog.setCssNameMapping(). وهذه تتغير مع كل إصدار — وأحيانًا أسبوعيًا.

  2. الاختبار A/B. يرى المستخدمون المختلفون بنيات HTML مختلفة في الوقت نفسه. قد يعمل scraper على جهازك لكنه يفشل لدى شخص في منطقة أخرى.

  3. اختلافات اللغة والمنطقة. المستخدمون في الاتحاد الأوروبي يرون مصطلحات وتخطيطات وحقول بيانات مختلفة عن المستخدمين في الولايات المتحدة.

اكتب selectors أكثر تحمّلًا

فضّل المحددات المرتبطة بالمعنى بدلًا من الشكل:

# Fragile — breaks on every build
price_el = await card.query_selector("div.BVAVmf > div.YMlIz")

# More resilient — tied to accessibility labels
dep_el = await card.query_selector('span[aria-label*="Departure time"]')

# Also resilient — text-based matching
more_btn = page.locator('button:has-text("Show more flights")')

ترتيب ثبات الـ selectors (من الأكثر ثباتًا إلى الأقل):

  1. سمات aria-label — مرتبطة بإتاحة الوصول ونادرًا ما تتغير
  2. سمات data-* — تُضاف صراحةً للوظائف
  3. سمات role — أدوار ARIA ذات معنى
  4. المحددات المبنية على النص — تطابق المحتوى الظاهر
  5. مطابقة جزء من اسم الفئة — مثل [class*="price"]
  6. أسماء الفئات الكاملة المموهة — تجنبها قدر الإمكان

أضف دالة تحقق

لا تدع selectors المعطلة تُنتج بيانات فارغة بصمت. اكتشف المشكلة مبكرًا:

import logging

logger = logging.getLogger(__name__)

def validate_flight(data: FlightData) -> bool:
    required = ["airline", "price", "departure_time",
                "arrival_time", "duration"]
    valid = True
    for field_name in required:
        if not getattr(data, field_name, ""):
            logger.warning(
                f"Missing '{field_name}' — selectors may need updating"
            )
            valid = False
    return valid

شغّل هذه الدالة على كل رحلة تستخرجها. وإذا بدأت ترى تحذيرات، فقد حان وقت فحص الصفحة وتحديث المحددات.

استراتيجية صيانة الـ selectors

  • راجع الـ selectors شهريًا، أو فور انخفاض جودة المخرجات
  • احتفظ بها في قاموس إعدادات منفصل لتسهيل التحديث
  • تم التحقق من المحددات في هذه المقالة آخر مرة: أبريل 2026
  • فكّر في fast-flights library كبديل — فهي تستخدم فك ترميز Protobuf بدلًا من CSS selectors، ما يتجاوز هذه المشكلة تمامًا (مع أنها تبقى عرضة للتعطل عندما تغيّر Google صيغ البيانات الداخلية)

من استخراج لمرة واحدة إلى متتبع أسعار Google Flights مؤتمت

معظم الشروحات تنتهي عند "احفظ إلى JSON." لكن عنوان هذه المقالة يتحدث عن "تنبيهات الأسعار". حان وقت التنفيذ.

scraper-to-price-tracker-sfo-jfk.webp

جدولة الـ scraper ليعمل تلقائيًا

الخيار 1: مكتبة schedule في Python (الأبسط ومتوافقة مع جميع الأنظمة):

import schedule
import time

def run_scraper():
    asyncio.run(main())

schedule.every().day.at("06:00").do(run_scraper)
schedule.every().day.at("18:00").do(run_scraper)

while True:
    schedule.run_pending()
    time.sleep(60)

الخيار 2: مهمة cron (Linux/Mac):

# Run at 6 AM and 6 PM daily
0 6,18 * * * cd /path/to/scraper && python scraper.py

الخيار 3: Windows Task Scheduler — أنشئ مهمة أساسية تشغّل python scraper.py وفق الجدول الذي تفضله.

الجانب السلبي: كل هذه الطرق تحتاج جهازًا يعمل دائمًا. إذا كنت تشغّلها على لابتوب يدخل في وضع السكون، فستفقد بعض عمليات الاستخراج.

تخزين بيانات الأسعار التاريخية

بدلًا من استبدال ملف JSON في كل مرة، اجعل الإضافة قاعدة SQLite:

import sqlite3
from datetime import datetime

def init_db():
    conn = sqlite3.connect("flights.db")
    conn.execute("""
        CREATE TABLE IF NOT EXISTS flights (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            scrape_date TEXT NOT NULL,
            route TEXT NOT NULL,
            airline TEXT,
            departure_time TEXT,
            arrival_time TEXT,
            duration TEXT,
            stops TEXT,
            price_usd REAL,
            co2_emissions TEXT
        )
    """)
    conn.execute(
        "CREATE INDEX IF NOT EXISTS idx_route_date "
        "ON flights(route, scrape_date)"
    )
    conn.commit()
    return conn

def save_flight(conn, route: str, flight: FlightData):
    price_num = float(
        flight.price.replace("$", "").replace(",", "")
    ) if flight.price else None
    conn.execute(
        "INSERT INTO flights "
        "(scrape_date, route, airline, departure_time, "
        "arrival_time, duration, stops, price_usd, co2_emissions) "
        "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
        (datetime.now().isoformat(), route, flight.airline,
         flight.departure_time, flight.arrival_time,
         flight.duration, flight.stops, price_num,
         flight.co2_emissions)
    )
    conn.commit()

بعد أسبوع من عمليتي استخراج يوميًا، سيكون لديك ما يكفي من البيانات لبدء ملاحظة الاتجاهات.

تحليل اتجاهات الأسعار وضبط التنبيهات

اعثر على أرخص خيار في بياناتك التاريخية:

import pandas as pd
import sqlite3

conn = sqlite3.connect("flights.db")
df = pd.read_sql_query(
    "SELECT * FROM flights WHERE route = 'SFO-JFK'", conn
)
summary = df.groupby("scrape_date")["price_usd"].agg(
    ["min", "max", "mean"]
)
cheapest = df.loc[df["price_usd"].idxmin()]
print(
    f"Cheapest: ${cheapest['price_usd']:.0f} on "
    f"{cheapest['scrape_date']} ({cheapest['airline']})"
)

أرسل تنبيهًا عبر البريد الإلكتروني عندما ينخفض السعر تحت الحد الذي تحدده:

import smtplib
from email.mime.text import MIMEText

def send_price_alert(route, price, threshold, recipient):
    msg = MIMEText(
        f"Price drop alert! {route}: ${price:.0f} "
        f"(below your ${threshold:.0f} threshold)"
    )
    msg["Subject"] = f"Flight Deal: {route} at ${price:.0f}"
    msg["From"] = "alerts@example.com"
    msg["To"] = recipient

    with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
        server.login("alerts@example.com", "your_app_password")
        server.send_message(msg)

# After each scrape, check for deals
min_price = df["price_usd"].min()
threshold = 250
if min_price < threshold:
    send_price_alert("SFO-JFK", min_price, threshold,
                     "you@email.com")

وبالنسبة لتكرار الاستخراج الموصى به: مرتان يوميًا تكفيان لمتابعة الأسعار الشخصية، وتقللان خطر الاكتشاف. أما للمراقبة التجارية، فكل 4–6 ساعات مع تدوير البروكسي. لا تُجرِ الاستخراج كل ساعة إلا خلال فترات التخفيضات القصيرة، ولمدة مؤقتة فقط.

الطريق الأسهل: Scheduled Scraper من Thunderbit

إذا كانت إدارة cron، وخادم يعمل باستمرار، وإعدادات البروكسي تبدو لك أكثر من اللازم، فإن Scheduled Scraper من Thunderbit يعالج نفس الحالة من دون أي عبء تشغيلي. أنت تصف فترة الاستخراج بلغة بسيطة، وتُدخل روابط Google Flights، ثم يعمل الـ scraper تلقائيًا على بنية Thunderbit السحابية — مع إدارة مدمجة للحماية ضد الروبوتات، وتصدير مباشر إلى Google Sheets أو Excel أو Airtable. إنه ليس بديلًا عن نهج Python الكامل (فأنت ستفقد بعض التخصيص)، لكنه بالنسبة لحالة "أريد جدولًا لتتبع الأسعار" فهو أسرع طريق. ويمكنك تجربته عبر الخطة المجانية.

عندما يكون Python مبالغًا فيه: طرق بدون كود لاستخراج Google Flights

بعد بناء كل ما سبق، سأكون صريحًا: هناك الكثير من الأجزاء المتحركة. ليس الجميع بحاجة إلى هذا المستوى من التحكم. الـ selectors تنكسر، والبروكسيات تحتاج تدويرًا، والمهام المجدولة تحتاج مراقبة. إذا كان هدفك فقط "إدخال أسعار الرحلات إلى جدول بيانات بشكل منتظم"، فهناك خيارات أسرع.

مقارنة: Python يدوي vs خدمات API vs Thunderbit

النهجوقت الإعدادهل يتطلب برمجة؟يتعامل مع الحماية ضد الروبوتاتالجدولةالتكلفة
Playwright اليدوي (هذا الدليل)من 1 إلى 2 ساعةPython (متوسط)إعداد يدوييدوي (cron)مجاني + تكلفة البروكسي
SerpApi Google Flights endpoint15 دقيقةاستدعاءات API فقطمُعالَجعبر APIنحو 50 دولارًا+/شهر
Thunderbit Chrome Extensionدقيقتانلاscraping سحابيجدول مدمجتتوفر خطة مجانية

ملاحظة بشأن SerpApi: رفعت Google دعوى DMCA ضد SerpApi في ديسمبر 2025، بدعوى أن طلباتهم زادت بنسبة 25,000% خلال عامين. هذا الغموض القانوني يستحق الانتباه عند تقييم مزوّدي API.

كيف يستخرج Thunderbit بيانات Google Flights

افتح نتائج بحث Google Flights في Chrome، ثم اضغط زر Thunderbit "AI Suggest Fields" — سيقرأ الذكاء الاصطناعي الصفحة ويقترح أعمدة مثل شركة الطيران، السعر، وقت المغادرة، وعدد التوقفات — راجع الحقول المقترحة ثم اضغط "Scrape". ستظهر النتائج في جدول يمكنك تصديره إلى Excel أو Google Sheets أو Airtable أو Notion — وكل ذلك على الخطة المجانية.

ولحالة تتبع الأسعار تحديدًا، فإن Scheduled Scraper في Thunderbit وCloud Scraping (القادر على التعامل مع 50 صفحة بالتوازي) يستبدلان البنية الكاملة المكوّنة من cron والبروكسي والخادم.

Python يمنحك تحكمًا كاملًا وتخصيصًا بلا حدود. Thunderbit يمنحك السرعة وصفر صيانة. اختر بناءً على هدفك الحقيقي. وإذا أردت معرفة المزيد عن طرق scraping بدون كود، فاطّلع على دليلنا حول أفضل أدوات web scraping بدون كود.

flight-data-options-comparison.webp

هل يُعد scraping Google Flights قانونيًا؟ ما الذي يجب أن تعرفه

يطرح المستخدمون هذا السؤال باستمرار: "استخراج بيانات Google Flights مباشرة يخالف شروط الاستخدام الخاصة بـ Google." وهذا قلق مشروع — خصوصًا مع توقف API وعدم وجود بديل رسمي معتمد.

مخالفة الشروط vs. المسؤولية القانونية

تنص شروط استخدام Google (المحدّثة في 22 مايو 2024) على أنه يجب على المستخدمين ألا "يصلوا إلى الخدمات أو يستخدموها أو أي محتوى من خلالها باستخدام أي وسيلة آلية (مثل الروبوتات أو العناكب أو أدوات الاستخراج)." مخالفة الشروط هي إخلال تعاقدي (مسألة مدنية) — وليست بالضرورة جريمة قانونية.

والسابقة القانونية الأهم: hiQ v. LinkedIn (الدائرة التاسعة، 2022) قررت أن استخراج البيانات المتاحة للعامة لا ينتهك قانون Computer Fraud and Abuse Act (CFAA). ومع ذلك، انتهت القضية بتسوية، بينما تعتمد دعوى Google ضد SerpApi في ديسمبر 2025 على نظرية قانونية مختلفة — وهي DMCA Section 1201 (التحايل على وسائل الحماية التقنية) — وقد تكون أكثر خطورة.

أفضل الممارسات للاستخراج المسؤول

  • حدّد معدل الطلبات — فواصل 10 إلى 20 ثانية مع تدوير IP
  • لا تستخرج بيانات شخصية — أسعار الرحلات هي بيانات عامة مجمعة
  • لا تتحايل برمجيًا على CAPTCHAs (وهنا يكمن خطر DMCA)
  • استخدم البيانات للبحث الشخصي، لا لبناء منتج تجاري منافس من دون ترخيص مناسب
  • فكّر في الـ APIs الرسمية عندما تكون متاحة

مصادر بيانات بديلة

إذا شعرت أن scraping محفوف بالمخاطر بالنسبة لحالتك، فهناك خيارات API قانونية ومباشرة:

المزوّدالتكلفةالخطة المجانيةملاحظات
SerpApi75–3,750 دولارًا+/شهر250 عملية بحث/شهرJSON مباشر لـ Google Flights (تحت تدقيق قانوني)
Kiwi Tequilaمجاني (نموذج شركاء)غير محدودالأفضل للشركات الناشئة والاختبار
Amadeusحسب الاستخدام2,000 طلب/شهرأكثر من 400 شركة طيران، مع إمكانية الحجز
Skyscannerمخصصيتطلب موافقة52 سوقًا، 30 لغة

كتبنا شرحًا أعمق عن الآثار القانونية لـ web scraping إذا أردت الصورة الكاملة.

الخلاصة وأهم النقاط

كان ذلك كثيرًا، لكن هذه هي النقاط المهمة:

  • Python + Playwright هو النهج الأكثر مرونة لاستخراج Google Flights، لكنه يحتاج صيانة مستمرة
  • وسائل مكافحة الروبوتات (الفواصل الزمنية، تدوير user-agent، البروكسيات السكنية) ليست اختيارية — بل ضرورية للموثوقية، خصوصًا بعد SearchGuard
  • الـ selectors تنكسر كثيرًا — استخدم aria-label والمحددات المبنية على النص قدر الإمكان، وتحقق من المخرجات، وضع جدول صيانة
  • أتمتة باستخدام schedule أو cron لتحويل الاستخراج لمرة واحدة إلى متتبع أسعار حقيقي مع بيانات تاريخية وتنبيهات بريدية
  • Thunderbit يقدم بديلًا بدون كود مع جدولة مدمجة، وcloud scraping، ومعالجة للحماية ضد الروبوتات — وهو مثالي إذا كان هدفك جدول تتبع أسعار بدلًا من مشروع برمجي
  • احترم الحدود القانونية — حدّد معدل الطلبات، واستخرج البيانات العامة فقط، وفكّر في بدائل الـ API للاستخدام التجاري

احصل على الكود من هذا الدليل، أو ثبّت Thunderbit Chrome extension إن أردت الطريق الأسرع. في كلتا الحالتين، ستصبح تتابع أسعار الرحلات بدلًا من تحديث Google Flights يدويًا.

ولمزيد من تقنيات scraping باستخدام Python، اطّلع على أدلتنا حول كيفية استخراج البيانات من الويب باستخدام Python وأفضل أدوات web scraping في Python.

الأسئلة الشائعة

1. هل يمكنني استخراج بيانات Google Flights بدون Python؟

نعم. خدمات API مثل SerpApi وKiwi Tequila توفر بيانات رحلات منظمة عبر استدعاءات API (من دون الحاجة إلى أتمتة المتصفح). وإذا أردت نهجًا بلا كود تمامًا، فإن Thunderbit Chrome extension يمكنه استخراج نتائج Google Flights مباشرة من المتصفح مع حقول مقترحة بالذكاء الاصطناعي وتصدير بنقرة واحدة.

2. هل تمنع Google استخراج بيانات الرحلات؟

تستخدم Google تقنيات اكتشاف الروبوتات (SearchGuard)، وCAPTCHAs، وrate limiting. ومع وسائل الحماية المناسبة — الفواصل العشوائية، تدوير user-agent، البروكسيات السكنية، وإعدادات المتصفح stealth — يمكنك الحفاظ على استخراج موثوق عند الأحجام المتوسطة. راجع قسم anti-bot أعلاه للاطلاع على التقنيات والحدود المحددة.

3. كم مرة يجب أن أستخرج Google Flights لتتبع الأسعار؟

مرتان يوميًا (في أوقات عشوائية) تكفيان لمتابعة الأسعار الشخصية، وتبقيان خطر الاكتشاف منخفضًا. أما للمراقبة التجارية، فكل 4 إلى 6 ساعات مع تدوير البروكسي. تجنب الاستخراج كل ساعة إلا أثناء فترات التخفيض القصيرة — فهذا يزيد كثيرًا من احتمال الحظر.

4. هل توجد API مجانية لـ Google Flights؟

واجهة Google QPX Express الرسمية أُوقفت في أبريل 2018. لا يوجد بديل رسمي مجاني. وأقرب خيار مجاني هو Kiwi Tequila API (بنموذج شراكات، مع عمليات بحث غير محدودة). كما يقدم SerpApi 250 عملية بحث مجانية شهريًا. ولغالبية المستخدمين، يكون scraping أو أداة بدون كود مثل Thunderbit هو المسار العملي.

5. لماذا تستمر CSS selectors الخاصة بـ Google Flights في إرجاع بيانات فارغة؟

تستخدم Google Closure Compiler لتوليد أسماء فئات مموهة تتغير مع كل إصدار. كما أن الاختبار A/B واختلاف اللغة والمنطقة يسببان تغيّر بنية HTML بين المستخدمين. الحل: استخدم سمات aria-label والمحددات المبنية على النص بدلًا من أسماء الفئات، وأضف دالة تحقق لاكتشاف التعطل مبكرًا، وراجع المحددات شهريًا. راجع قسم صيانة selectors للحصول على استراتيجية مفصلة.

تعرف على المزيد

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