TinyDB<\/a> 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:<\/p>\n\n\n\nimport requests\nimport os, sys, json\nfrom datetime import date, timedelta, datetime\nfrom tinydb import TinyDB, Query\n\ndb = TinyDB('skyscanner.json')\nProfile = db.table('Profile')\nPlace = db.table('Place')\nENDPOINT_PREFIX = \"https:\/\/skyscanner-skyscanner-flight-search-v1.p.rapidapi.com\/apiservices\/\"\nTAG = \"Return \\\\ Depart\"\nEND_TAG = \" | \"\nCELL_LENGTH = int(len(TAG))\n<\/pre>\n\n\n\nWe 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.<\/p>\n\n\n\n
def initProfileDB():\n if \"SKYSCAN_RAPID_API_KEY\" in os.environ:\n API_KEY = os.environ['SKYSCAN_RAPID_API_KEY'] \n Profile.upsert({'api_key':API_KEY}, Query().api_key.exists())\n else: \n API_KEY = Profile.search(Query().api_key)\n if API_KEY == []: \n sys.exit(\"No API key found\")\n API_KEY = API_KEY[0]['api_key']\n\n init_market = \"US\" if Profile.search(Query().market)==[] else Profile.search(Query().market)[0]['market']\n init_from = \"SFO\" if Profile.search(Query().place_from)==[] else Profile.search(Query().place_from)[0]['place_from']\n init_to = \"JFK\" if Profile.search(Query().place_to)==[] else Profile.search(Query().place_to)[0]['place_to']\n init_connect = \"Y\" if Profile.search(Query().connect)==[] else Profile.search(Query().connect)[0]['connect']\n init_currency = \"USD\" if Profile.search(Query().currency)==[] else Profile.search(Query().currency)[0]['currency']\n 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']\n 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']\n\n profile_dict = {\n \"API_KEY\": API_KEY,\n \"init_market\": init_market,\n \"init_from\": init_from, \n \"init_to\": init_to,\n \"init_connect\": init_connect,\n \"init_currency\": init_currency,\n \"init_depart\": init_depart,\n \"init_return\": init_return,\n }\n return profile_dict\n<\/pre>\n\n\n\nAfter that, we build a CLI to let user enter searching criteria.<\/p>\n\n\n\n
profile_dict = initProfileDB() #init our profile\nheaders = {\n 'x-rapidapi-host': \"skyscanner-skyscanner-flight-search-v1.p.rapidapi.com\",\n 'x-rapidapi-key': profile_dict[\"API_KEY\"]\n }\n\nmarket= input(f\"Market Country(ISO two-letter country code) [{profile_dict['init_market']}]: \") or profile_dict['init_market']\nplace_from = input(f\"From(place name or IATA code) [{profile_dict['init_from']}]: \") or profile_dict['init_from']\nplace_to = input(f\"To(place name or IATA code) [{profile_dict['init_to']}]: \") or profile_dict['init_to'] \nconnect = input(f\"Consider Connecting Flight(Y\/N) [{profile_dict['init_connect']}]: \") or profile_dict['init_connect']\ncurrency = input(f\"Currency [{profile_dict['init_currency']}]: \") or profile_dict['init_currency']\ndate_depart = input(f\"Depart (YYYY-MM-DD) [{profile_dict['init_depart']}]: \") or profile_dict['init_depart']\ndate_return = input(f\"Return (YYYY-MM-DD) [{profile_dict['init_return']}]: \") or profile_dict['init_return']\n<\/pre>\n\n\n\nWe can try to run our cheap flights seeker program. But before doing this, we have to enter the RapidAPI key for the first run. <\/p>\n\n\n\n
Run following line on your console:\n>export SKYSCAN_RAPID_API_KEY=abcde (for Linux\/MacOS)\nor\n>$Env:SKYSCAN_RAPID_API_KEY=\"abcde\" (for Win10, double quotes are needed)<\/code><\/pre>\n\n\n\nWhen we start our program, we should see following input interface popping out from our console:<\/p>\n\n\n\n
Market Country(ISO two-letter country code) [US]:\nFrom(place name or IATA code) [SFO]:\nTo(place name or IATA code) [JFK]:\nConsider Connecting Flight(Y\/N) [Y]:\nCurrency [USD]:\nDepart (YYYY-MM-DD) [2020-01-17]:\nReturn (YYYY-MM-DD) [2020-01-21]:<\/code><\/pre>\n\n\n\nYou 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.<\/p>\n\n\n\n
Where all the cheap flights magic happens<\/h3>\n\n\n\n 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.<\/p>\n\n\n\n
def handleAPIException(responseText, apiname):\n print(json.dumps(json.loads(responseText), indent=3, sort_keys=True))\n sys.exit(f\"API exception on [{apiname}]\")\n\ndef getIataCodeByString(place_string, market, currency, headers):\n place_info = Place.search(Query()['search_string'] == place_string) #search from DB first\n if place_info == []:\n print(\"Searching IATA code from Skyscanner...\") #search from Skyscanner API\n url = ENDPOINT_PREFIX+f\"autosuggest\/v1.0\/{market}\/{currency}\/en-US\/\"\n querystring = {\"query\":place_string}\n response = requests.request(\"GET\", url, headers=headers, params=querystring)\n if response.status_code != 200: handleAPIException(response.text, getIataCodeByString.__name__)\n place_json = json.loads(response.text)\n for place in place_json[\"Places\"]:\n if len(place['PlaceId']) == 7:\n Place.upsert({'search_string':place_string, 'iata':place['PlaceId'][:3], 'name':place['PlaceName'], 'country':place['CountryName']}, Query().api_key.exists())\n iata_code = place['PlaceId'][:3]\n break\n else: \n iata_code = place_info[0][\"iata\"] #get the code from DB\n return iata_code\n\nplace_from = getIataCodeByString(place_from, market, currency, headers) if (len(place_from) > 3) else place_from \nplace_to = getIataCodeByString(place_to, market, currency, headers) if (len(place_to) > 3) else place_to \n<\/pre>\n\n\n\nWe have currency, market, places and dates as our searching criteria, let’s do our magic here by sending cheap flights request to RapidAPI-Skyscanner:<\/p>\n\n\n\n
def getCheapQuote(market, currency, place_from, place_to, date_depart, date_return, check_place): \n url = ENDPOINT_PREFIX+f\"browsequotes\/v1.0\/{market}\/{currency}\/en-US\/{place_from}\/{place_to}\/{date_depart}\/{date_return}\"\n response = requests.request(\"GET\", url, headers=headers)\n if response.status_code != 200: handleAPIException(response.text, \"browse quotes\")\n quotes_json = json.loads(response.text)\n min_price_low = None\n carrier_names = []\n is_direct = \"N\/A\"\n for quote in quotes_json[\"Quotes\"]:\n direct_flight = quote['Direct']\n if (connect==False and direct_flight==False): continue\n min_price_this = quote['MinPrice'] \n if (min_price_low == None or min_price_this < min_price_low): \n min_price_low = min_price_this\n is_direct = direct_flight\n carrier_id_outbound = quote['OutboundLeg']['CarrierIds']\n carrier_id_inbound = quote['InboundLeg']['CarrierIds']\n carrier_ids = set(carrier_id_outbound + carrier_id_inbound)\n\n if min_price_low != None: \n for carrier in quotes_json[\"Carriers\"]:\n carrier_id = carrier['CarrierId']\n if carrier_id in carrier_ids:\n carrier_name = carrier['Name']\n carrier_names.append(carrier_name)\n if len(carrier_names) == len(carrier_ids): break\n if (check_place): \n for place in quotes_json[\"Places\"]: \n iata_code = place['IataCode']\n if (iata_code == place_from): \n place_from = f\"{place_from} - {place['Name']}, {place['CityName']}, {place['CountryName']}\"\n elif (iata_code == place_to): \n place_to = f\"{place_to} - {place['Name']}, {place['CityName']}, {place['CountryName']}\"\n\n cheapquote_dict = {\n \"price\": min_price_low,\n \"carriers\": carrier_names,\n \"is_direct\": is_direct,\n \"place_from\": place_from, \n \"place_to\": place_to\n } \n return cheapquote_dict\n\nselected_cheapquote_dict = getCheapQuote(market, currency, place_from, place_to, date_depart, date_return, True)\n<\/pre>\n\n\n\nDing! 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. <\/p>\n\n\n\n
First, we define a wider date range from our desired travel date, i.e. +3 \/ -3 days of our depart and return dates:<\/p>\n\n\n\n
dates_depart = []\ndates_return = []\nselected_date_depart = date_depart\nselected_date_return = date_return\ndates_depart.append(date_depart)\ndates_return.append(date_return)\n\nfor change_day_minus in range(3): #do last 3 days search\n date_depart_d_obj = datetime.strptime(date_depart, '%Y-%m-%d').date()\n if (date_depart_d_obj > date.today()):\n date_depart = (date_depart_d_obj - timedelta(days=1)).strftime('%Y-%m-%d')\n date_return = (datetime.strptime(date_return, '%Y-%m-%d').date() - timedelta(days=1)).strftime('%Y-%m-%d')\n dates_depart.insert(0,date_depart)\n dates_return.insert(0,date_return)\n else: \n break\n\ndate_depart = selected_date_depart\ndate_return = selected_date_return\nfor change_day_plus in range(3): #do next 3 days search\n date_depart = (datetime.strptime(date_depart, '%Y-%m-%d').date() + timedelta(days=1)).strftime('%Y-%m-%d')\n date_return = (datetime.strptime(date_return, '%Y-%m-%d').date() + timedelta(days=1)).strftime('%Y-%m-%d')\n dates_depart.append(date_depart)\n dates_return.append(date_return)\n<\/pre>\n\n\n\nSecond, we call the cheap flights API again using our wider date range and build the price grid:<\/p>\n\n\n\n
dates_return.insert(0,TAG)\nrow_length = 0\nfor row_index, row_cell in enumerate(dates_return):\n row_cell = row_cell+\"*\" if (row_cell == selected_date_return) else row_cell\n print(f\"\\n{row_cell}{' '*(len(TAG)-len(row_cell))}\", end =END_TAG)\n if row_index==0:\n for col_cell in dates_depart:\n col_cell = col_cell+\"*\" if (col_cell == selected_date_depart) else col_cell\n print(f\"{col_cell}{' '*(CELL_LENGTH-len(col_cell))}\", end=END_TAG)\n row_length += CELL_LENGTH+len(END_TAG)\n print(f\"\\n{'-'*(len(TAG))}{END_TAG}{'-'*row_length}\", end =\"\")\n else: \n cheapquotes = []\n for col_cell in dates_depart:\n if (row_cell[:10]==selected_date_return and col_cell==selected_date_depart):\n cheapquote_dict = selected_cheapquote_dict\n elif (datetime.strptime(col_cell, '%Y-%m-%d').date() < datetime.strptime(row_cell[:10], '%Y-%m-%d').date()):\n cheapquote_dict = getCheapQuote(market, currency, place_from, place_to, col_cell, row_cell[:10], False)\n else: \n cheapquote_dict = { \"price\": None }\n cheapquotes.append(cheapquote_dict)\n displayPrice(cheapquote_dict, 0)\n \n for i in range(1,3):\n print(f\"\\n{' '*(len(TAG))}\", end =END_TAG)\n for cheapquote in cheapquotes:\n displayPrice(cheapquote, i) \n print(f\"\\n{'-'*(len(TAG))}{END_TAG}{'-'*row_length}\", end =\"\")\n<\/pre>\n\n\n\nWe're all done! I love cheap flights!<\/p>\n\n\n\n
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:<\/p>\n\n\n\n
>python .\\skyscanner.py\nMarket Country(ISO two-letter country code) [US]: HK\nFrom(place name or IATA code) [SFO]: Hong Kong\nTo(place name or IATA code) [JFK]: Tokyo\nConsider Connecting Flight(Y\/N) [N]: n\nCurrency [USD]: \nDepart (YYYY-MM-DD) [2020-03-12]: \nReturn (YYYY-MM-DD) [2020-03-15]: <\/code><\/pre>\n\n\n\nThe program should send above information to Skyscanner and generate a price grid like this:<\/p>\n\n\n\n
Start processing your request...\n\nFrom: HKG - Hong Kong Intl, Hong Kong, Hong Kong\nTo: NRT - Tokyo Narita, Tokyo, Japan\nDepart: 2020-03-12\nReturn: 2020-03-15\nConsider Connecting Fligh? N\n\nReturn \\ Depart | 2020-03-09 | 2020-03-10 | 2020-03-11 | 2020-03-12* | 2020-03-13 | 2020-03-14 | 2020-03-15 | \n--------------- | ------------------------------------------------------------------------------------------------------------------------------ \n2020-03-12 | USD 253.0 | USD 166.0 | USD 267.0 | No info found | No info found | No info found | No info found | \n | Hong Kong Air.. | Jetstar | Jetstar | No info found | No info found | No info found | No info found | \n | Direct? Y | Direct? Y | Direct? Y | No info found | No info found | No info found | No info found | \n--------------- | ------------------------------------------------------------------------------------------------------------------------------ \n2020-03-13 | USD 282.0 | USD 282.0 | USD 166.0 | USD 166.0 | No info found | No info found | No info found | \n | Hong Kong Air.. | Hong Kong Air.. | Jetstar | Jetstar | No info found | No info found | No info found | \n | Direct? Y | Direct? Y | Direct? Y | Direct? Y | No info found | No info found | No info found | \n--------------- | ------------------------------------------------------------------------------------------------------------------------------ \n2020-03-14 | USD 253.0 | USD 260.0 | USD 253.0 | USD 253.0 | USD 344.0 | No info found | No info found | \n | Hong Kong Air.. | Hong Kong Air.. | Hong Kong Air.. | Hong Kong Air.. | HK Express | No info found | No info found | \n | Direct? Y | Direct? Y | Direct? Y | Direct? Y | Direct? Y | No info found | No info found | \n--------------- | ------------------------------------------------------------------------------------------------------------------------------ \n2020-03-15* | USD 260.0 | USD 260.0 | USD 253.0 | USD 260.0 | USD 253.0 | USD 462.0 | No info found | \n | Hong Kong Air.. | Hong Kong Air.. | Hong Kong Air.. | Hong Kong Air.. | Hong Kong Air.. | Japan Airlines | No info found | \n | Direct? Y | Direct? Y | Direct? Y | Direct? Y | Direct? Y | Direct? Y | No info found | \n--------------- | ------------------------------------------------------------------------------------------------------------------------------ \n2020-03-16 | USD 249.0 | USD 240.0 | USD 225.0 | USD 260.0 | USD 231.0 | USD 253.0 | USD 291.0 | \n | Jetstar, HK E.. | Jetstar | Jetstar | Hong Kong Air.. | HK Express | Hong Kong Air.. | Jetstar, HK E.. | \n | Direct? Y | Direct? Y | Direct? Y | Direct? Y | Direct? Y | Direct? Y | Direct? Y | \n--------------- | ------------------------------------------------------------------------------------------------------------------------------ \n2020-03-17 | USD 174.0 | USD 260.0 | USD 231.0 | USD 216.0 | USD 253.0 | USD 205.0 | USD 170.0 | \n | Jetstar | Hong Kong Air.. | Jetstar | Jetstar | Hong Kong Air.. | HK Express | HK Express | \n | Direct? Y | Direct? Y | Direct? Y | Direct? Y | Direct? Y | Direct? Y | Direct? Y | \n--------------- | ------------------------------------------------------------------------------------------------------------------------------ \n2020-03-18 | USD 167.0 | USD 329.0 | USD 253.0 | USD 166.0 | USD 253.0 | USD 253.0 | USD 266.0 | \n | HK Express | HK Express | Hong Kong Air.. | Jetstar | Hong Kong Air.. | Hong Kong Air.. | Jetstar, HK E.. | \n | Direct? Y | Direct? Y | Direct? Y | Direct? Y | Direct? Y | Direct? Y | Direct? Y | \n--------------- | ------------------------------------------------------------------------------------------------------------------------------ <\/code><\/pre>\n\n\n\nVideo sample: <\/p>\n\n\n\n