Cheap Flights API

Cheap Flights Checker – Flight for your dream

Spread the love

We love travel and we love easy stuff. So we have our easy flight prices checker :]] . When we start to plan our vacation, flight ticket is always the first item we purchase. It would be great if we can have a look on cheap flight tickets around our desired date. No worries, the wait is over. This time, we are using Python (as always) to make a cheap flights checker.

Get the Cheap Flights

First things first, we do not get cheap flight prices from thin air. So we have to get the price info from somewhere. Ideally, we should get anything from Google, but the reality is, Google cut their flight fare API in April 2018.

There are several travel search sites, like Skyscanner, Expedia and Kayak. We should possibly get the cheap flight prices from APIs on those sites. The good thing is, they do provide APIs for getting flight prices, but the bad thing is, they only open those APIs to travel agencies and partners. Well, CodeAStar never gonna let you down. We can’t get the API from those travel search sites directly, but we can still be able to get it from an API marketplace and it is FREE.

So we start our journey from the API marketplace, RapidAPI.

Setup your API account

RapidAPI is a place where developers can purchase API access from different providers. There are several free APIs as well and the flight prices API is one of them. So we need to create an account there (again, it is free). After that we can go to the flight prices API page which is connected to Skyscanner API.

The actual sequence should look like:

RapidAPI Skyscanner API

Once the account has been done, the rests are straight forward. We can use Python modules, requests and JSON, to get flight prices, likes the way we did in getting weather info from AccuWeather.

Design before Develop Cheap Flights

From our past exercises, we learnt how to make applications with programming. But it will be much better if we make useful applications with programming. In order to make our flight prices checker application, we should design the app using user’s perspectives. As a cheap flights seeker, I would like to know:

  • the lowest price of my desired trip
  • the airlines with lowest offer
  • the prices around my desired date
  • is it a direct flight
  • my previous searching criteria

Then I would expect a similar screen popping up when I have inputted flight query:

Flight Prices Results

I also want all of the above things are done from a command line interface (CLI). Okay, we have a design blueprint in our mind, let’ starting coding.

Remember what the user wanted

From the AccuWeather forecast exercise, we used TinyDB to record the AccuWeather API key, so a user doesn’t need to enter it every time he/she runs the program. This time, other than the API key, we store the user’s last successful search criteria as well. So we start with importing required libraries and creating TinyDB:

import requests
import os, sys, json
from datetime import date, timedelta, datetime
from tinydb import TinyDB, Query

db = TinyDB('skyscanner.json')
Profile = db.table('Profile')
Place = db.table('Place')
ENDPOINT_PREFIX = "https://skyscanner-skyscanner-flight-search-v1.p.rapidapi.com/apiservices/"
TAG = "Return \\ Depart"
END_TAG = " | "
CELL_LENGTH = int(len(TAG))

We write a function to load user’s API key and previous searching criteria from TinyDB when the program is started. And we also provide default values when there is no previous input.

def initProfileDB():
  if "SKYSCAN_RAPID_API_KEY" in os.environ:
    API_KEY = os.environ['SKYSCAN_RAPID_API_KEY']   
    Profile.upsert({'api_key':API_KEY}, Query().api_key.exists())
  else: 
    API_KEY = Profile.search(Query().api_key)
    if API_KEY == []: 
      sys.exit("No API key found")
    API_KEY = API_KEY[0]['api_key']

  init_market = "US" if Profile.search(Query().market)==[] else Profile.search(Query().market)[0]['market']
  init_from = "SFO" if Profile.search(Query().place_from)==[] else Profile.search(Query().place_from)[0]['place_from']
  init_to = "JFK" if Profile.search(Query().place_to)==[] else Profile.search(Query().place_to)[0]['place_to']
  init_connect = "Y" if Profile.search(Query().connect)==[] else Profile.search(Query().connect)[0]['connect']
  init_currency = "USD" if Profile.search(Query().currency)==[] else Profile.search(Query().currency)[0]['currency']
  init_depart = (date.today() + timedelta(days=7)).strftime('%Y-%m-%d') if (Profile.search(Query().date_depart)==[] or (date.today() > datetime.strptime(Profile.search(Query().date_depart)[0]['date_depart'], '%Y-%m-%d').date())) else Profile.search(Query().date_depart)[0]['date_depart']
  init_return = (date.today() + timedelta(days=11)).strftime('%Y-%m-%d') if (Profile.search(Query().date_return)==[] or (date.today() > datetime.strptime(Profile.search(Query().date_return)[0]['date_return'], '%Y-%m-%d').date())) else Profile.search(Query().date_return)[0]['date_return']

  profile_dict = {
    "API_KEY": API_KEY,
    "init_market": init_market,
    "init_from": init_from, 
    "init_to": init_to,
    "init_connect": init_connect,
    "init_currency": init_currency,
    "init_depart": init_depart,
    "init_return": init_return,
  }
  return profile_dict

After that, we build a CLI to let user enter searching criteria.

profile_dict = initProfileDB()  #init our profile
headers = {
    'x-rapidapi-host': "skyscanner-skyscanner-flight-search-v1.p.rapidapi.com",
    'x-rapidapi-key': profile_dict["API_KEY"]
    }

market= input(f"Market Country(ISO two-letter country code) [{profile_dict['init_market']}]: ") or profile_dict['init_market']
place_from = input(f"From(place name or IATA code) [{profile_dict['init_from']}]: ") or profile_dict['init_from']
place_to = input(f"To(place name or IATA code) [{profile_dict['init_to']}]: ") or profile_dict['init_to'] 
connect = input(f"Consider Connecting Flight(Y/N) [{profile_dict['init_connect']}]: ") or profile_dict['init_connect']
currency = input(f"Currency [{profile_dict['init_currency']}]: ") or profile_dict['init_currency']
date_depart = input(f"Depart (YYYY-MM-DD) [{profile_dict['init_depart']}]: ") or profile_dict['init_depart']
date_return = input(f"Return (YYYY-MM-DD) [{profile_dict['init_return']}]: ") or profile_dict['init_return']

We can try to run our cheap flights seeker program. But before doing this, we have to enter the RapidAPI key for the first run.

Run following line on your console:
>export SKYSCAN_RAPID_API_KEY=abcde   (for Linux/MacOS)
or
>$Env:SKYSCAN_RAPID_API_KEY="abcde"   (for Win10, double quotes are needed)

When we start our program, we should see following input interface popping out from our console:

Market Country(ISO two-letter country code) [US]:
From(place name or IATA code) [SFO]:
To(place name or IATA code) [JFK]:
Consider Connecting Flight(Y/N) [Y]:
Currency [USD]:
Depart (YYYY-MM-DD) [2020-01-17]:
Return (YYYY-MM-DD) [2020-01-21]:

You can either input your data or just press enter to use the previous inputs. At this moment, our program won’t find the cheap flights for you yet, but we can have a look and feel on how we handle user input.

Where all the cheap flights magic happens

Now it is the time we code our connection logic with RapidAPI-Skyscanner. Other than the cheap flights searching logic, we would like to a location searching one. Thus Skyscanner can provide an IATA code for us, when we enter our departing / returning places.

def handleAPIException(responseText, apiname):
      print(json.dumps(json.loads(responseText), indent=3, sort_keys=True))
      sys.exit(f"API exception on [{apiname}]")

def getIataCodeByString(place_string, market, currency, headers):
    place_info = Place.search(Query()['search_string'] == place_string)  #search from DB first
    if place_info == []:
        print("Searching IATA code from Skyscanner...")    #search from Skyscanner API
        url = ENDPOINT_PREFIX+f"autosuggest/v1.0/{market}/{currency}/en-US/"
        querystring = {"query":place_string}
        response = requests.request("GET", url, headers=headers, params=querystring)
        if response.status_code != 200: handleAPIException(response.text, getIataCodeByString.__name__)
        place_json = json.loads(response.text)
        for place in place_json["Places"]:
            if len(place['PlaceId']) == 7:
                Place.upsert({'search_string':place_string, 'iata':place['PlaceId'][:3], 'name':place['PlaceName'], 'country':place['CountryName']}, Query().api_key.exists())
                iata_code = place['PlaceId'][:3]
                break
    else: 
       iata_code =  place_info[0]["iata"]   #get the code from DB
    return iata_code

place_from = getIataCodeByString(place_from, market, currency, headers) if (len(place_from) > 3) else place_from  
place_to = getIataCodeByString(place_to, market, currency, headers) if (len(place_to) > 3) else place_to  

We have currency, market, places and dates as our searching criteria, let’s do our magic here by sending cheap flights request to RapidAPI-Skyscanner:

def getCheapQuote(market, currency, place_from, place_to, date_depart, date_return, check_place):    
    url = ENDPOINT_PREFIX+f"browsequotes/v1.0/{market}/{currency}/en-US/{place_from}/{place_to}/{date_depart}/{date_return}"
    response = requests.request("GET", url, headers=headers)
    if response.status_code != 200: handleAPIException(response.text, "browse quotes")
    quotes_json = json.loads(response.text)
    min_price_low = None
    carrier_names = []
    is_direct = "N/A"
    for quote in quotes_json["Quotes"]:
        direct_flight = quote['Direct']
        if (connect==False and direct_flight==False): continue
        min_price_this = quote['MinPrice']     
        if (min_price_low == None or min_price_this < min_price_low):  
            min_price_low = min_price_this
            is_direct = direct_flight
            carrier_id_outbound = quote['OutboundLeg']['CarrierIds']
            carrier_id_inbound = quote['InboundLeg']['CarrierIds']
            carrier_ids = set(carrier_id_outbound + carrier_id_inbound)

    if min_price_low != None: 
        for carrier in quotes_json["Carriers"]:
          carrier_id = carrier['CarrierId']
          if carrier_id in carrier_ids:
            carrier_name = carrier['Name']
            carrier_names.append(carrier_name)
            if len(carrier_names) == len(carrier_ids): break
        if (check_place):    
          for place in quotes_json["Places"]:     
            iata_code = place['IataCode']
            if (iata_code == place_from): 
              place_from = f"{place_from} - {place['Name']}, {place['CityName']}, {place['CountryName']}"
            elif (iata_code == place_to): 
              place_to = f"{place_to} - {place['Name']}, {place['CityName']}, {place['CountryName']}"

    cheapquote_dict = {
      "price": min_price_low,
      "carriers": carrier_names,
      "is_direct": is_direct,
      "place_from": place_from, 
      "place_to": place_to
    }         
    return cheapquote_dict

selected_cheapquote_dict = getCheapQuote(market, currency, place_from, place_to, date_depart, date_return, True)

Ding! We got the cheap flight info from Skyscanner! Please note that we only got one cheap flight from our desired date range. What if there are cheaper options around my desired dates? No problem, we search the dates around the inputted date range as well. Keep in mind that the free Skyscanner API can only allow 50 requests per minute. Try not to run your code that often.

First, we define a wider date range from our desired travel date, i.e. +3 / -3 days of our depart and return dates:

dates_depart = []
dates_return = []
selected_date_depart = date_depart
selected_date_return = date_return
dates_depart.append(date_depart)
dates_return.append(date_return)

for change_day_minus in range(3): #do last 3 days search
  date_depart_d_obj = datetime.strptime(date_depart, '%Y-%m-%d').date()
  if (date_depart_d_obj > date.today()):
    date_depart = (date_depart_d_obj - timedelta(days=1)).strftime('%Y-%m-%d')
    date_return = (datetime.strptime(date_return, '%Y-%m-%d').date() - timedelta(days=1)).strftime('%Y-%m-%d')
    dates_depart.insert(0,date_depart)
    dates_return.insert(0,date_return)
  else: 
    break

date_depart = selected_date_depart
date_return = selected_date_return
for change_day_plus in range(3):  #do next 3 days search
  date_depart = (datetime.strptime(date_depart, '%Y-%m-%d').date() + timedelta(days=1)).strftime('%Y-%m-%d')
  date_return = (datetime.strptime(date_return, '%Y-%m-%d').date() + timedelta(days=1)).strftime('%Y-%m-%d')
  dates_depart.append(date_depart)
  dates_return.append(date_return)

Second, we call the cheap flights API again using our wider date range and build the price grid:

dates_return.insert(0,TAG)
row_length = 0
for row_index, row_cell in enumerate(dates_return):
  row_cell = row_cell+"*" if (row_cell == selected_date_return) else row_cell
  print(f"\n{row_cell}{' '*(len(TAG)-len(row_cell))}", end =END_TAG)
  if row_index==0:
     for col_cell in dates_depart:
       col_cell = col_cell+"*" if (col_cell == selected_date_depart) else col_cell
       print(f"{col_cell}{' '*(CELL_LENGTH-len(col_cell))}", end=END_TAG)
       row_length += CELL_LENGTH+len(END_TAG)
     print(f"\n{'-'*(len(TAG))}{END_TAG}{'-'*row_length}", end ="")
  else: 
     cheapquotes = []
     for col_cell in dates_depart:
       if (row_cell[:10]==selected_date_return and col_cell==selected_date_depart):
         cheapquote_dict = selected_cheapquote_dict
       elif (datetime.strptime(col_cell, '%Y-%m-%d').date() < datetime.strptime(row_cell[:10], '%Y-%m-%d').date()):
         cheapquote_dict = getCheapQuote(market, currency, place_from, place_to, col_cell, row_cell[:10], False)
       else: 
         cheapquote_dict = { "price": None }
       cheapquotes.append(cheapquote_dict)
       displayPrice(cheapquote_dict, 0)
     
     for i in range(1,3):
       print(f"\n{' '*(len(TAG))}", end =END_TAG)
       for cheapquote in cheapquotes:
         displayPrice(cheapquote, i)   
     print(f"\n{'-'*(len(TAG))}{END_TAG}{'-'*row_length}", end ="")

We're all done! I love cheap flights!

I am going to have a short trip from Hong Kong to Japan in March, let' see what we get from our newly coded cheap flights seeker. When I start the program and input my flight information:

>python .\skyscanner.py
Market Country(ISO two-letter country code) [US]: HK
From(place name or IATA code) [SFO]: Hong Kong
To(place name or IATA code) [JFK]: Tokyo
Consider Connecting Flight(Y/N) [N]: n
Currency [USD]: 
Depart (YYYY-MM-DD) [2020-03-12]: 
Return (YYYY-MM-DD) [2020-03-15]: 

The program should send above information to Skyscanner and generate a price grid like this:

Start processing your request...

From: HKG - Hong Kong Intl, Hong Kong, Hong Kong
To: NRT - Tokyo Narita, Tokyo, Japan
Depart: 2020-03-12
Return: 2020-03-15
Consider Connecting Fligh? N

Return \ Depart | 2020-03-09      | 2020-03-10      | 2020-03-11      | 2020-03-12*     | 2020-03-13      | 2020-03-14      | 2020-03-15      | 
--------------- | ------------------------------------------------------------------------------------------------------------------------------      
2020-03-12      | USD 253.0       | USD 166.0       | USD 267.0       | No info found   | No info found   | No info found   | No info found   | 
                | Hong Kong Air.. | Jetstar         | Jetstar         | No info found   | No info found   | No info found   | No info found   | 
                | Direct? Y       | Direct? Y       | Direct? Y       | No info found   | No info found   | No info found   | No info found   |       
--------------- | ------------------------------------------------------------------------------------------------------------------------------      
2020-03-13      | USD 282.0       | USD 282.0       | USD 166.0       | USD 166.0       | No info found   | No info found   | No info found   | 
                | Hong Kong Air.. | Hong Kong Air.. | Jetstar         | Jetstar         | No info found   | No info found   | No info found   | 
                | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       | No info found   | No info found   | No info found   |       
--------------- | ------------------------------------------------------------------------------------------------------------------------------      
2020-03-14      | USD 253.0       | USD 260.0       | USD 253.0       | USD 253.0       | USD 344.0       | No info found   | No info found   | 
                | Hong Kong Air.. | Hong Kong Air.. | Hong Kong Air.. | Hong Kong Air.. | HK Express      | No info found   | No info found   | 
                | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       | No info found   | No info found   |       
--------------- | ------------------------------------------------------------------------------------------------------------------------------      
2020-03-15*     | USD 260.0       | USD 260.0       | USD 253.0       | USD 260.0       | USD 253.0       | USD 462.0       | No info found   | 
                | Hong Kong Air.. | Hong Kong Air.. | Hong Kong Air.. | Hong Kong Air.. | Hong Kong Air.. | Japan Airlines  | No info found   | 
                | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       | No info found   |       
--------------- | ------------------------------------------------------------------------------------------------------------------------------      
2020-03-16      | USD 249.0       | USD 240.0       | USD 225.0       | USD 260.0       | USD 231.0       | USD 253.0       | USD 291.0       |       
                | Jetstar, HK E.. | Jetstar         | Jetstar         | Hong Kong Air.. | HK Express      | Hong Kong Air.. | Jetstar, HK E.. |       
                | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       |       
--------------- | ------------------------------------------------------------------------------------------------------------------------------      
2020-03-17      | USD 174.0       | USD 260.0       | USD 231.0       | USD 216.0       | USD 253.0       | USD 205.0       | USD 170.0       |       
                | Jetstar         | Hong Kong Air.. | Jetstar         | Jetstar         | Hong Kong Air.. | HK Express      | HK Express      |       
                | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       |       
--------------- | ------------------------------------------------------------------------------------------------------------------------------      
2020-03-18      | USD 167.0       | USD 329.0       | USD 253.0       | USD 166.0       | USD 253.0       | USD 253.0       | USD 266.0       |       
                | HK Express      | HK Express      | Hong Kong Air.. | Jetstar         | Hong Kong Air.. | Hong Kong Air.. | Jetstar, HK E.. |       
                | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       | Direct? Y       |       
--------------- | ------------------------------------------------------------------------------------------------------------------------------  

Video sample:

Conclusion

During this exercise, there is nothing new in programming, we keep using Python and JSON for the API request and response tasks. But the thing we should consider, is the way we design a program. We should think about how to maximum the usage and help clients to solve their problems.

One more note, we are not saying Skyscanner can provide the cheapest flights for us. We use their API just because this is easier for us to implement. Personally, I would get the price info from the API, then buy tickets directly from airlines for better deals. :]]

What have we learnt in this post?

  1. Usage of RapidAPI on Skyscanner API
  2. The way to design an application
  3. Usage of getting inputs from CLI

(the complete source can be found at GitHubhttps://github.com/codeastar/easy_cheap_flights_checker  )

1 thought on “Cheap Flights Checker – Flight for your dream”

  1. Pingback: Cheap Flights Checker Extra - the Airport Seeker ⋆ Code A Star

Comments are closed.