أوقفت 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. تحتاج إلى متصفح حقيقي.
| الميزة | Playwright | Selenium | requests + BS4 |
|---|---|---|---|
| عرض JavaScript | دعم كامل | دعم كامل | لا يوجد |
| السرعة | أسرع بنسبة 42% إجمالًا | خط أساس | غير مناسب لهذا الاستخدام |
| دعم async | مدمج | تسلسلي فقط | غير مناسب |
| استهلاك الذاكرة | أقل بنسبة 30% | أعلى | منخفض جدًا |
| تجاوز اكتشاف الروبوتات | جيد (مع stealth) | أسهل في الاكتشاف | غير مناسب |
Playwright أسرع، أحدث، ويدعم async بشكل أفضل. وبالنسبة لـ Google Flights تحديدًا، فهو الخيار الأوضح.
خطوة بخطوة: كيف تستخرج بيانات Google Flights باستخدام Python
هذا هو قلب الدليل. سنبني scraper خطوة خطوة.

الخطوة 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 عند المستخدمين. إليك ما تواجهه وكيف تتعامل معه.

فواصل زمنية عشوائية بين الطلبات
أبسط وسيلة لتقليل أثر 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 Mbps | 10–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؟
الأمر يعود إلى ثلاثة أسباب:
-
إخفاء عبر Closure Compiler. تستخدم Google Closure Stylesheets لإنشاء أسماء فئات مثل
BVAVmfوYMlIzعبرgoog.setCssNameMapping(). وهذه تتغير مع كل إصدار — وأحيانًا أسبوعيًا. -
الاختبار A/B. يرى المستخدمون المختلفون بنيات HTML مختلفة في الوقت نفسه. قد يعمل scraper على جهازك لكنه يفشل لدى شخص في منطقة أخرى.
-
اختلافات اللغة والمنطقة. المستخدمون في الاتحاد الأوروبي يرون مصطلحات وتخطيطات وحقول بيانات مختلفة عن المستخدمين في الولايات المتحدة.
اكتب 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 (من الأكثر ثباتًا إلى الأقل):
- سمات
aria-label— مرتبطة بإتاحة الوصول ونادرًا ما تتغير - سمات
data-*— تُضاف صراحةً للوظائف - سمات
role— أدوار ARIA ذات معنى - المحددات المبنية على النص — تطابق المحتوى الظاهر
- مطابقة جزء من اسم الفئة — مثل
[class*="price"] - أسماء الفئات الكاملة المموهة — تجنبها قدر الإمكان
أضف دالة تحقق
لا تدع 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 ليعمل تلقائيًا
الخيار 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 endpoint | 15 دقيقة | استدعاءات 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 بدون كود.

هل يُعد 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 قانونية ومباشرة:
| المزوّد | التكلفة | الخطة المجانية | ملاحظات |
|---|---|---|---|
| SerpApi | 75–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 للحصول على استراتيجية مفصلة.
تعرف على المزيد


