🎯 कस्टमाइज़ करने योग्य, डिटेक्शन-प्रतिरोधी क्लाउड ब्राउज़र जो स्व-विकसित Chromium द्वारा संचालित है, वेब क्रॉलर और एआई एजेंट्स के लिए डिज़ाइन किया गया। 👉अभी आज़माएं
वापस ब्लॉग पर

एक फ़्लाइट प्राइस ट्रैकर बनाएँ स्क्रेपलेस स्क्रेपिंग एपीआई के साथ

Sophia Martinez
Sophia Martinez

Specialist in Anti-Bot Strategies

14-Jul-2026

TL;DR:

  • फ्लाइट वॉच एक रूट और एक तारीख है, URL नहीं। एयरफेयर का कोई एकल मानक पेज नहीं है जैसे कि एक रिटेल SKU — वही यात्रा departure_id, arrival_id, outbound_date, और (राउंड ट्रिप के लिए) return_date के संयोजन का परिणाम है। नीचे दिया गया पाइपलाइन इस संयोजन को सीधे Scrapeless Scraping API के scraper.google.flights एक्टर के माध्यम से प्रश्न करता है बजाय इसके कि Google Flights के अपने इंटरफेस को रेंडर और स्क्रैप करे।
  • यात्रा का प्रकार एक पहला दर्जा का पैरामीटर है, URL विविधता नहीं। data_type एक ही अनुरोध को एकतरफा, राउंड ट्रिप, और मल्टी-सिटी के बीच बदलता है, और मल्टी-सिटी अपनी खुद की multi_city_json की पैरामीटर लेता है। एक रिटेल प्राइस वॉच को इस आयाम की आवश्यकता कभी नहीं होती; एक फ्लाइट वॉच को पहले दिन से इसकी आवश्यकता होती है।
  • Google Flights अपना खुद का वोलाटिलिटी सिग्नल भेजता है। प्रतिक्रिया का price_insights ऑब्जेक्ट lowest_price और एक price_level वर्गीकरण (low, typical, high) सहित best_flights और other_flights में व्यक्तिगत ऑफ़र ले जाता है। इससे पाइपलाइन को "यह वर्तमान में एक अच्छा किराया है," पर सतर्क किया जा सकता है, केवल "यह पिछले बार से सस्ता है" नहीं।
  • इतिहास रूट और तारीख द्वारा कुंजीबद्ध है, प्रति जांच एक बार जोड़ा जाता है। (departure_id, arrival_id, outbound_date, return_date) संयोग के लिए एक रिकॉर्ड के साथ एक JSONL लॉग पर्याप्त है जो एक चलती न्यूनतम की गणना करता है और एक ड्रॉप का पता लगाता है — जब तक वॉचलिस्ट कुछ रूट से अधिक नहीं बढ़ती तब तक डेटाबेस की आवश्यकता नहीं होती है।
  • एक ड्रॉप, या price_level का low में परिवर्तन, एक वेबहुक को सक्रिय करता है। अलर्ट निर्णय संख्यात्मक तुलना को Google के अपने गुणात्मक सिग्नल के साथ मिलाता है, फिर इसे एक बार उस किसी भी एंडपॉइंट को पोस्ट करता है जो इसे प्राप्त करता है।
  • एक शेड्यूलर लूप चलाता है; इसके लिए कुछ भी चलाने की आवश्यकता नहीं है। क्रॉन, टास्क शेड्यूलर, या एक सर्वरलेस टाइमर गति पर जांच करता है और बाहर निकलता है — पाइपलाइन में कोई दीर्घकालिक प्रक्रिया नहीं है।
  • शुरू करने के लिए स्वतंत्र। नए Scrapeless खाते मुफ्त Scraping API क्रेडिट शामिल करते हैं — साइन अप करें app.scrapeless.com पर।

परिचय: एक किराया एक चलती लक्ष्य है जिसमें दो आयाम हैं, एक नहीं

एक रिटेल मूल्य एक एकल संख्या है जो एक एकल पृष्ठ के अनुलग्नक होती है। एक फ्लाइट किराया एक रूट और एक तारीख का कार्य है, और वही रूट इसी खोज के भीतर हर प्रस्थान तिथि के लिए विभिन्न दामों में हो सकता है। यही कारण है कि Google के अपने फ्लाइट कीमत ट्रैकिंग फीचर का अस्तित्व है: एयरलाइंस सीट इन्वेंटरी को मांग के खिलाफ लगातार फिर से मूल्य निर्धारण करती हैं, और एक खरीदार जो एक बार चेक करता है और बुक करता है, वह समय का अनुमान लगा रहा होता है। Google का ट्रैकर उस समय सारांश भेजता है जब ट्रैक की गई route आगे बढ़ती है; यह पाठक को कच्चे डेटा, एक वेबहुक, या उस सिग्नल को किसी अन्य चीज के साथ मिलाने का तरीका नहीं देता है जो पाठक पहले से चला रहा है।

यह पोस्ट एक छोटा पाइपलाइन तैयार करता है जो उस काम को प्रोग्रामेटिक रूप से करता है, Google Flights के इंटरफेस के बजाय एक विशिष्ट रूट और तारीख विंडो के शीर्ष पर। Scrapeless का Scraping API इस सतह के लिए एक विशेष रूप से निर्मित एक्टर भेजता है — scraper.google.flights — जो एक रूट और तारीख संयोजन स्वीकार करता है और संरचित किराया डेटा लौटाता है: व्यक्तिगत ऑफ़र, प्रति ऑफ़र एक एयरलाइन और अवधि, और किराए पर Google का अपना मूल्य-स्तरीय रीड। पाइपलाइन उस एक्टर को प्रश्न करती है, किराया सिग्नल को निकालती है, उन्हें रूट और तारीख से कुंजीबद्ध इतिहास लॉग में जोड़ती है, निर्धारित करती है कि क्या पढ़ाई अलर्ट के लायक है, और जब यह होती है, तो एक वेबहुक को सक्रिय करती है। एक शेड्यूलर जांच को गति पर चलाता है।

एक्टर ने इस पोस्ट के शोध के लिए उपयोग किए गए खाते के लिए एक लाइव disabled actor प्रतिक्रिया वापस की — एक योजना-स्तर की कमी, एक टूटे पाइपलाइन नहीं — और वह कमी उस बिंदु पर प्रलेखित है जहाँ यह महत्वपूर्ण है न कि इसे नजरअंदाज किया जाए। नीचे का हर अन्य चरण वास्तविक, निष्पादित Python पर प्रशिक्षित है।

आप इससे क्या कर सकते हैं

  • व्यक्तिगत किराया वॉच। एक विशिष्ट रूट और तारीख विंडो को ट्रैक करें और केवल तब सूचित हों जब कीमत वास्तव में एक उपयोगी दिशा में हिलती है।
  • लचीला-तारीख खरीदारी। एक फैलाव के उम्मीदवार तिथियों पर उसी रूट को चलाएं और फैलाव के बीच price_insights.price_level की तुलना करें ताकि सबसे सस्ती विंडो मिल सके, ना सिर्फ सबसे सस्ती एकल तारीख।
  • कॉर्पोरेट यात्रा निगरानी। एक यात्रा डेस्क आवर्ती यात्रा या ग्राहक रूट को देख सकती है और यह चिह्नित कर सकती है कि जब एक बुक की गई यात्रा का रूट तब से गिर गया है, जिससे पुनः बुक करने का संकेत मिलता है।
  • मल्टी-सिटी यात्रा ट्रैकिंग। multi_city_json को एक मल्टी-लेग यात्रा को एक ही अनुरोध के प्रतिनिधित्व करने की अनुमति देती है, इसलिए तीन या चार खंडों वाली यात्रा एक कॉल है बजाय तीन या चार अलग-अलग एकतरफा watches के जो हाथ से एक साथ जुड़े हों।
  • किराया-इतिहास डेटा सेट। केवल जोड़ने वाला लॉग एक रूट के लिए एक समय श्रृंखला के रूप में कार्य करता है, जो बाद में प्रवृत्ति विश्लेषण या "अब बुक करें या प्रतीक्षा करें" शास्त्र को खिलाने के लिए उपयोगी है।

इसके लिए Scrapeless Scraping API क्यों

Scrapeless Scraping API साइट-विशिष्ट एक्टर्स को उजागर करता है — scraper.<site> — जो एक लक्षित है के लिए संरचित JSON लौटाते हैं बजाय इसके कि एक रेंडर किया हुआ पृष्ठ हो जिसे कॉलिंग को पार्स करना हो। विशेष रूप से उड़ान डेटा के लिए, इसका मतलब है:

  • एक मार्ग-और-तारीख अनुरोध, न कि एक ब्राउज़र सत्र। कॉल एक प्रमाणित POST है जिसमें एक JSON बॉडी है; कोई पृष्ठ रेंडर करने के लिए नहीं है, कोई चयनकर्ता नहीं है, और कोई सत्र जीवित रखने के लिए नहीं है।
  • अनुरोध रूप में यात्रा प्रकार अंतर्निर्मित है। data_type एकतरफा (2), गोल यात्रा (1), और बहु-शहर (3, multi_city_json के साथ) को शामिल करता है जैसे कि दस्तावेज़ित पैरामीटर, न कि मामले के अनुसार अलग-अलग खुरचने के तर्क के रूप में।
  • एक मूल्यांकन वर्गीकरण जो लक्षित साइट स्वयं गणना करती है। price_insights.price_level गूगल का अपना निर्णय है कि क्या किराया मार्ग के लिए निम्न, सामान्य, या उच्च है - पाइपलाइन उस पर भरोसा कर सकती है इसके बजाय कि प्रारंभ से एक थ्रेशोल्ड का अनुमान लगाया जाए।
  • एक अनुरोध वितरण स्टैक के बजाय। एयरलाइन किराया डेटा अन्यथा एक कॉलर तक एक पैचवर्क GDSes और एयरलाइन-प्रत्यक्ष फीड के माध्यम से पहुंचता है - जिस प्रकार का विखंडित वितरण IATA का नया वितरण क्षमता मानक एयरलाइन-उद्योग पक्ष पर मानकीकरण के लिए मौजूद है। एक एकल संरचित अभिनेता कॉल पूरी तरह से उस स्टैक को दरकिनार करता है केवल पढ़ने के लिए मूल्य निगरानी के लिए।

फ्री-प्लान API कुंजी प्राप्त करें app.scrapeless.com पर। खरचने API उत्पाद पृष्ठ उस अभिनेता परिवार को कवर करता है जिस पर यह पाइपलाइन कॉल करती है, और इस विशेष अभिनेता के लिए अनुरोध रूप को Google उड़ान API अनुरोध गाइड में समझाया गया है।

पूर्वापेक्षाएँ

  • Python 3.10 या नया, प्लस requests पैकेज (pip install requests)।
  • एक Scrapeless खाता और API कुंजी, जिसे SCRAPELESS_API_KEY पर्यावरण चर से पढ़ा जाता है। उत्पादन ट्रैफ़िक को इंगित करने से पहले खाते की योजना के लिए scraper.google.flights अभिनेता सक्रिय है यह सुनिश्चित करें - नीचे Fetch के तहत नोट देखें।
  • अलर्ट प्राप्त करने के लिए एक वेबहुक एंडपॉइंट (Slack, Discord, या कोई भी रिले जो JSON POST स्वीकार करता है)।
  • एक क्रोन-क्षमता वाला होस्ट, विंडोज़ टास्क शेड्यूलर, या एक सर्वर रहित शेड्यूलर जो जाँच को एक आवृत्ति पर चलाने के लिए।

पाइपलाइन एक नज़र में

पाइपलाइन छह चरणों में सीधे एक पंक्ति में बनाई गई है: fetch → discover → extract → transform → store → decide → alert, जिसमें एक शेड्यूलर पूरे काम को एक आवृत्ति पर संचालित करता है।

  1. फेचscraper.google.flights पर एक मार्ग, एक तारीख (या तारीख की अवधि), और एक यात्रा प्रकार POST करें।
  2. डिस्कवर — यात्रा प्रकार लिफाफे को वापस पढ़ें: प्रतिक्रिया ने वास्तव में best_flights, other_flights, और price_insights में से कौन सा भरा।
  3. एक्सट्रैक्ट — सबसे सस्ती पेशकश की कीमत और गूगल के अपने price_level वर्गीकरण को उस लिफाफे से खींचें।
  4. ट्रांसफॉर्म — पढ़ाई को एक मानक रिकॉर्ड में सामान्यीकृत करें जो मार्ग और तारीख द्वारा कुंजीबद्ध हो।
  5. स्टोर — रिकॉर्ड को एक इतिहास लॉग में जोड़ें।
  6. निर्णय लें और अलर्ट करें — इतिहास के खिलाफ तुलना करें, और जब पढ़ाई को उजागर करने लायक हो, तो एक वेबहुक फायर करें।

फेच: एक मार्ग और एक तारीख की अवधि को क्वेरी करें

अनुरोध एक एकल प्रमाणित POST है। अभिनेता का नाम actor में जाता है; मार्ग और तारीख पैरामीटर input में जाते हैं। data_type एक गोल यात्रा के लिए 1 है और एकतरफा के लिए 2; एक गोल यात्रा return_date भी ले जाती है।

python Copy
import os
import requests

API_URL = "https://api.scrapeless.com/api/v1/scraper/request"


def fetch_route(departure_id: str, arrival_id: str, outbound_date: str,
                 return_date: str | None = None, gl: str = "us", hl: str = "en") -> dict:
    payload = {
        "actor": "scraper.google.flights",
        "input": {
            "departure_id": departure_id,
            "arrival_id": arrival_id,
            "data_type": 1 if return_date else 2,   # 1 = गोल यात्रा, 2 = एकतरफा
            "outbound_date": outbound_date,
            "gl": gl,
            "hl": hl,
        },
    }
    if return_date:
        payload["input"]["return_date"] = return_date

    headers = {"Content-Type": "application/json", "x-api-token": os.environ["SCRAPELESS_API_KEY"]}
    response = requests.post(API_URL, headers=headers, json=payload, timeout=60)
    response.raise_for_status()
    return response.json()

outbound_date और return_date सुनहरे YYYY-MM-DD कैलेंडर-तारीख रूप में हैं RFC 3339 द्वारा परिभाषित इंटरनेट तिथि-समय एक्सचेंज के लिए — कोई समय घटक नहीं, क्योंकि किराया खोज एक प्रस्थान दिन से सम्बंधित है, न कि एक प्रस्थान क्षण से। एक बहु-शहर यात्रा योजना को एकल-लेग फ़ील्ड को data_type: 3 और multi_city_json ऐरे से बदलना होगा, जिसमें एक वस्तु प्रति पैर, प्रत्येक अपनी स्वयं की departure_id, arrival_id, और date के साथ होती है।
पूर्ववर्ती अंतर: उपरोक्त कॉल को एक वास्तविक मार्ग के खिलाफ चलाने पर, इस पाइपलाइन की पुष्टि के लिए उपयोग किए गए खाते के लिए एक सक्रिय HTTP 400 वापस मिला: {"कोड": 14002, "संदेश": "अक्षम अभिनेता: scraper.google.flights"}। यह संदेश उस त्रुटि से भिन्न है जो एक अप्रअवस्थीत अभिनेता नाम वापस करता है ("अमान्य अभिनेता: <name>" — कुछ गढ़े हुए रूपांतरों को जानबूझकर अनुरोध करके पुष्टि की गई), जो यह दर्शाता है कि अभिनेता स्वयं वास्तविक और अनुक्रमित है; यह यहां प्रयुक्त मुफ्त योजना से बंद है न कि गायब है। खाते की अंतर्निहित अनुरोध यांत्रिकी पर प्रश्न नहीं है — भाई scraper.google.search अभिनेता ने इसी कुंजी और इसी एंडपॉइंट पर इस शोध के दौरान वास्तविक HTTP 200 के साथ जैविक परिणाम लौटाए। लक्ष्य योजना के लिए अभिनेता सक्षम है (GET /api/v1/me योजना और क्रेडिट संतुलन की रिपोर्ट करता है) को निर्माण उत्पादन ट्रैफ़िक बनाने से पहले पुष्टि करें, और Discover से आगे की सब चीज़ों को अभिनेता की दस्तावेजित प्रतिक्रिया स्वरूप के खिलाफ चलने के रूप में मानें न कि इस सत्र में लाइव कैप्चर किए गए पृष्ठ के रूप में।

खोजें: यात्रा-प्रकार लिफाफा पढ़ना

एक राउंड-ट्रिप या एकतरफा खोज दो ऑफ़र्स की श्रंखलाएँ लौटाती है - best_flights और other_flights - प्लस एक price_insights वस्तु। एक मल्टी-शहर खोज प्रति यात्रा के लिए समकक्ष संरचना लौटाती है न कि प्रति एकल चरण। कुछ भी निकालने से पहले, पाइपलाइन को बस यह जानने की आवश्यकता होती है कि प्रतिक्रिया में वास्तव में कौन से कुंजी भरी गई हैं, क्योंकि एक संकीर्ण मार्ग या एक odd तारीख other_flights को खाली रख सकती है और केवल best_flights ऑफ़र्स ले जा सकती है।

python Copy
def envelope_shape(payload: dict) -> dict:
    return {
        "has_best": bool(payload.get("best_flights")),
        "has_other": bool(payload.get("other_flights")),
        "has_insights": bool(payload.get("price_insights")),
    }


if __name__ == "__main__":
    sample = {"best_flights": [{"price": 312}], "other_flights": [], "price_insights": {}}
    print(envelope_shape(sample))

एक संकीर्ण प्रतिक्रिया के खिलाफ चलाने पर जो केवल best_flights को भरी गई, envelope_shape वापस करता है {'has_best': True, 'has_other': False, 'has_insights': False} — बिल्कुल वह मामला जो नीचे के निकालने वाले चरण को सहन करना है।

यह एक-लाइन की मानसिकता जांच है, लेकिन यह मायने रखता है: एक लिफाफे से "सस्ती पेशकश" निकालना जो कभी भी best_flights को भरा नहीं गया है, एक None उत्पन्न करता है बजाय गलत संख्या के, बशर्ते निकालने वाला चरण दोनों सरणियों की जांच करता है न कि यह मानता है कि एक हमेशा उपस्थित है।

निकालें: प्रतिक्रिया से किराया संकेत निकालें

निकालने वाला चरण दोनों सरणियों और साथ में price_insights क्षेत्रों में से सबसे सस्ती पेशकश को निकालता है। नीचे का फिक्स्चर इस अभिनेता परिवार के लिए दस्तावेजित प्रतिक्रिया संरचना का प्रतिबिंब है — best_flights और other_flights {flights: [...], price, layovers} वस्तुओं की सरणियों के रूप में, और price_insights में lowest_price, price_level, और एक typical_price_range है — क्योंकि लाइव कॉल वह अंतर है जो ऊपर नोट किया गया था न कि इस सत्र में कैप्चर किया गया पृष्ठ।

python Copy
# दस्तावेजित प्रतिक्रिया आकार से मेल खाते एक निरूपात्मक फिक्स्चर (देखें
# फ़ेच के तहत पूर्ववर्ती-गैप नोट) — एक लाइव कैप्चर नहीं।
FIXTURE_RESPONSE = {
    "best_flights": [
        {
            "flights": [{
                "departure_airport": {"id": "JFK", "time": "2026-08-10 08:00"},
                "arrival_airport": {"id": "LAX", "time": "2026-08-10 11:24"},
                "airline": "Example Air",
                "flight_number": "EX 101",
                "duration": 384,
                "travel_class": "Economy",
            }],
            "price": 312,
            "layovers": 0,
        }
    ],
    "other_flights": [
        {"flights": [{"airline": "Sample Airways", "flight_number": "SA 220"}], "price": 349, "layovers": 1}
    ],
    "price_insights": {
        "lowest_price": 289,
        "price_level": "low",
        "typical_price_range": [295, 430],
    },
}


def extract_fares(payload: dict) -> dict:
    best = payload.get("best_flights", [])
    other = payload.get("other_flights", [])
    insights = payload.get("price_insights", {})

    cheapest_offer = min([*best, *other], key=lambda offer: offer["price"], default=None)

    return {
        "cheapest_price": cheapest_offer["price"] if cheapest_offer else None,
        "cheapest_layovers": cheapest_offer["layovers"] if cheapest_offer else None,
        "offer_count": len(best) + len(other),
        "price_insights_lowest": insights.get("lowest_price"),
        "price_level": insights.get("price_level"),
        "typical_price_range": insights.get("typical_price_range"),
    }


if __name__ == "__main__":
    print(extract_fares(FIXTURE_RESPONSE))

फिक्स्चर के खिलाफ चलाने पर, extract_fares लौटाता है:

text Copy
{'cheapest_price': 312, 'cheapest_layovers': 0, 'offer_count': 2,
 'price_insights_lowest': 289, 'price_level': 'low', 'typical_price_range': [295, 430]}

ध्यान दें cheapest_price (वास्तव में लौटाए गए सबसे कम एकल प्रस्ताव) और price_insights_lowest (मार्ग के लिए Google का अपना फर्श) के बीच का अंतर। दोनों अक्सर एकदम मेल नहीं खाते — price_insights इस एक उत्तर में विशिष्ट प्रस्तावों की तुलना में मार्ग पर एक व्यापक पढ़ाई को दर्शाता है — इसलिए दोनों फ़ील्ड को रखें बजाय इसके कि उन्हें एक संख्या में समेकित किया जाए।

रूपांतरित करें: एक रूट-तारीख रिकॉर्ड में मानकीकरण

एक उड़ान निगरानी की पहचान (departure_id, arrival_id, outbound_date, return_date) के टपल है, न कि एक URL। ट्रांसफॉर्म चरण उस कुंजी को एक मानक रिकॉर्ड रूप के साथ बनाता है ताकि भंडारण और तुलना को कभी भी इसे फिर से व्युत्पन्न करने की आवश्यकता न हो।

python Copy
from datetime import datetime, timezone


def to_record(route: dict, fares: dict) -> dict:
    return {
        "departure_id": route["departure_id"],
        "arrival_id": route["arrival_id"],
        "outbound_date": route["outbound_date"],
        "return_date": route.get("return_date"),
        "trip_type": "round_trip" if route.get("return_date") else "one_way",
        "price": fares["cheapest_price"],
        "price_level": fares["price_level"],
        "currency": "USD",
        "checked_at": datetime.now(timezone.utc).strftime("%d-%b-%Y %H:%M UTC"),
    }


def route_key(record: dict) -> str:
    return f"{record['departure_id']}-{record['arrival_id']}:{record['outbound_date']}:{record.get('return_date')}"


if __name__ == "__main__":
    sample_route = {"departure_id": "JFK", "arrival_id": "LAX",
                     "outbound_date": "2026-08-10", "return_date": "2026-08-17"}
    sample_fares = {"cheapest_price": 312, "price_level": "low"}
    record = to_record(sample_route, sample_fares)
    print(record)
    print(route_key(record))

नमूना मार्ग (JFK से LAX, 10 अगस्त को बाहर, 17 अगस्त को लौटने) और उपरोक्त निकाली गई दरों के खिलाफ चलाने पर, to_record एक रिकॉर्ड प्रदान करता है जिसमें price: 312, price_level: 'low', trip_type: 'round_trip' होता है, और route_key JFK-LAX:2026-08-10:2026-08-17 लौटाता है। समान मार्ग और आउटबाउंड तिथि पर एक-तरफा निगरानी उस समय एक अलग कुंजी produes करती है जब return_date अनुपस्थित होता है, जो बात है: एक ही मार्ग पर दो यात्रा प्रकार दो विभिन्न निगरानियाँ हैं, न कि एक।

संग्रहित करें: एक केवल जोड़ने वाली लॉग कुंजी जो मार्ग और तिथि द्वारा अनुक्रमित होती है

भंडारण वही केवल जोड़ने वाली JSONL पैटर्न है जो एक खुदरा मूल्य निगरानी उपयोग करता है, एक अंतर के साथ: लुकअप फ़िल्टर मार्ग-तिथि कुंजी पर होते हैं न कि एक URL पर।

python Copy
import json

HISTORY_FILE = "flight_history.jsonl"


def append_history(record: dict) -> dict:
    with open(HISTORY_FILE, "a", encoding="utf-8") as f:
        f.write(json.dumps(record) + "\n")
    return record


def load_history(key: str) -> list[dict]:
    rows = []
    try:
        with open(HISTORY_FILE, encoding="utf-8") as f:
            for line in f:
                row = json.loads(line)
                if route_key(row) == key:
                    rows.append(row)
    except FileNotFoundError:
        pass
    return rows


if __name__ == "__main__":
    open(HISTORY_FILE, "w").close()  # इस डेमो को एक खाली लॉग से शुरू करें
    sample_record = {"departure_id": "JFK", "arrival_id": "LAX", "outbound_date": "2026-08-10",
                      "return_date": "2026-08-17", "trip_type": "round_trip",
                      "price": 312, "price_level": "low", "currency": "USD",
                      "checked_at": "13-Jul-2026 14:28 UTC"}
    append_history(sample_record)
    append_history(sample_record)
    with open(HISTORY_FILE, encoding="utf-8") as f:
        print(f"{len(f.readlines())} पंक्तियाँ {HISTORY_FILE} में दो चेक के बाद")

एक ही रिकॉर्ड को दो बार जोड़ने और कुंजी द्वारा पुनः लोड करने पर क्रम में दोनों पंक्तियाँ लौटती हैं, यह पुष्टि करती है कि लॉग संचयित होता है न कि अधिलेखित — वही गारंटी जो एक केवल जोड़ने वाली फ़ाइल एक URL-कुंजी वाली निगरानी को देती है, सिर्फ एक अलग कुंजी पर फ़िल्टर की गई। कुछ मार्गों के बाद, फ़ाइल को (departure_id, arrival_id, outbound_date, return_date) को सम्मिलित कुंजी के रूप में छोटे SQLite तालिका के लिए स्वैप करें; पढ़ाई और लिखाई का आकार वही रहता है।

निर्णय लें: इतिहास और Google के अपने मूल्य संकेत के खिलाफ तुलना करें

एक खुदरा मूल्य निगरानी में एक निर्णय नियम होता है: क्या नया मूल्य अब तक देखे गए सबसे कम मूल्य से नीचे है। एक किराया निगरानी में एक दूसरा संकेत उपलब्ध होता है — price_level — इसलिए निर्णय चरण दोनों की जाँच करता है: एक वास्तविक संख्यात्मक गिरावट जो चल रही न्यूनतम से कम है, या एक पहली पढ़ाई जो पहले से ही price_level: "low" पर है।

python Copy
def is_price_drop(history: list[dict], current_price: float, current_level: str) -> dict:
    prior_prices = [r["price"] for r in history if r.get("price") is not None]
    previous_low = min(prior_prices) if prior_prices else None

    numeric_drop = (
        current_price is not None
        and previous_low is not None
        and current_price < previous_low
    )
    level_signal = current_level == "low"

    return {
        "dropped": numeric_drop,
        "worth_alerting": numeric_drop or (level_signal and previous_low is None),
        "current": current_price,
        "previous_low": previous_low,
    }

"मूल्य_स्तर": वर्तमान_स्तर,
}

if name == "main":
इतिहास = [{"मूल्य": 349}, {"मूल्य": 340}]
print(is_price_drop(इतिहास, वर्तमान_मूल्य=312, वर्तमान_स्तर="कम"))

Copy
349 और 340 के दो पूर्व रीडिंग के खिलाफ, 312 का नया रीडिंग `मूल्य_स्तर: "कम"` के साथ `{'गिरा': True, 'चेतावनी देने के लायक': True, 'वर्तमान': 312, 'पिछला_कम': 340, 'मूल्य_स्तर': 'कम'}` लौटाता है। `चेतावनी देने के लायक` फ़ील्ड जानबूझकर `गिरा` से व्यापक है: यह गूगल की अपनी वर्गीकरण द्वारा पहले रीडिंग पर भी सक्रिय होता है यदि यह पहले से ही इसे कम कहता है, क्योंकि चल रहे-निम्न तुलना के लिए अभी तक कोई सामान नहीं है, लेकिन गुणात्मक संकेत है। वह भेद कुछ <a href="https://arxiv.org/abs/2411.08126" rel="nofollow"><strong>हवाई टिकट बाजारों में ऑफ़लाइन डायनैमिक प्राइसिंग पर अनुसंधान</strong></a> द्वारा श्रेणी की एक संरचनात्मक विशेषता के रूप में व्यवहार किया जाता है: हवाई यात्रा की सीट सूची मांग और शेष क्षमता के खिलाफ पुनः मूल्यांकन करती है, किसी निश्चित बिक्री कैलेंडर के खिलाफ नहीं, इसलिए एकल स्नैपशॉट पहले से ही सूचनात्मक हो सकता है बिना कीमत के इतिहास की तुलना करने के लिए।

## चेतावनी: ड्राप पर एक वेबहुक भेजें

चेतावनी तंत्र वही एकल `requests.post` है जिसका उपयोग रिटेल वॉच करता है — एक HTTP कॉल, कोई कतार नहीं, कोई ब्रोकर नहीं — संदेश को उत्पाद नाम के बजाय मार्ग-और-तारीख की पहचान से बनाया गया है।

```python
def send_alert(route_label: str, निर्णय: dict, webhook_url: str) -> int:
    संदेश = (
        f"भाड़ा वॉच: {route_label}\n"
        f"अब ${निर्णय['वर्तमान']} (था ${निर्णय['पिछला_कम']}), "
        f"गूगल मूल्य_स्तर={निर्णय['मूल्य_स्तर']}"
    )
    प्रतिक्रिया = requests.post(webhook_url, json={"text": संदेश}, timeout=15)
    प्रतिक्रिया.raise_for_status()
    return प्रतिक्रिया.status_code

raise_for_status() तुरंत एक टूटे हुए वेबहुक अंत बिंदु को सतही पर लाता है, बजाय इसके कि एक गलत कॉन्फ़िगर किया गया रिले हर अलर्ट को चुपचाप निगल ले। {"text": संदेश} शरीर सामान्य स्लैक/डिस्कॉर्ड इनकमिंग-वेबहुक आकार से मेल खाता है; प्राप्त करने वाले अंत बिंदु को जो भी अपेक्षित हो उसके लिए JSON को समायोजित करें।

अनुसूची: यात्रा द्वारा सीमा बंधी एक ताल पर मतदान करें

चेतावनी के माध्यम से फ़ेच को एक फ़ंक्शन में तार करना एकल check_route() कॉल देता है जिसे शेड्यूलर चला सकता है:

python Copy
def check_route(departure_id, arrival_id, outbound_date, return_date=None, webhook_url=None):
    पे लोड = fetch_route(departure_id, arrival_id, outbound_date, return_date)
    किराए = extract_fares(पे लोड)
    मार्ग = {"departure_id": departure_id, "arrival_id": arrival_id,
             "outbound_date": outbound_date, "return_date": return_date}
    रिकॉर्ड = append_history(to_record(मार्ग, किराए))
    निर्णय = is_price_drop(load_history(route_key(रिकॉर्ड)), रिकॉर्ड["मूल्य"], रिकॉर्ड["मूल्य_स्तर"])
    if निर्णय["चेतावनी देने के लायक"] और webhook_url:
        send_alert(f"{departure_id}-{arrival_id} {outbound_date}", निर्णय, webhook_url)
    return रिकॉर्ड, निर्णय

अधिकतर व्यक्तिगत वॉच के लिए एक दैनिक रन पर्याप्त है; एक कॉर्पोरेट यात्रा डेस्क कई पुनरावर्ती मार्गों पर नज़र रखती है, वह अधिक बार चला सकती है। एक रिटेल SKU के विपरीत, एक उड़ान वॉच का भी एक स्वाभाविक अंत है: एक बार जब आउटबाउंड तारीख गुजर जाती है, तो मार्ग जीवित खोज बनना बंद कर देता है और इसके लिए इतिहास पूरा हो जाता है। Linux या macOS पर सबसे सरल ड्राइवर एक क्रोनटैब प्रविष्टि है, जो किसी भी अनुसूचित कार्य पर प्लेटफॉर्म का उपयोग करता है:

bash Copy
# क्रोनटैब -e — दिन में दो बार जांचें, 07:00 और 19:00
0 7,19 * * * cd /opt/fare-watch && /usr/bin/python3 check.py >> watch.log 2>&1

Windows टास्क शेड्यूलर एक समकक्ष ट्रिगर पर वही कमांड चलाता है, और एक सर्वरलेस टाइमर भी काम करता है - स्क्रिप्ट के पास इतिहास फ़ाइल के अलावा कोई स्थिति नहीं है, इसलिए एक अमूर्त क्रियाविधि उसके लिए उपयुक्त है। कई मार्गों की वॉच लिस्ट के लिए, check_route() को सूची पर लूप करें और समांतरता को संयमित रखें ताकि API कुंजी की दर सीमाएँ आरामदायक बनी रहें।

आपको क्या वापस मिलता है

प्रत्येक जांच एक रिकॉर्ड को इतिहास लॉग में जोड़ती है, जो मार्ग और तिथि द्वारा कुंजीबद्ध होती है, URL द्वारा नहीं:

json Copy
// स्कीमा ठीक वही दर्शाता है जो to_record()/append_history() लिखता है।
// फ़ील्ड मान चित्रणात्मक हैं, जो ऊपर उपयोग की गई फ़िक्स्चर से मेल खाते हैं - कोई लाइव रीडिंग नहीं।
{
  "departure_id": "JFK",
  "arrival_id": "LAX",
  "outbound_date": "2026-08-10",
  "return_date": "2026-08-17",
  "trip_type": "round_trip",
  "मूल्य": 312,
  "मूल्य_स्तर": "कम",
  "मुद्रा": "USD",
  "जांच की गई_at": "13-जुलाई-2026 15:01 UTC"
}

इससे पहले कि आप इसे बड़े पैमाने पर चलाएं, जानने के लिए कुछ बिंदु:

  • मूल्य और मूल्य_अवबोधन_सबसे_कम भिन्न हो सकते हैं। पूर्व का अर्थ है कि यह विशेष प्रतिक्रिया का सबसे सस्ता प्रस्ताव है; बाद का अर्थ है गूगल की मार्ग पर व्यापक दृष्टि। यदि डाउनस्ट्रीम उपयोग का मामला अंतर के बारे में परवाह करता है तो दोनों को स्टोर करें।
  • संग्रहीत आंकड़ा पूरी दर होनी चाहिए, आधार दर नहीं। यूएस carriers जो एक दर का विज्ञापन करते हैं, उन्हें संघीय पूर्ण-फेर विज्ञापन नियम के तहत उस पूरी कीमत को उद्धृत करना होता है जो एक यात्री चुकाता है, न कि एक कटौती की हुई आधार दर — पाइपलाइन में किसी भी क्षेत्र को price के रूप में उसी तरीके से मानें, ताकि चेक के बीच तुलना कभी भी एक दिन के लिए आधार किराए और दूसरे दिन के लिए कुल लागत न हो।
  • बहु-शहर आकार को बदलता है, पाइपलाइन को नहीं। data_type: 3 और multi_city_json एक यात्रा-स्तरीय प्रतिक्रिया उत्पन्न करते हैं, न कि एकल-लेग की; to_record को विस्तारित करें ताकि प्रत्येक लेग की कीमत को कुल में शामिल किया जा सके, न कि यात्रा को एक समतल दर के रूप में माना जाए।
  • gl और hl मुद्रा और भाषा को प्रभावित करते हैं, सिर्फ शब्दों को नहीं। दोनों को एक खुदरा घड़ी की तरह पिन करें जो proxy_country को पिन करती है, ताकि चेक के बीच तुलना एक ही बाजार पर बनी रहे।
  • एक नल-योग्य मूल्य शून्य नहीं है। एक गुम cheapest_price को None के रूप में मानें, ठीक वैसे ही जैसे कि निष्कर्षण चरण पहले ही करता है — एक असामान्य अजीब प्रतिक्रिया कभी भी चल रहे निम्न में एक गलत शून्य नहीं डालनी चाहिए।

निष्कर्ष: एक मार्ग, एक तिथि, एक निर्णय

पाइपलाइन किसी अन्य मूल्य निगरानी के समान आकार में घटित होती है — लाना, निष्कर्षण, संग्रहीत करना, निर्णय लेना, चेतावनी देना, अनुसूची बनाना — "निगरानी" की पहचान को श्रेणी के लिए फिर से परिभाषित किया गया है: एक मार्ग और एक तिथि जोड़ी एक यूआरएल के बजाय, एक यात्रा प्रकार पैरामीटर एकल अनुरोध आकार के बजाय, और एक दूसरा, लक्षित-प्रदत्त सिग्नल (price_level) सामान्य संख्यात्मक तुलना के साथ उपलब्ध है। अधिक मार्गों, अधिक तारीखों या एक बहु-शहर यात्रा कार्यक्रम के लिए वॉच लिस्ट का विस्तार बिना किसी बदलाव के हर चरण को फिर से उपयोग करता है; केवल Fetch में पारित किए गए पैरामीटर बदलते हैं।

क्या आप अपने AI-संचालित डेटा पाइपलाइन बनाने के लिए तैयार हैं?

समुदाय में शामिल हों ताकि डेटा पाइपलाइन बनाने वाले डेवलपर्स के साथ नोट्स का आदान-प्रदान किया जा सके: Discord · Telegram

app.scrapeless.com पर मुफ्त स्क्रैपिंग API क्रेडिट के लिए साइन अप करें, सुनिश्चित करें कि खाता की योजना पर scraper.google.flights अभिनेता सक्षम किया गया है, और ऊपर के चरणों को वॉच लिस्ट की जरूरत के लिए अनुकूलित करें। योजना विवरण के लिए मूल्य निर्धारण देखें, और वर्तमान अभिनेता संदर्भ के लिए स्क्रैपिंग API डॉक

सामान्य प्रश्न

प्रश्न: क्या scraper.google.flights एक वास्तविक, प्रलेखित अभिनेता है?
हाँ। जानबूझकर बनाया गया अभिनेता नाम "invalid actor: <name>" लौटाता है; scraper.google.flights का अनुरोध "disabled actor: scraper.google.flights" लौटाता है — एक विशिष्ट त्रुटि जो केवल मान्यता प्राप्त, सूचीबद्ध अभिनेता को लागू होती है। यह कुछ योजनाओं से बंद होता है, न कि गैर-मौजूद; सुनिश्चित करें कि यह लक्षित खाते की योजना के लिए सक्षम है इससे पहले कि आप उत्पादन ट्रैफिक को इस पर केंद्रित करें।

प्रश्न: एक URL के बजाय मार्ग और तिथि द्वारा इतिहास को क्यों कुंजीबद्ध करें, जैसा कि खुदरा निगरानी करता है?
क्योंकि एक उड़ान की दर के पास किसी खुदरा SKU की तरह कोई एकल मानक पृष्ठ नहीं है। एक ही मार्ग अलग-अलग प्रस्थान तिथि, वापसी तिथि, और यात्रा प्रकार के अनुसार अलग-अलग मूल्य रखता है, इसलिए "निगरानी" की पहचान उस संयोजन होनी चाहिए, न कि एक यूआरएल।

प्रश्न: price_insights.price_level एक साधारण "पिछली बार से सस्ता" तुलना पर क्या जोड़ता है?
यह मार्ग के लिए मौजूदा दर कम, सामान्य, या उच्च है, इसका Google का स्वयं का वर्गीकरण है, जो उस डेटा से गणना की जाती है जो पाइपलाइन अभी मिली एकल प्रतिक्रिया से परे है। इसे चल रहे निम्न तुलना के साथ मिलाना एक मार्ग की पहली रीडिंग को कार्रवाई योग्य बनाता है, न कि केवल इसके दूसरे और बाद के स्थानों को।

प्रश्न: क्या यह बहु-शहर यात्रा कार्यक्रम को संभाल सकता है?
हाँ, अनुरोध स्तर पर — data_type: 3 के साथ multi_city_json लेग की एक श्रृंखला इस अभिनेता परिवार के लिए एक प्रलेखित पैरामीटर आकार है। एक बहु-लेग यात्रा कार्यक्रम को एक तुलनीय कुल में समेटने के लिए to_record का विस्तार करना एक बहु-शहर निगरानी के लिए एक अतिरिक्त तर्क है, जो इस पोस्ट द्वारा निर्मित नहीं है।

प्रश्न: जांच कितनी बार चलानी चाहिए?
एक दैनिक या दो बार दैनिक चलना अधिकांश व्यक्तिगत किराया निगरानियों के लिए उपयुक्त है। एक उड़ान निगरानी के लिए एक स्वाभाविक रोक भी है: जब तक प्रस्थान की तारीख गुजरती है, मार्ग अब एक सक्रिय खोज नहीं है, इसके विपरीत एक खुदरा SKU जो अनिश्चितकाल तक देखा जा सकता है।

प्रश्न: क्या यह बिना AI एजेंट के चल सकता है?
हाँ। Fetch के भीतर Python Schedule के माध्यम से अपने आप अंत से अंत तक चलता है — कॉल, निष्कर्षण, संग्रहित, निर्णय, चेतावनी, और एक शेड्यूलर को ताल बनाए रखने दें। एक एजेंट मार्ग और तिथि चयन को प्राकृतिक भाषा में चलाने का एक सुविधाजनक तरीका है, लेकिन पाइपलाइन को इसके लिए केवल Python और एक शेड्यूलर की आवश्यकता होती है।

स्क्रैपलेस में, हम केवल सार्वजनिक रूप से उपलब्ध डेटा का उपयोग करते हैं, जबकि लागू कानूनों, विनियमों और वेबसाइट गोपनीयता नीतियों का सख्ती से अनुपालन करते हैं। इस ब्लॉग में सामग्री केवल प्रदर्शन उद्देश्यों के लिए है और इसमें कोई अवैध या उल्लंघन करने वाली गतिविधियों को शामिल नहीं किया गया है। हम इस ब्लॉग या तृतीय-पक्ष लिंक से जानकारी के उपयोग के लिए सभी देयता को कोई गारंटी नहीं देते हैं और सभी देयता का खुलासा करते हैं। किसी भी स्क्रैपिंग गतिविधियों में संलग्न होने से पहले, अपने कानूनी सलाहकार से परामर्श करें और लक्ष्य वेबसाइट की सेवा की शर्तों की समीक्षा करें या आवश्यक अनुमतियाँ प्राप्त करें।

सबसे लोकप्रिय लेख

सूची