איך לגלוש ולחלץ נתונים מ‑Google Flights עם Python: מקוד ועד התראות מחיר

עודכן לאחרונה ב- April 16, 2026
איך לגלוש ולחלץ נתונים מ‑Google Flights עם Python: מקוד ועד התראות מחיר

Google סגרה את ה‑Flights API שלה כבר ב‑2018, אבל מחירי הטיסות ממשיכים לזוז בלי הפסקה — עד 17 פעמים ב‑48 שעות עבור אותו קו טיסה פנימי. אם אתם צריכים גישה תכנותית לנתונים האלה, גרידה היא כמעט האופציה היחידה.

השקעתי לא מעט זמן בבדיקה של גישות שונות לשליפת נתוני טיסות מ‑Google, והתחום השתנה בצורה דרמטית — במיוחד אחרי ש‑Google השיקה את SearchGuard בינואר 2025. במדריך הזה אראה איך לבנות סקרייפר Python שעובד מול Google Flights באמצעות Playwright, איך להתמודד עם מנגנוני ההגנה נגד בוטים שמכשילים את רוב האנשים, ואיך להרחיב את הפתרון למעקב מחירים אוטומטי עם התראות. אם אתם מעדיפים לדלג על הקוד לגמרי, אראה גם קיצור דרך ללא קוד באמצעות Thunderbit שמגיע לאותה תוצאה בכשתי דקות.

למה לגרד את Google Flights עם Python?

Google Flights שולטת בחיפושי הטיסות. החשיפה שלה במובייל בארה״ב זינקה ל‑47.6%, ועקפה כל OTA גדולה. שוק חיפוש הטיסות שמאחוריה מוערך ב‑$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% בנסיעות עסקיות
מפתחיםבניית אפליקציות להשוואת מחירי טיסותגישה תכנותית לנתוני תמחור
חוקריםניתוח תנודתיות במחירי תעופהמחקר אקדמי ושוקי

משתמשים בפורומים מדברים ממש בפשטות על למה עברו לגרידה: "Google Flights API בוטל ואני צריך להשתמש במקום זה ב‑web scraping" הוא משפט שחוזר שוב ושוב. וה‑ROI אמיתי — Hopper טוענת ל‑95% דיוק בתחזיות אחרי ניתוח של יותר מ‑5 מיליארד הצעות מחיר ביום, בעוד נתוני Expedia ל‑2026 מראים שהזמנה 8–15 ימים מראש חוסכת כ‑$25 בטיסות פנים.

אילו נתונים אפשר לגרד מ‑Google Flights?

דף התוצאות של Google Flights כולל סט די עשיר של שדות נתונים. הנה מה שבדרך כלל אפשר לשלוף:

  • שם חברת התעופה (וגם הלוגו)
  • שעת המראה וקוד שדה התעופה
  • שעת נחיתה וקוד שדה התעופה
  • משך הטיסה הכולל
  • מספר עצירות ופרטי קונקשן (שדה תעופה, משך, האם יש לילה)
  • מחיר הכרטיס (תלוי מטבע)
  • פליטות CO2 (ק״ג CO2e, עם אחוז השוואה לטיסות טיפוסיות)
  • מחלקת נסיעה, מספר טיסה, דגם המטוס
  • מרווח לרגליים
  • תוספות (Wi‑Fi, שקעי חשמל, הזרמת מדיה)
  • אינדיקטור רמת מחיר (נמוך/רגיל/גבוה)
  • אזהרות עיכוב ("Often delayed by over 30 min")

הזמינות של הנתונים משתנה לפי מסלול, תאריך וסוג כרטיס (כיוון אחד מול הלוך‑חזור). כך נראית רשומת טיסה אחת שנגרדה, בפורמט 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

לפני שנכתוב את קוד הגרידה, צריך להכין כמה דברים.

דרישות מקדימות:

  • רמת קושי: בינונית
  • זמן נדרש: כ‑1–2 שעות למדריך המלא
  • מה צריך: Python 3.7+, ידע בסיסי ב‑Python, דפדפן מבוסס Chrome

התקנת הספריות הנדרשות

אנחנו משתמשים ב‑Playwright לאוטומציית דפדפן (Google Flights נטען 100% ב‑JavaScript — בקשות HTTP סטטיות לא מחזירות שום דבר שימושי), ועוד כמה עזרים:

pip install playwright playwright-stealth pandas
playwright install chromium
  • Playwright — אוטומציית דפדפן ב‑headless, תומך ברינדור JavaScript ובמנגנוני המתנה מובנים
  • playwright-stealth — מטפל בסימני זיהוי נפוצים של בוטים
  • pandas — לניתוח נתונים ולהמשך ייצוא ל‑CSV

למה Playwright ולא Selenium או Requests

Google Flights לא יעבוד רק עם requests ו‑BeautifulSoup — התוכן של העמוד נוצר כולו על ידי JavaScript. צריך דפדפן אמיתי.

תכונהPlaywrightSeleniumrequests + BS4
רינדור JSתמיכה מלאהתמיכה מלאהאין
מהירות42% מהיר יותר בסך הכולקו בסיסלא רלוונטי לשימוש הזה
תמיכה ב‑asyncמובניתרק רציףלא רלוונטי
שימוש בזיכרון30% פחותגבוה יותרמינימלי
עקיפת זיהוי בוטיםטובה (עם stealth)קלה יותר לזיהוילא רלוונטי

Playwright מהיר יותר, מודרני יותר, ותומך טוב יותר ב‑async. במיוחד עבור Google Flights, זה הפתרון המועדף.

צעד‑אחר‑צעד: איך לגרד את Google Flights עם Python

זה הלב של המדריך. נבנה את הסקרייפר שלב אחר שלב.

google-flights-scraping-workflow.webp

שלב 1: הגדרת מחלקות הנתונים

נתחיל בסידור פרמטרי החיפוש ונתוני הטיסות באמצעות dataclasses של Python. זה שומר על סדר ומקל להרחיב את הקוד בהמשך.

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

@dataclass
class SearchParams:
    origin: str          # למשל: "SFO"
    destination: str     # למשל: "JFK"
    departure_date: str  # למשל: "2026-05-15"
    return_date: Optional[str] = None
    trip_type: str = "one-way"  # "one-way" או "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: להבין את מבנה ה‑URL של Google Flights

Google Flights מקודדת את פרמטרי החיפוש באמצעות Protobuf שמקודד ב‑Base64 בתוך פרמטר ה‑URL ‏tfs. אפשר לבצע reverse engineering לקידוד הזה, או לבחור בדרך הפשוטה יותר: לבנות URL של שאילתה בשפה טבעית.

השיטה הפשוטה ביותר היא פורמט החיפוש הבא:

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

ליותר שליטה, אפשר לבנות URLs בצורה תכנותית:

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",
        )
        # קיבוע מראש של עוגיית הסכמה כדי לדלג על החלונית
        await context.add_cookies([{
            "name": "SOCS",
            "value": "CAESHwgBEhJnd3NfMjAyNTAyMjctMF9SQzIaBXpoLUNOIAEaBgiAy6O-Bg",
            "domain": ".google.com",
            "path": "/"
        }])
        page = await context.new_page()

אנחנו מריצים headless לצורכי production (עברו ל‑headless=False לצורכי דיבוג), מגדירים viewport ו‑user agent ריאליסטיים, ומקבעים מראש את עוגיית SOCS כדי לדלג על חלונית ההסכמה — עוד על כך בחלק של מנגנוני ההגנה נגד בוטים.

שלב 4: ניווט לתוצאות החיפוש

טוענים את ה‑URL שנבנה ומחכים עד שתוצאות הטיסות יופיעו:

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

        # מחכים לטעינת תוצאות הטיסות
        await page.wait_for_selector(
            "li.pIav2d", timeout=15000
        )

אם יש כאן timeout, בדרך כלל זה אומר שאו שחלונית ההסכמה חסמה את הדף (ראו תיקון ה‑cookie בשלב 3) או ש‑Google מציגה CAPTCHA. נכסה את שני המצבים בחלק של anti-bot.

שלב 5: טעינת כל תוצאות הטיסה

Google Flights מסתיר תוצאות נוספות מאחורי כפתור "Show more flights". צריך ללחוץ עליו שוב ושוב עד שכל הטיסות מוצגות:

        # לוחצים על "Show more flights" עד שכל התוצאות נטענות
        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

הלולאה הזו לוחצת על הכפתור, מחכה 2 שניות עד שהתוצאות החדשות נרנדרות, ומפסיקה כשהכפתור כבר לא נראה. בבדיקות שלי, ברוב המסלולים יש 1–3 עמודי תוצאות.

שלב 6: חילוץ נתוני הטיסה עם CSS selectors

עכשיו נחלץ בפועל את נתוני הטיסות מהעמוד שנטען. הנה ה‑selectors (מאומתים נכון לאפריל 2026 — ראו בהמשך למה התאריך הזה חשוב):

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

        for card in cards:
            flight = FlightData()

            # שם חברת התעופה
            airline_el = await card.query_selector(
                "div.sSHqwe span:not([class])"
            )
            if airline_el:
                flight.airline = (await airline_el.inner_text()).strip()

            # שעת המראה
            dep_el = await card.query_selector(
                'span[aria-label*="Departure time"]'
            )
            if dep_el:
                flight.departure_time = (await dep_el.inner_text()).strip()

            # שעת נחיתה
            arr_el = await card.query_selector(
                'span[aria-label*="Arrival time"]'
            )
            if arr_el:
                flight.arrival_time = (await arr_el.inner_text()).strip()

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

            # עצירות
            stops_el = await card.query_selector("div.EfT7Ae span")
            if stops_el:
                flight.stops = (await stops_el.inner_text()).strip()

            # מחיר
            price_el = await card.query_selector(
                "div.FpEdX span"
            )
            if price_el:
                flight.price = (await price_el.inner_text()).strip()

            # פליטות CO2
            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 ויכולים להשתנות בכל build. ה‑selectors של 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)

    # שומרים גם כ‑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

רוב המדריכים עוצרים כאן. רוב הסקרייפרים נכשלים כאן. Google השיקה את SearchGuard בינואר 2025, ושברה כמעט כל סקרייפר תוצאות חיפוש בן‑לילה. Google מתארת את זה כ"תוצר של עשרות אלפי שעות עבודה ומיליוני דולרים של השקעה". רמת הקושי של גרידה ב‑Google Flights מוערכת ב‑4/5.

אף מאמר מתחרה לא מכסה את זה לעומק, וזה בדיוק למה סקרייפרים רבים פשוט מפסיקים לעבוד. הנה מה שמולכם ואיך להתמודד.

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 יחד עם השהיות ריאליסטיות בדרך כלל מספיק.

סבב פרוקסי: Datacenter מול Residential

לכל דבר מעבר לכמה חיפושים בודדים, תצטרכו פרוקסי. ההבדל חשוב:

תכונהפרוקסי Datacenterפרוקסי Residential
מהירות100–1,000 Mbps10–100 Mbps
שיעור הצלחה (Google)20–40%85–95%
עלות0.10–0.50 דולר/IP/חודש5–15 דולר/GB
סיכון לזיהויגבוהנמוך מאוד

פרוקסי Residential הם בערך 180x זולים יותר לכל בקשה מוצלחת כשמגרדים אתרים מוגנים. תמחור ספקים ב‑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" חוסמת את התהליך: "first google will show you the 'I agree to terms and conditions' popup." הפתרון הנקי ביותר הוא לקבע מראש את עוגיית 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  # אין חלונית

שימו לב: הטקסט של הכפתור משתנה לפי שפה — "Alle akzeptieren" בגרמנית, "Tout accepter" בצרפתית.

רפרנס מהיר נגד בוטים

טכניקהקושיאפקטיביותצריך קוד?
השהיות אקראיות (2–7 שניות)נמוךבינונית2 שורות
סבב user-agentנמוךבינונית5 שורות
עקיפת זיהוי headlessבינוניגבוהפרמטרי launch של Playwright
תוסף playwright-stealthבינוני60–80% באתרים בסיסייםpip install
סבב פרוקסי (datacenter)בינוניבינוניתהגדרה
סבב פרוקסי (residential)בינוניהצלחה של 85–95%הגדרה
קיבוע מראש של הסכמת עוגיות (SOCS)נמוךנדרששורה אחת

לשיעורי בטיחות מומלצים: שמרו על השהיות של 10–20 שניות בין בקשות עם סבב IP. ספי Google הם בערך 100 בקשות בדקה לכל IP לפני שמקבלים 429, ונפח קבוע מעל 1,000 בקשות ביום לכל IP יכול להוביל לחסימות זמניות.

למה ה‑Selectors שלכם ב‑Google Flights ממשיכים להישבר (ואיך מתקנים)

זה כאב הראש מספר 1 בפער גדול. שרשורי פורומים מלאים בגרסאות שונות של "כל מה שאני מקבל חזרה זה 14 רשימות ריקות." כל מדריך נותן selectors. כמעט אף אחד לא מסביר למה הם נשברים.

למה selectors של Google Flights משתנים

זה נובע משלושה דברים:

  1. הסתרה באמצעות Closure Compiler. Google משתמשת ב‑Closure Stylesheets כדי לייצר שמות מחלקות כמו BVAVmf ו‑YMlIz באמצעות goog.setCssNameMapping(). הם משתנים בכל build — לפעמים מדי שבוע.

  2. A/B testing. משתמשים שונים רואים בו‑זמנית מבני HTML שונים. הסקרייפר שלכם עשוי לעבוד אצלכם, אבל להיכשל אצל מישהו באזור אחר.

  3. הבדלי לוקאל. משתמשים באירופה רואים מונחים, פריסות ואפילו שדות נתונים שונים לעומת משתמשים בארה״ב.

כתיבת selectors עמידים

עדיף לבחור selectors שמבוססים על משמעות ולא על מראה:

# שביר — נשבר בכל build
price_el = await card.query_selector("div.BVAVmf > div.YMlIz")

# עמיד יותר — קשור לתוויות נגישות
dep_el = await card.query_selector('span[aria-label*="Departure time"]')

# גם עמיד — התאמה מבוססת טקסט
more_btn = page.locator('button:has-text("Show more flights")')

היררכיית יציבות selectors (מהיציב ביותר לפחות יציב):

  1. מאפייני aria-label — קשורים לנגישות, כמעט שלא משתנים
  2. מאפייני data-* — נוספים במכוון לצורכי פונקציונליות
  3. מאפייני role — תפקידי ARIA הם סמנטיים
  4. selectors מבוססי טקסט — תואמים לתוכן גלוי
  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

  • בדקו את ה‑selectors מדי חודש, או מיד כשאיכות הפלט יורדת
  • שמרו selectors במילון קונפיגורציה נפרד כדי שיהיה קל לעדכן
  • ה‑selectors במאמר הזה נבדקו לאחרונה: אפריל 2026
  • שקלו את fast-flights library כחלופה — היא משתמשת בפענוח Protobuf במקום CSS selectors, וכך עוקפת את הבעיה הזו לחלוטין (אם כי גם לה יש שבריריות משלה כש‑Google משנה את פורמטי הנתונים הפנימיים)

מגרידה חד‑פעמית למעקב מחירי Google Flights אוטומטי

רוב המדריכים נגמרים ב"שמור ל‑JSON". כותרת המאמר הזה אומרת "Price Alerts". הגיע הזמן לקיים.

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

תזמון הרצת הסקרייפר אוטומטית

אפשרות 1: ספריית Python schedule (הכי פשוטה, חוצה פלטפורמות):

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 job (Linux/Mac):

# הרצה ב‑6 בבוקר וב‑6 בערב בכל יום
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)

# אחרי כל סריקה, בודקים אם יש דילים
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 jobs, שרת פעיל והגדרות proxy נשמעים כמו יותר תשתית ממה שאתם רוצים לתחזק, ה‑Scheduled Scraper של Thunderbit מטפל באותו שימוש בלי כל התקורה הזו. אתם מתארים את תדירות הגרידה בשפה פשוטה, מזינים את כתובות ה‑Google Flights שלכם, והסקרייפר רץ אוטומטית על תשתית הענן של Thunderbit — עם טיפול מובנה ב‑anti-bot וייצוא תוצאות ישירות ל‑Google Sheets, Excel או Airtable. זה לא תחליף מלא לגישת Python (מאבדים התאמה אישית), אבל עבור השימוש הספציפי של "אני רוצה גיליון מעקב מחירים", זה המסלול המהיר ביותר. אפשר לנסות אותו ב‑תוכנית החינמית.

כש‑Python הוא יותר מדי: דרכים ללא קוד לגרידת Google Flights

אחרי שבניתם את כל זה, אגיד בכנות: יש פה לא מעט חלקים נעים. לא כולם צריכים רמת שליטה כזו. selectors נשברים, פרוקסי צריכים סבב, cron jobs צריכים ניטור. אם המטרה שלכם היא "להכניס מחירי טיסות לגיליון באופן קבוע", יש אפשרויות מהירות יותר.

השוואה: Python DIY מול שירותי API מול Thunderbit

גישהזמן הקמהנדרש קודטיפול ב‑anti-botתזמוןעלות
Playwright עצמאי (המדריך הזה)1–2 שעותPython (בינוני)הגדרה ידניתידני (cron)חינם + עלויות פרוקסי
SerpApi Google Flights endpoint15 דק׳קריאות API בלבדמטופלדרך APIכ‑$50+/חודש
Thunderbit Chrome Extension2 דק׳לאגרידה בענןמתזמן מובנהיש תוכנית חינמית

הערה על SerpApi: Google הגישה תביעת DMCA נגד SerpApi בדצמבר 2025, בטענה שהבקשות שלהם גדלו ב‑25,000% בתוך שנתיים. חוסר הוודאות המשפטי הזה שווה שקלול אם אתם בוחנים ספקי API.

איך Thunderbit מגרד את Google Flights

פתחו את תוצאות החיפוש שלכם ב‑Google Flights בתוך Chrome, לחצו על כפתור "AI Suggest Fields" של Thunderbit — ה‑AI קורא את הדף ומציע עמודות כמו חברת תעופה, מחיר, שעת המראה ועצירות — עברו על השדות המוצעים, ולחצו "Scrape." התוצאות יופיעו בטבלה שאפשר לייצא ל‑Excel, Google Sheets, Airtable או Notion — והכול ב‑תוכנית החינמית.

לשימוש של מעקב מחירים במיוחד, Scheduled Scraper של Thunderbit ו‑Cloud Scraping (שיכול להתמודד עם 50 עמודים במקביל) מחליפים את כל התשתית של cron + proxy + שרת.

Python נותן שליטה מלאה והתאמה אישית בלי גבול. Thunderbit נותן מהירות ואפס תחזוקה. תבחרו לפי המטרה האמיתית שלכם. אם אתם רוצים ללמוד עוד על גישות ללא קוד, תסתכלו על המדריך שלנו ל‑טובי web scrapers ללא קוד.

flight-data-options-comparison.webp

האם גרידת Google Flights חוקית? מה חשוב לדעת

משתמשים בפורומים מעלים את זה שוב ושוב: "גרידה ישירה של Google Flights מפרה את תנאי השימוש של Google." זו דאגה לגיטימית — במיוחד כי ה‑API בוטל ואין חלופה מאושרת.

הפרת ToS מול אחריות משפטית

תנאי השירות של Google (עודכנו ב‑22 במאי 2024) קובעים שמשתמשים לא רשאים "לגשת או להשתמש בשירותים או בכל תוכן באמצעות כל אמצעי אוטומטי (כגון רובוטים, עכבישים או סקרייפרים)." הפרת ToS היא הפרת חוזה (עניין אזרחי) — וזה לא אותו דבר כמו עבירה על החוק.

התקדים המשפטי המרכזי: hiQ v. LinkedIn (המעגל התשיעי, 2022) קבע שגרידה של נתונים ציבוריים לא מפרה את Computer Fraud and Abuse Act ‏(CFAA). עם זאת, התיק הסתיים בהסדר, והתביעה של Google נגד SerpApi בדצמבר 2025 נשענת על תיאוריה משפטית אחרת — סעיף 1201 של ה‑DMCA (עקיפת אמצעי הגנה טכנולוגיים) — שעשויה להיות חמורה יותר.

שיטות עבודה מומלצות לגרידה אחראית

  • הגבילו קצב בקשות — השהיות של 10–20 שניות עם סבב IP
  • אל תגרדו נתונים אישיים — מחירי טיסות הם נתונים ציבוריים ומוצגים במצטבר
  • אל תעקפו CAPTCHA בתכנות (זה אזור הסיכון של ה‑DMCA)
  • השתמשו בנתונים למחקר אישי, לא לבניית מוצר מסחרי מתחרה בלי רישוי מתאים
  • שקלו API רשמי כשיש כזה

מקורות נתונים חלופיים

אם גרידה מרגישה מסוכנת מדי לשימוש שלכם, יש גם אפשרויות API לגיטימיות:

ספקעלותתוכנית חינמיתהערות
SerpApi$75–$3,750+/חודש250 חיפושים/חודשJSON ישיר של Google Flights (בבדיקה משפטית)
Kiwi Tequilaחינם (מודל שותפים)ללא הגבלההכי טוב לסטארטאפים ולבדיקות
Amadeusתשלום לפי שימוש2,000 בקשות/חודשיותר מ‑400 חברות תעופה, כולל יכולת הזמנה
Skyscannerמותאם אישיתדורש אישור52 שווקים, 30 שפות

כתבנו פירוט עמוק יותר על ההשלכות המשפטיות של web scraping אם אתם רוצים את התמונה המלאה.

מסקנות עיקריות וסיכום

זה היה הרבה. הנה מה שבאמת חשוב:

  • Python + Playwright הוא הגישה הגמישה ביותר לגרידת Google Flights, אבל היא דורשת תחזוקה מתמשכת
  • מנגנוני anti-bot (השהיות, סבב user-agent, פרוקסי Residential) הם לא אופציונליים — הם חיוניים לאמינות, במיוחד אחרי SearchGuard
  • Selectors נשברים לעיתים קרובות — השתמשו ב‑aria-label וב‑selectors מבוססי טקסט כשאפשר, ותקפו את הפלט עם ולידציה ולוח תחזוקה
  • אוטומציה עם schedule או cron יכולה להפוך גרידה חד‑פעמית למעקב מחירים אמיתי עם נתונים היסטוריים והתראות אימייל
  • Thunderbit מציע חלופה ללא קוד עם תזמון מובנה, גרידה בענן וטיפול ב‑anti-bot — אידיאלי אם המטרה שלכם היא גיליון מעקב מחירים ולא פרויקט קוד
  • כבדו גבולות משפטיים — הגבילו קצב, גרדו רק נתונים ציבוריים, ושקלו חלופות API לשימוש מסחרי

קחו את הקוד מהמדריך הזה, או התקינו את תוסף Chrome של Thunderbit בשביל המסלול המהיר. כך או כך, תעקבו אחרי מחירי טיסות במקום לרענן ידנית את Google Flights.

לעוד טכניקות גרידה ב‑Python, עיינו במדריכים שלנו על איך לגרד אתר עם Python ו‑כלי גרידת web הטובים ביותר ב‑Python.

שאלות נפוצות

1. אפשר לגרד את Google Flights בלי Python?

כן. שירותי API כמו SerpApi ו‑Kiwi Tequila מספקים נתוני טיסות מובנים דרך קריאות API (בלי צורך באוטומציית דפדפן). לגישה מלאה ללא קוד, תוסף Chrome של Thunderbit יכול לגרד ישירות תוצאות של Google Flights מהדפדפן שלכם עם שדות שמוצעים ע״י AI וייצוא בלחיצה אחת.

2. האם Google חוסמת גרידת טיסות?

Google משתמשת בזיהוי בוטים (SearchGuard), ב‑CAPTCHA וב‑rate limiting. עם אמצעי anti-bot מתאימים — השהיות אקראיות, סבב user-agent, פרוקסי Residential והגדרות דפדפן stealth — אפשר לשמור על גרידה אמינה בנפחים בינוניים. ראו את חלק ה‑anti-bot למעלה לטכניקות ולספים הספציפיים.

3. באיזו תדירות כדאי לגרד את Google Flights למעקב מחירים?

פעמיים ביום, בשעות אקראיות, מספיק למעקב אישי ומקטין את סיכון הזיהוי. למעקב עסקי, כל 4–6 שעות עם סבב פרוקסי. הימנעו מגרידה כל שעה, אלא אם מדובר במבצעי תעריפים קצרים — זה מעלה משמעותית את הסיכוי להיחסם.

4. יש API חינמי ל‑Google Flights?

ה‑Google QPX Express API הרשמי בוטל באפריל 2018. אין תחליף רשמי חינמי. האפשרות החינמית הכי קרובה היא Kiwi Tequila API (מודל שותפים, חיפושים ללא הגבלה). SerpApi מציע 250 חיפושים חינם בחודש. לרוב המשתמשים, גרידה או כלי ללא קוד כמו Thunderbit הם הנתיב המעשי.

5. למה ה‑CSS selectors שלי ב‑Google Flights ממשיכים להחזיר נתונים ריקים?

Google משתמשת ב‑Closure Compiler כדי לייצר שמות מחלקות אובפוסקטיים שמשתנים בכל build. גם A/B testing והבדלי לוקאל גורמים למבנה ה‑HTML להשתנות בין משתמשים. הפתרון: להשתמש ב‑aria-label וב‑selectors מבוססי טקסט במקום שמות מחלקות, להוסיף פונקציית ולידציה כדי לאתר שבירות מוקדם, ולבדוק את ה‑selectors מדי חודש. ראו את חלק תחזוקת ה‑selectors לאסטרטגיה מפורטת.

למידע נוסף

Ke
Ke
CTO ב-Thunderbit | מדען נתונים בכיר ומומחה ב-ML עם כמעט עשור של ניסיון בלמידת מכונה ובמדע הנתונים, קה שן הוא בוגר אוניברסיטת קולומביה ולשעבר מדען נתונים בכיר ב-Walmart Labs. עם מומחיות עמוקה ומוכרת בקרב עמיתים ב-Python, R, Java וסטטיסטיקה, הוא משתף תובנות מוכחות-בקרב על המעבר מאלגוריתמי AI מורכבים מתיאוריה לארכיטקטורה ברמת production.
תוכן עניינים

גרדו דף אינטרנט פשוט על ידי בקשה

תגידו מה צריך באנגלית פשוטה. או אפילו לא צריך להגיד כלום.

נסו את Thunderbit חינם
חילוץ נתונים באמצעות AI
העבירו נתונים בקלות ל-Google Sheets, Airtable או Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week