diff --git a/.gitignore b/.gitignore index ea1d262..597369e 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ venv2.7 .pypirc .DS_Store +.env diff --git a/.travis.yml b/.travis.yml index 1c0c05d..f10009a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,12 @@ +sudo: false +dist: xenial language: python python: - - "3.3" + - "3.7" - "2.7" - - "2.6" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors -install: pip install -r requirements.txt +install: + - "pip install pipenv" + - "pipenv install" # command to run tests, e.g. python setup.py test script: nosetests --with-doctest diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..782f6d0 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSE README +recursive-include airbnb/files * \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..c23a0c9 --- /dev/null +++ b/Pipfile @@ -0,0 +1,14 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +requests = "*" +python-dateutil = "*" + +[dev-packages] +nose = "*" + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..3812c3a --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,82 @@ +{ + "_meta": { + "hash": { + "sha256": "d15561b3ace180be8ea111e850d9578a367cd5f9c5bfb52bf8b7dc018500a25b" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", + "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" + ], + "version": "==2019.6.16" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "python-dateutil": { + "hashes": [ + "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", + "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + ], + "index": "pypi", + "version": "==2.8.0" + }, + "requests": { + "hashes": [ + "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", + "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + ], + "index": "pypi", + "version": "==2.22.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "urllib3": { + "hashes": [ + "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", + "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" + ], + "version": "==1.25.3" + } + }, + "develop": { + "nose": { + "hashes": [ + "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac", + "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a", + "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98" + ], + "index": "pypi", + "version": "==1.3.7" + } + } +} diff --git a/README b/README new file mode 120000 index 0000000..42061c0 --- /dev/null +++ b/README @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..00db872 --- /dev/null +++ b/README.md @@ -0,0 +1,162 @@ +airbnb-python +=================== + +Airbnb Python API + +Disclaimer +---------- + +This is a unofficial python API wrapper for airbnb.com + +Using this software might contradict airbnb.com terms of service + +Requirements +------------ + +* requests + +Usage: +------ + + pip install airbnb + +### Initialize API + +```python +import airbnb +api = airbnb.Api() +``` + +Now, you'd be able to access most of the API endpoints (which don't require authentication). + +### Use random client identification + +```python +import airbnb +api = airbnb.Api(randomize=True) +``` + +This will allow you to randomize device UDID, advertisement UUID and user agent. + +### Initialize API with your airbnb.com username and password: + +You need to login to access certain endpoints requiring authentication: + +```python +api = airbnb.Api(login, password) +``` + +### Once you logged in, please reuse your access token, to avoid getting your account locked + +```python +api = airbnb.Api(access_token=``) +``` + +### Get your user profile +#### (requires auth) + +```python +api.get_profile() +``` + +### Get listing availability + +```python +api.get_calendar(listing_id) +``` + +Optional parameters: + +- `starting_month`: first month of the calendar (can't be in the past) +- `starting_year`: first year of the calendar (can't be in the past) +- `calendar_months`: how many months ahead you want to get the calendar for + +Example: + +```python +api.get_calendar(975964, starting_month=9, starting_year=2017, calendar_months=1) +``` + +### Get listing reviews + +```python +api.get_reviews(listing_id) +``` + +Optional parameters: + +- `offset`: paging offset +- `limit`: number of results per page + +Example: + +```python +api.get_reviews(975964, offset=20, limit=20) +``` + +### Get a list of available homes in a `` with a query or at a given location + +```python +api.get_homes(``) +api.get_homes(gps_lat=`Latitude`, gps_lng=`Longitude`) +``` + +Optional parameters: + +- `offset`: paging offset +- `items_per_grid`: amount of listings to fetch for a given offset +- `checkin`: checkin date (e.g. `2019-04-20`) +- `checkout`: checkout date (e.g. `2019-04-20`) + +Example: + +Get first 8 listings for Lisbon, Portugal + +```python +api.get_homes("Lisbon, Portugal") +``` + +Example: + +Get first listings at a given location (by GPS coordinates) + +```python +api.get_homes(gps_lat=55.6123352, gps_lng=37.7117917) +``` + +Note: at the moment `items_per_grid` limit appears to be *306* listings + + +### 🌿 VerificationError (420) + +This exception occurs when you send too many login requests (i.e. call `Api()` with your credentials). +Once you are logged in with your credentials you can just use your access token (`Api(access_token="")`) +As a workaround, try to login manually though the website or mobile app and complete Airbnb's verification process. +You can also try to use a VPN or a proxy. + + +### Testing + +The package has some doctests to test authentication process, to run the tests first export the following env variables: + +```bash +export AIRBNB_LOGIN=`` +export AIRBNB_PASSWORD=`` +export AIRBNB_ACCESS_TOKEN=`` +``` + +Then, setup Python environment as follows: + +```bash +pipenv shell +pipenv install --dev +``` + +Finally, run the doctests using nose: + +```bash +nosetests --with-doctest + +``` + +[![Build Status](https://travis-ci.org/nderkach/airbnb-python.svg)](https://travis-ci.org/nderkach/airbnb-python) diff --git a/README.rst b/README.rst deleted file mode 100644 index cd37552..0000000 --- a/README.rst +++ /dev/null @@ -1,33 +0,0 @@ -airbnb-python -=================== - -Airbnb Python API - -Disclaimer ----------- - -This is a unofficial python API wrapper for airbnb.com - -Using this software might contradict airbnb.com terms of service - -Requirements ------------- - -* requests - -Usage: ------- - - pip install airbnb-python - -* Initialize API with your airbnb.com username and password:: - - from airbnb import Api - api = Api(login, password) - -* Get you user profile:: - - api.get_profile() - -.. image:: https://travis-ci.org/nderkach/airbnb-python.png - :target: https://travis-ci.org/nderkach/airbnb-python diff --git a/airbnb/api.py b/airbnb/api.py index 0c302d9..3ea0daa 100644 --- a/airbnb/api.py +++ b/airbnb/api.py @@ -1,9 +1,13 @@ import requests import json +import datetime +from dateutil.tz import tzlocal +from airbnb.random_request import RandomRequest +import os +import functools -API_URL = "https://api.airbnb.com" -# API_KEY might be tied to a particular app installation, use with caution +API_URL = "https://api.airbnb.com/v2" API_KEY = "915pw2pnf4h1aiguhph5gc5b2" @@ -14,70 +18,374 @@ class AuthError(Exception): pass +class VerificationError(AuthError): + """ + Authentication error + """ + pass + + +class MissingParameterError(Exception): + """ + Missing parameter error + """ + pass + + +class MissingAccessTokenError(MissingParameterError): + """ + Missing access token error + """ + pass + + +def require_auth(function): + """ + A decorator that wraps the passed in function and raises exception + if access token is missing + """ + @functools.wraps(function) + def wrapper(self, *args, **kwargs): + if not self.access_token(): + raise MissingAccessTokenError + return function(self, *args, **kwargs) + return wrapper + + +def randomizable(function): + """ + A decorator which randomizes requests if needed + """ + @functools.wraps(function) + def wrapper(self, *args, **kwargs): + if self.randomize: + self.randomize_headers() + return function(self, *args, **kwargs) + return wrapper + + class Api(object): """ Base API class - >>> api = Api("vtflotci@sharklasers.com", "qwerty") - >>> api.uid - 25418725 + >>> api = Api(access_token=os.environ.get("AIRBNB_ACCESS_TOKEN")) >>> api.get_profile() # doctest: +ELLIPSIS {...} - >>> api = Api("foo", "bar") # doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - AuthError - >>> api = Api(uid=25418725, access_token="c9pfdtaakmqh8vrwcyviaai0w") + >>> api = Api() + >>> api.get_homes("Lisbon, Portugal") # doctest: +ELLIPSIS + {...} + >>> api.get_homes(gps_lat=55.6123352, gps_lng=37.7117917) # doctest: +ELLIPSIS + {...} + >>> api.get_homes("Lisbon, Portugal", checkin=datetime.datetime.now().strftime("%Y-%m-%d"), checkout=(datetime.datetime.now() + datetime.timedelta(days=30)).strftime("%Y-%m-%d")) # doctest: +ELLIPSIS + {...} + >>> api.get_calendar(975964) # doctest: +ELLIPSIS + {...} + >>> api.get_reviews(975964) # doctest: +ELLIPSIS + {...} + >>> api = Api(randomize=True) + >>> api.get_listing_details(975964) # doctest: +ELLIPSIS + {...} """ - def __init__(self, username=None, password=None, - uid=None, access_token=None, api_key=API_KEY): + def __init__(self, username=None, password=None, access_token=None, api_key=API_KEY, session_cookie=None, + proxy=None, randomize=None): self._session = requests.Session() + self._access_token = None + self.user_agent = "Airbnb/19.18 AppVersion/19.18 iPhone/12.2 Type/Phone" + self.udid = "9120210f8fb1ae837affff54a0a2f64da821d227" + self.uuid = "C326397B-3A38-474B-973B-F022E6E4E6CC" + self.randomize = randomize self._session.headers = { - "Accept": "application/json", - "Accept-Encoding": "gzip, deflate", - "Content-Type": "application/json", - "X-Airbnb-API-Key": api_key, - "User-Agent": "Airbnb/4.7.0 iPhone/8.1.2" + "accept": "application/json", + "accept-encoding": "br, gzip, deflate", + "content-type": "application/json", + "x-airbnb-api-key": api_key, + "user-agent": self.user_agent, + "x-airbnb-screensize": "w=375.00;h=812.00", + "x-airbnb-carrier-name": "T-Mobile", + "x-airbnb-network-type": "wifi", + "x-airbnb-currency": "USD", + "x-airbnb-locale": "en", + "x-airbnb-carrier-country": "us", + "accept-language": "en-us", + "airbnb-device-id": self.udid, + "x-airbnb-advertising-id": self.uuid } - if uid and access_token: - self.uid = uid + if proxy: + self._session.proxies = { + "http": proxy, + "https": proxy + } + + if access_token: self._access_token = access_token + if session_cookie and "_airbed_session_id=" in session_cookie: + self._session.headers.update({ + "Cookie": session_cookie + }) + self._session.headers.update({ - "X-Airbnb-OAuth-Token": self._access_token + "x-airbnb-oauth-token": self._access_token }) - else: - assert(username and password) - login_payload = {"username": username, + elif username and password: + login_payload = {"email": username, "password": password, - "prevent_account_creation": "true"} + "type": "email"} r = self._session.post( - API_URL + "/v1/authorize", data=json.dumps(login_payload) + API_URL + "/logins", data=json.dumps(login_payload) ) - if "access_token" not in r.json(): + if r.status_code == 420: + raise VerificationError + elif r.status_code == 403: raise AuthError - r.raise_for_status() - self._access_token = r.json()["access_token"] + self._access_token = r.json()["login"]["id"] + + print("Your access token: {}".format(self._access_token)) self._session.headers.update({ - "X-Airbnb-OAuth-Token": self._access_token + "x-airbnb-oauth-token": self._access_token }) + else: + # no auth + pass + + def access_token(self): + return self._access_token + + def set_user_agent(self, user_agent): + self.user_agent = user_agent + self._session.headers['user-agent'] = user_agent - r = self._session.get(API_URL + "/v1/account/active") + def set_udid(self, udid): + self.udid = udid + self._session.headers['airbnb-device-id'] = udid - r.raise_for_status() + def set_uuid(self, uuid): + self.uuid = uuid + self._session.headers['x-airbnb-advertising-id'] = uuid - self.uid = r.json()["user"]["user"]["id"] + def randomize_headers(self): + self.set_user_agent(RandomRequest.get_random_user_agent()) + self.set_udid(RandomRequest.get_random_udid()) + self.set_uuid(RandomRequest.get_random_uuid()) + @require_auth def get_profile(self): - assert(self._access_token and self.uid) + """ + Get my own profile + """ + r = self._session.get(API_URL + "/logins/me") + r.raise_for_status() + + return r.json() + + @randomizable + def get_calendar(self, listing_id, starting_month=datetime.datetime.now().month, starting_year=datetime.datetime.now().year, calendar_months=12): + """ + Get availability calendar for a given listing + """ + params = { + 'year': str(starting_year), + 'listing_id': str(listing_id), + '_format': 'with_conditions', + 'count': str(calendar_months), + 'month': str(starting_month) + } + + r = self._session.get(API_URL + "/calendar_months", params=params) + r.raise_for_status() + + return r.json() + + @randomizable + def get_reviews(self, listing_id, offset=0, limit=20): + """ + Get reviews for a given listing + """ + params = { + '_order': 'language_country', + 'listing_id': str(listing_id), + '_offset': str(offset), + 'role': 'all', + '_limit': str(limit), + '_format': 'for_mobile_client', + } + + print(self._session.headers) + + r = self._session.get(API_URL + "/reviews", params=params) + r.raise_for_status() + + return r.json() + + + # Host APIs + + @require_auth + def get_listing_calendar(self, listing_id, starting_date=datetime.datetime.now(), calendar_months=6): + """ + Get host availability calendar for a given listing + """ + params = { + '_format': 'host_calendar_detailed' + } + + starting_date_str = starting_date.strftime("%Y-%m-%d") + end_days = calendar_months * 30 + ending_date_str = ( + starting_date + datetime.timedelta(days=end_days)).strftime("%Y-%m-%d") + + r = self._session.get(API_URL + "/calendars/{}/{}/{}".format( + str(listing_id), starting_date_str, ending_date_str), params=params) + r.raise_for_status() + + return r.json() + + # User past trips and stats + + @require_auth + def get_trip_schedules(self): + params = { + '_format': 'for_unbundled_itinerary', + '_limit': '10', + '_offset': '0', + 'client_version': '3', + 'exclude_free_time': 'false' + } + + r = self._session.get(API_URL + '/trip_schedules', params=params) + + r.raise_for_status() + return r.json()["trip_schedules"] + + @require_auth + def get_travel_plans(self, upcoming_scheduled_plans_limit=20, past_scheduled_plans_limit=8): + now = datetime.datetime.now(tzlocal()) + strftime_date = now.strftime('%Y-%m-%dT%H:%M:%S%z') + + params = { + 'now': '{}:{}'.format(strftime_date[:-2], strftime_date[-2:]), + 'upcoming_scheduled_plans_limit': upcoming_scheduled_plans_limit, + 'past_scheduled_plans_limit': past_scheduled_plans_limit + } + + r = self._session.get(API_URL + "/plans", params=params) + r.raise_for_status() + + return r.json()['plans'][0] + + @require_auth + def get_scheduled_plan(self, identifier): + assert(self._access_token) + + params = { + '_format': 'for_trip_day_view' + } + + r = self._session.get(API_URL + "/scheduled_plans/{}".format(identifier), params=params) + r.raise_for_status() + + return r.json()['scheduled_plan'] + + @require_auth + def get_reservation(self, reservation_id): + assert(self._access_token) + + params = { + '_format': 'for_trip_planner' + } + + r = self._session.get(API_URL + "/reservations/{}".format(reservation_id), params=params) + r.raise_for_status() + + return r.json()['reservation'] + + @require_auth + def get_all_past_reservations(self): + past_scheduled_plan_ids = self.get_travel_plans()['past_scheduled_plans']['metadata']['cache']['identifiers'] + + past_reservations = [] + for plan_id in past_scheduled_plan_ids: + scheduled_plan = self.get_scheduled_plan(plan_id) + reservation_id = scheduled_plan['events'][0]['destination']['reservation_key'] + past_reservations.append(self.get_reservation(reservation_id)) + + return past_reservations + + @require_auth + def get_total_money_spent_in_usd(self): + reservations = self.get_all_past_reservations() + + total_spent = 0.0 + for reservation in reservations: + if reservation['total_price_formatted'].startswith('$'): + dollars_spent = reservation['total_price_formatted'] + total_spent += float(dollars_spent[1:]) + + return total_spent + + # Listing search + + @randomizable + def get_homes(self, query=None, gps_lat=None, gps_lng=None, checkin=None, checkout=None, offset=0, items_per_grid=8): + """ + Search listings with + * Query (e.g. query="Lisbon, Portugal") or + * Location (e.g. gps_lat=55.6123352&gps_lng=37.7117917) + * Check in/check out filters (e.g. checkin=2019-05-15&checkout=2019-05-20) + """ + params = { + 'toddlers': '0', + 'adults': '0', + 'infants': '0', + 'is_guided_search': 'true', + 'version': '1.4.8', + 'section_offset': '0', + 'items_offset': str(offset), + 'screen_size': 'small', + 'source': 'explore_tabs', + 'items_per_grid': str(items_per_grid), + '_format': 'for_explore_search_native', + 'metadata_only': 'false', + 'refinement_paths[]': '/homes', + 'timezone': 'Europe/Lisbon', + 'satori_version': '1.1.0' + } + + if not query and not (gps_lat and gps_lng): + raise MissingParameterError("Missing query or gps coordinates") + + if query: + params['query'] = query + + if gps_lat and gps_lng: + params['lat'] = gps_lat + params['lng'] = gps_lng + + if checkin and checkout: + params['checkin'] = checkin + params['checkout'] = checkout + + r = self._session.get(API_URL + '/explore_tabs', params=params) + r.raise_for_status() + + return r.json() + + @randomizable + def get_listing_details(self, listing_id): + params = { + 'adults': '0', + '_format': 'for_native', + 'infants': '0', + 'children': '0' + } - r = self._session.get(API_URL + "/v1/users/" + str(self.uid)) + r = self._session.get(API_URL + '/pdp_listing_details/' + str(listing_id), params=params) r.raise_for_status() return r.json() diff --git a/airbnb/files/airbnb_versions.txt b/airbnb/files/airbnb_versions.txt new file mode 100644 index 0000000..c8f6fcf --- /dev/null +++ b/airbnb/files/airbnb_versions.txt @@ -0,0 +1,83 @@ +17.38 +17.37 +17.36 +17.35 +17.34 +17.33 +17.32.1 +17.31 +17.30 +17.29 +17.28 +17.26 +17.25 +17.24 +17.23 +17.22 +17.21 +17.20 +17.19 +17.18 +17.17 +17.16 +17.15 +17.14 +17.13 +17.12 +17.11 +17.10.1 +17.09.1 +17.09 +17.08 +17.07 +17.06 +17.05 +17.03 +17.02 +16.49.2 +16.49 +16.47 +16.46.2 +16.46.1 +16.46 +16.45 +16.44 +16.43 +16.42.1 +16.42 +16.41 +16.40 +16.39 +16.38 +16.37.1 +16.37 +16.35 +16.34 +16.33.1 +16.33 +16.32 +16.31 +16.30 +16.29 +16.28 +16.26 +16.25 +16.24 +16.22 +16.20.2 +16.20 +16.19.1 +16.19 +16.17 +16.16.1 +16.16 +16.12 +16.11 +16.10 +16.08 +16.07 +16.06 +16.05 +16.04 +16.03 +16.02 \ No newline at end of file diff --git a/airbnb/files/supported_ios_versions.txt b/airbnb/files/supported_ios_versions.txt new file mode 100644 index 0000000..cd35e5c --- /dev/null +++ b/airbnb/files/supported_ios_versions.txt @@ -0,0 +1,25 @@ +9.0 +9.0.1 +9.0.2 +9.1 +9.2 +9.2.1 +9.3 +9.3.1 +9.3.2 +9.3.3 +9.3.4 +9.3.5 +10.0 +10.0.1 +10.0.2 +10.0.3 +10.1 +10.1.1 +10.2 +10.2.1 +10.3 +10.3.1 +10.3.2 +10.3.3 +11.0 \ No newline at end of file diff --git a/airbnb/random_request.py b/airbnb/random_request.py new file mode 100644 index 0000000..8eb6903 --- /dev/null +++ b/airbnb/random_request.py @@ -0,0 +1,24 @@ +import random +import uuid +import os + + +class RandomRequest(object): + + @classmethod + def get_random_user_agent(cls): + with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'files/supported_ios_versions.txt')) as f: + ios_versions = f.read().splitlines() + with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'files/airbnb_versions.txt')) as f: + airbnb_versions = f.read().splitlines() + + return "Airbnb/{} iPhone/{} Type/Phone".format(random.choice(airbnb_versions), random.choice(ios_versions)) + + @classmethod + def get_random_udid(cls): + hex_digits = "0123456789abcdef" + return ''.join(random.choice(hex_digits) for _ in range(40)) + + @classmethod + def get_random_uuid(cls): + return str(uuid.uuid4()).upper() diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index f229360..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -requests diff --git a/setup.py b/setup.py index 83f7099..20f4f7d 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from distutils.core import setup PROJECT = 'airbnb' -VERSION = '1.0.0' +VERSION = '2.3.3' URL = 'https://github.com/nderkach/airbnb-python' AUTHOR = 'Nikolay Derkach' AUTHOR_EMAIL = 'nderk@me.com' @@ -27,7 +27,7 @@ def read_file(file_name): url=URL, license=read_file('LICENSE'), packages=['airbnb'], - test_suite="tests", + package_data = {'airbnb': ["files/*"]}, classifiers=[ "Programming Language :: Python", "Topic :: Software Development :: Libraries :: Python Modules", @@ -42,4 +42,8 @@ def read_file(file_name): "Topic :: Internet :: WWW/HTTP", "Environment :: Console" ], + install_requires=[ + 'requests', + 'python-dateutil' + ] )