├── requirements.txt ├── .gitignore ├── LICENSE ├── setup.py ├── livepopulartimes ├── __init__.py └── crawler.py ├── README.md ├── example_output(get_places_by_search).txt ├── example_output(get_populartimes_by_PlaceID).json └── example_output(get_populartimes_by_address).json /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | # pyenv 62 | .python-version 63 | 64 | # Environments 65 | .env 66 | .venv 67 | env/ 68 | venv/ 69 | ENV/ 70 | env.bak/ 71 | venv.bak/ 72 | 73 | # IDE/Editor 74 | .vscode/ 75 | .idea 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | LivePopularTimes: Copyright (c) 2020 Brian Chen 2 | 3 | 4 | PopularTimes: Copyright (c) 2018 m-wrzr 5 | 6 | MIT License 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Always prefer setuptools over distutils 2 | from setuptools import setup, find_packages 3 | # To use a consistent encoding 4 | from codecs import open 5 | from os import path 6 | 7 | here = path.abspath(path.dirname(__file__)) 8 | 9 | # Get the long description from the README file 10 | with open(path.join(here, 'README.md'), encoding='utf-8') as f: 11 | long_description = f.read() 12 | 13 | setup( 14 | name='LivePopularTimes', 15 | version='1.3', 16 | description='LivePopularTimes: A Google Maps scraper', 17 | long_description=long_description, 18 | long_description_content_type='text/markdown', 19 | url='https://github.com/GrocerCheck/LivePopularTimes', 20 | author='Brian Chen', 21 | author_email = 'brianchen.chen@mail.utoronto.ca', 22 | license='MIT', 23 | classifiers=[ 24 | 'Development Status :: 4 - Beta', 25 | 'Intended Audience :: Developers', 26 | 'Topic :: Software Development :: Build Tools', 27 | 'License :: OSI Approved :: MIT License', 28 | 29 | # Specify the Python versions you support here. In particular, ensure 30 | # that you indicate whether you support Python 2, Python 3 or both. 31 | 'Programming Language :: Python :: 3.6', 32 | 'Programming Language :: Python :: 3.7', 33 | 'Programming Language :: Python :: 3.8', 34 | 'Programming Language :: Python :: 3.9', 35 | ], 36 | keywords=['populartimes googlemaps live', 'googlemaps', 'scraper', 'crawler', 'populartimes'], 37 | 38 | # TODO 39 | # You can just specify the packages manually here if your project is 40 | # simple. Or you can use find_packages(). 41 | packages=find_packages(exclude=['content', 'test']), 42 | 43 | # List run-time dependencies here. These will be installed by pip when 44 | # your project is installed. For an analysis of "install_requires" vs pip's 45 | # requirements files see: 46 | # https://packaging.python.org/en/latest/requirements.html 47 | install_requires=[ 48 | 'requests', 49 | ], 50 | 51 | ) 52 | -------------------------------------------------------------------------------- /livepopulartimes/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | LICENSE 6 | 7 | MIT License 8 | 9 | Copyright (c) 2020 ihasdapie 10 | 11 | Copyright (c) 2020 TheJoin95 12 | Copyright (c) 2018 m-wrzr 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in all 22 | copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | SOFTWARE. 31 | """ 32 | 33 | 34 | from .crawler import get_populartimes_by_place_id 35 | from .crawler import get_populartimes_by_formatted_address 36 | from .crawler import get_places 37 | 38 | import logging 39 | logging.getLogger(__name__).addHandler(logging.NullHandler()) 40 | 41 | """ 42 | 43 | ENTRY POINT 44 | 45 | """ 46 | 47 | 48 | 49 | def get_populartimes_by_address(formatted_address, proxy = False): 50 | """ 51 | retrieves populartimes & other location data for a given place by formatted address 52 | :param formatted_address: Preferred format: "(*location name*) , *full address*, *city*, *province/state/etc*, *country*" 53 | :param proxy: A dictionary of proxies of format { 54 | "http" : "http://10.10.1.10:2138", 55 | "https" : "http://10.10.1.10:1080", 56 | } 57 | :type formatted_address: string 58 | :return: json-formatted populartimes & current populartimes for a place (if applicable) alongside other scraped data 59 | :Example: 60 | 61 | detail_json = get_populartimes_by_address("(Costco) 605 Expo Blvd, Vancouver, BC V6B 1V4, Canada", proxy = proxy) 62 | 63 | """ 64 | 65 | return get_populartimes_by_formatted_address(formatted_address, proxy) 66 | 67 | def get_populartimes_by_PlaceID(api_key, place_id): 68 | """ 69 | retrieves populartimes & other location data for a given place by placeId 70 | :param api_key: 71 | :param place_id: 72 | :return: json-formatted populartimes & current populartimes for a place (if applicable) alongside other scraped data 73 | :Example: 74 | 75 | detail_json = get_populartimes_by_PlaceID(APIKEY, "ChIJnS31ep9zhlQR2ns5dwJVvcg") 76 | 77 | .. warning:: Makes API call 78 | 79 | """ 80 | return get_populartimes_by_place_id(api_key, place_id) 81 | 82 | def get_places_by_search(query): 83 | """ 84 | Retrieve a list of places via google search 85 | :param query: 86 | :type query: string 87 | :return: list of places with scraped details (refer to readme for complete list) 88 | :Example: 89 | 90 | places = get_places_by_search("pubs open in London") 91 | """ 92 | return get_places(query) 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Live Popular Times 2 | A library that extends the populartimes module to include support for **Live** *Google Maps* popular times data, which is currently inaccessible via Google's API. 3 | ## Getting Started 4 | 5 | ### Requirements 6 | Google Maps API key https://developers.google.com/places/web-service/get-api-key 7 | 8 | ### Installation: 9 | 10 | ##### Options: 11 | 1. For most current version: `python3 -m pip install --upgrade git+https://github.com/GrocerCheck/LivePopularTimes` 12 | 2. `git clone` the repository, `cd` into the populartimes directory and run `pip install .` 13 | 3. Possibly outdated: `python3 -m pip install LivePopularTimes` 14 | 15 | ## Usage 16 | 17 | - Please note that certain functions of this module use the Google Places Web API, which is billable to Google. 18 | - Functions that accept api keys in their arguments will make API calls, and vice versa (self-explanatory) 19 | - If you're trying to reduce API usage a typical workflow may look like: 20 | 1. Get PlaceIDs & addresses https://developers.google.com/places/web-service/search#PlaceSearchRequests 21 | 2. Create formatted addresses. You may need to use `get_populartimes_by_Place_ID` to get more address data if necessary. This performs a reverse lookup by placeID. 22 | 3. use `get_populartimes_by_address` to collect data without API use! 23 | 24 | ## livepopulartimes.get_populartimes_by_address(formatted_address) 25 | 26 | Retrieves information for a given address and adds populartimes, wait, time_spent and other data not accessible via Google Places by scraping given a formatted address. **Does not make an API call!** 27 | + `livepopulartimes.get_populartimes_by_address(formatted_address)` 28 | + **formatted_address** 29 | + str; address of location you are looking for, preferably in the following format: 30 | + "(*location name*) , *full address*, *city*, *province/state/etc*, *country*" 31 | + **proxy** 32 | + default = False 33 | + proxies ip in format 34 | `{ 35 | "http" : "http://10.10.1.10:2138", 36 | "https" : "http://10.10.1.10:1080", 37 | `} 38 | 39 | + **Example call** 40 | + `livepopulartimes.get_populartimes_by_address("(H-Mart Dunbar) 5557 Dunbar Street, Vancouver BC, Canada", proxy=proxy)` 41 | 42 | + **Response** 43 | + The information present for places can vary. Therefore *popularity*, *current_popularity*, *rating*, *rating_n*, *time_wait*, *time_spent* and *phone* are optional return parameters and only present if available. 44 | + *time_wait* and *time_spent* are in minutes 45 | + **Note**: The *time_wait* and *time_spent* parameters were only added recently to Google Maps and are only present as a language specific string. The extracted values may therefore be incorrect and you might have to parse the raw string yourself, depending on your language settings. 46 | + Returns dictionary of the following parameters: 47 | + `name` 48 | + `place_id` 49 | + `populartimes` *populartimes is "cleaned up" data, please refer to example output for details* 50 | + `current_popularity` 51 | + `popular_times` *popular_times is "raw" data, please refer to example output for details* 52 | + `address` 53 | + `location` 54 | + `lat` 55 | + `lng` 56 | + `categories` 57 | + `place_types` 58 | + **Refer to `example_output(get_populartimes_by_address).json)` for example output** 59 | 60 | 61 | ## livepopulartimes.get_populartimes_by_PlaceID(api_key, place_id) 62 | 63 | Retrieves information for a given address and adds populartimes, wait, time_spent and other data not accessible via Google Places via Places API call & scraping, given a place_id. 64 | 65 | + `livepopulartimes.get_populartimes_by_PlaceID(api_key, place_id)` 66 | + **api_key** 67 | + str; your Google Places API key 68 | + **place_id** 69 | + str; Unique Google Places place identifier, can be obtained via `get_places_by_search` or Google Places javascript API 70 | + more details here: [https://developers.google.com/places/place-id] 71 | 72 | + **Example call** 73 | + `livepopulartimes.get_populartimes_by_PlaceID(API_KEY, "ChIJtW0qSJp0hlQRj22fXuPh7s4")` 74 | 75 | + **Response** 76 | + The information present for places can vary. Therefore *popularity*, *current_popularity*, *rating*, *rating_n*, *time_wait*, *time_spent* and *phone* are optional return parameters and only present if available. 77 | + *time_wait* and *time_spent* are in minutes 78 | + **Note**: The *time_wait* and *time_spent* parameters were only added recently to Google Maps and are only present as a language specific string. The extracted values may therefore be incorrect and you might have to parse the raw string yourself, depending on your language settings. 79 | + Returns dictionary of the following parameters: 80 | + `name` 81 | + `place_id` 82 | + `populartimes` *populartimes is "cleaned up" data, please refer to example output for details* 83 | + `current_popularity` 84 | + `popular_times` *popular_times is "raw" data, please refer to example output for details* 85 | + `address` 86 | + `location` 87 | + `lat` 88 | + `lng` 89 | + `hours` 90 | + `categories` 91 | + `place_types` 92 | + **Refer to `example_output(get_populartimes_by_PlaceID).json)` for example output** 93 | 94 | 95 | ## livepopulartimes.get_places_by_search(api_key, query) 96 | 97 | Retrives Google Maps location data from search query 98 | 99 | + `livepopulartimes.get_populartimes_by_search(api_key, query)` 100 | + **query** 101 | + str; Google search query 102 | + **Example call** 103 | + `livepopulartimes.get_places_by_query("restaurants open in London")` 104 | 105 | + **Response** 106 | + The information present for places can vary 107 | + Returns dictionary with the following fields: 108 | + `name` 109 | + `place_id` 110 | + `address` 111 | + `location` 112 | + `lat` 113 | + `lng` 114 | + `categories` 115 | + `place_types` 116 | + **Refer to `example_output(get_places_by_search).txt)` for example output** 117 | 118 | Special thanks to m-wrzr's populartimes module for the framework upon which this is built. 119 | -------------------------------------------------------------------------------- /example_output(get_places_by_search).txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name":"Corbeaux's London Market", 4 | "place_id":"ChIJce4kUcmAhYARJYXzSyNbAtA", 5 | "address":"2901 Sacramento St, San Francisco, CA 94115, United States", 6 | "location":{ 7 | "lat":37.7888836, 8 | "lng":-122.4407559 9 | }, 10 | "categories":[ 11 | "Grocery store", 12 | "Deli", 13 | "Liquor store" 14 | ], 15 | "place_types":[ 16 | [ 17 | "grocery_store" 18 | ], 19 | [ 20 | "deli" 21 | ], 22 | [ 23 | "liquor_store" 24 | ] 25 | ] 26 | }, 27 | { 28 | "name":"London Food Co-op", 29 | "place_id":"ChIJ2R_sZQryLogRT8RRhaTJZy8", 30 | "address":"621 Princess Ave, London, ON N5W 3L9", 31 | "location":{ 32 | "lat":42.992987199999995, 33 | "lng":-81.23283099999999 34 | }, 35 | "categories":[ 36 | "Natural goods store" 37 | ], 38 | "place_types":[ 39 | [ 40 | "natural_foods_store" 41 | ] 42 | ] 43 | }, 44 | { 45 | "name":"United Supermarket", 46 | "place_id":"ChIJcZp_GubtLogRgXHlB574AfM", 47 | "address":"1062 Adelaide St N, London, ON N5Y 2N1", 48 | "location":{ 49 | "lat":43.0093458, 50 | "lng":-81.2408401 51 | }, 52 | "categories":[ 53 | "Grocery store", 54 | "Asian grocery store", 55 | "Supermarket" 56 | ], 57 | "place_types":[ 58 | [ 59 | "grocery_store" 60 | ], 61 | [ 62 | "asian_grocery_store" 63 | ], 64 | [ 65 | "supermarket" 66 | ] 67 | ] 68 | }, 69 | { 70 | "name":"Superking Supermarket", 71 | "place_id":"ChIJqSOkbRvxLogRfTWxjSW-E0Q", 72 | "address":"785 Wonderland Rd S, London, ON N6K 1M6", 73 | "location":{ 74 | "lat":42.9489863, 75 | "lng":-81.29122 76 | }, 77 | "categories":[ 78 | "Asian grocery store", 79 | "Supermarket" 80 | ], 81 | "place_types":[ 82 | [ 83 | "asian_grocery_store" 84 | ], 85 | [ 86 | "supermarket" 87 | ] 88 | ] 89 | }, 90 | { 91 | "name":"Covent Garden Market", 92 | "place_id":"ChIJ8a8sqh3yLogR5bUJ-zrfBrg", 93 | "address":"Covent Garden Market Bldg, 130 King St, London, ON N6A 1C5", 94 | "location":{ 95 | "lat":42.9828131, 96 | "lng":-81.2503452 97 | }, 98 | "categories":[ 99 | "Grocery store", 100 | "Farmers' market" 101 | ], 102 | "place_types":[ 103 | [ 104 | "grocery_store" 105 | ], 106 | [ 107 | "farmers_market" 108 | ] 109 | ] 110 | }, 111 | { 112 | "name":"Old East Village Grocer", 113 | "place_id":"ChIJt-t3RAvyLogRRKcZuKzlp-s", 114 | "address":"630 Dundas St, London, ON N5W 2Y8", 115 | "location":{ 116 | "lat":42.9892855, 117 | "lng":-81.2308814 118 | }, 119 | "categories":[ 120 | "Grocery store", 121 | "Supermarket" 122 | ], 123 | "place_types":[ 124 | [ 125 | "grocery_store" 126 | ], 127 | [ 128 | "supermarket" 129 | ] 130 | ] 131 | }, 132 | { 133 | "name":"Festival Food-Mart", 134 | "place_id":"ChIJR8sVStvzLogRLr2Nuta5Iwo", 135 | "address":"456 Southdale Rd E, London, ON N6E 1A3", 136 | "location":{ 137 | "lat":42.942456, 138 | "lng":-81.242172 139 | }, 140 | "categories":[ 141 | "Grocery store", 142 | "Supermarket" 143 | ], 144 | "place_types":[ 145 | [ 146 | "grocery_store" 147 | ], 148 | [ 149 | "supermarket" 150 | ] 151 | ] 152 | }, 153 | { 154 | "name":"Remark Fresh Markets", 155 | "place_id":"ChIJP28BamnwLogRmCb4EX25N80", 156 | "address":"1190 Oxford St W, London, ON N6H 4N2", 157 | "location":{ 158 | "lat":42.9744148, 159 | "lng":-81.3217544 160 | }, 161 | "categories":[ 162 | "Gourmet grocery store" 163 | ], 164 | "place_types":[ 165 | [ 166 | "gourmet_grocery_store" 167 | ] 168 | ] 169 | }, 170 | { 171 | "name":"Indo-Asian Groceries and Spices", 172 | "place_id":"ChIJs0lA6TTwLogR3d8YRzYJqI4", 173 | "address":"689 Oxford St W, London, ON N6H 1V1", 174 | "location":{ 175 | "lat":42.9826075, 176 | "lng":-81.2982152 177 | }, 178 | "categories":[ 179 | "Asian grocery store", 180 | "Beauty supply store", 181 | "Grocery store", 182 | "Indian grocery store", 183 | "Indian restaurant", 184 | "Organic shop", 185 | "Rice shop", 186 | "Spice store" 187 | ], 188 | "place_types":[ 189 | [ 190 | "asian_grocery_store" 191 | ], 192 | [ 193 | "beauty_supply_store" 194 | ], 195 | [ 196 | "grocery_store" 197 | ], 198 | [ 199 | "indian_grocery_store" 200 | ], 201 | [ 202 | "indian_restaurant", 203 | "Indian" 204 | ], 205 | [ 206 | "organic_store" 207 | ], 208 | [ 209 | "rice_shop" 210 | ], 211 | [ 212 | "spice_store" 213 | ] 214 | ] 215 | }, 216 | { 217 | "name":"Food Island Supermarket", 218 | "place_id":"ChIJX_NH4cvxLogRB7ZdjNVleUI", 219 | "address":"530 Oxford St W, London, ON N6H 1T6", 220 | "location":{ 221 | "lat":42.9827167, 222 | "lng":-81.2903694 223 | }, 224 | "categories":[ 225 | "Asian grocery store", 226 | "Supermarket" 227 | ], 228 | "place_types":[ 229 | [ 230 | "asian_grocery_store" 231 | ], 232 | [ 233 | "supermarket" 234 | ] 235 | ] 236 | }, 237 | { 238 | "name":"The Grocery", 239 | "place_id":"ChIJe1Pt6LscdkgR1yaQghzm0OY", 240 | "address":"52-56 Kingsland Rd, Hackney, London E2 8DP, United Kingdom", 241 | "location":{ 242 | "lat":51.528903899999996, 243 | "lng":-0.0775836 244 | }, 245 | "categories":[ 246 | "Grocery store", 247 | "Supermarket" 248 | ], 249 | "place_types":[ 250 | [ 251 | "grocery_store" 252 | ], 253 | [ 254 | "supermarket" 255 | ] 256 | ] 257 | }, 258 | { 259 | "name":"FreshCo Trafalgar & Highbury", 260 | "place_id":"ChIJMfh5-oTyLogRzY28cXG-QaE", 261 | "address":"1298 Trafalgar St, London, ON N5Z 1H9", 262 | "location":{ 263 | "lat":42.985, 264 | "lng":-81.2 265 | }, 266 | "categories":[ 267 | "Grocery store" 268 | ], 269 | "place_types":[ 270 | [ 271 | "grocery_store" 272 | ] 273 | ] 274 | }, 275 | { 276 | "name":"Grocery Checkout Fresh Market", 277 | "place_id":"ChIJe8b6qT3uLogRUQC9Ymdoq7o", 278 | "address":"1151 Richmond St, London, ON N6A 3K7", 279 | "location":{ 280 | "lat":43.008891, 281 | "lng":-81.276276 282 | }, 283 | "categories":[ 284 | "Grocery store", 285 | "Supermarket" 286 | ], 287 | "place_types":[ 288 | [ 289 | "grocery_store" 290 | ], 291 | [ 292 | "supermarket" 293 | ] 294 | ] 295 | }, 296 | { 297 | "name":"Oriental Mart (Hanul Food Inc.)", 298 | "place_id":"ChIJtUJImYLxLogR4se_ClXOoVk", 299 | "address":"515 Wharncliffe Rd S Unit 4, London, ON N6J 2N1", 300 | "location":{ 301 | "lat":42.9576163, 302 | "lng":-81.26120809999999 303 | }, 304 | "categories":[ 305 | "Grocery store", 306 | "Supermarket" 307 | ], 308 | "place_types":[ 309 | [ 310 | "grocery_store" 311 | ], 312 | [ 313 | "supermarket" 314 | ] 315 | ] 316 | }, 317 | { 318 | "name":"Aladdin's Food", 319 | "place_id":"ChIJubYTz8vxLogRSMgBoHS4j1A", 320 | "address":"611 Wonderland Rd N #9, London, ON N6H 1T6", 321 | "location":{ 322 | "lat":42.9813769, 323 | "lng":-81.2895611 324 | }, 325 | "categories":[ 326 | "Grocery store", 327 | "Butcher shop", 328 | "Meat processor", 329 | "Supermarket" 330 | ], 331 | "place_types":[ 332 | [ 333 | "grocery_store" 334 | ], 335 | [ 336 | "butcher_shop" 337 | ], 338 | [ 339 | "meat_processor" 340 | ], 341 | [ 342 | "supermarket" 343 | ] 344 | ] 345 | }, 346 | { 347 | "name":"Sobeys North London", 348 | "place_id":"ChIJAAAAAITuLogRmvPqJsP-WwA", 349 | "address":"1595 Adelaide St N, London, ON N5X 4E8", 350 | "location":{ 351 | "lat":43.036618999999995, 352 | "lng":-81.257431 353 | }, 354 | "categories":[ 355 | "Grocery store" 356 | ], 357 | "place_types":[ 358 | [ 359 | "grocery_store" 360 | ] 361 | ] 362 | }, 363 | { 364 | "name":"Westmount Halal Food Store", 365 | "place_id":"ChIJlyS55wDxLogRBK99lShaph0", 366 | "address":"490 Wonderland Rd S, London, ON N6K 3T1", 367 | "location":{ 368 | "lat":42.9573964, 369 | "lng":-81.291861 370 | }, 371 | "categories":[ 372 | "Supermarket" 373 | ], 374 | "place_types":[ 375 | [ 376 | "supermarket" 377 | ] 378 | ] 379 | }, 380 | { 381 | "name":"Loblaws", 382 | "place_id":"ChIJrePRPjzxLogRInxB_6UQjdA", 383 | "address":"3040 Wonderland Rd S, London, ON N6L 1A6", 384 | "location":{ 385 | "lat":42.9377259, 386 | "lng":-81.27816779999999 387 | }, 388 | "categories":[ 389 | "Grocery store", 390 | "Bakery", 391 | "Deli", 392 | "Fruit and vegetable store", 393 | "Pharmacy" 394 | ], 395 | "place_types":[ 396 | [ 397 | "grocery_store" 398 | ], 399 | [ 400 | "bakery" 401 | ], 402 | [ 403 | "deli" 404 | ], 405 | [ 406 | "fruit_and_vegetable_store" 407 | ], 408 | [ 409 | "pharmacy" 410 | ] 411 | ] 412 | }, 413 | { 414 | "name":"Metro", 415 | "place_id":"ChIJ78vYXKryLogRpGuG0qP4vHE", 416 | "address":"155 Clarke Rd, London, ON N5W 5C9", 417 | "location":{ 418 | "lat":42.990649999999995, 419 | "lng":-81.17053299999999 420 | }, 421 | "categories":[ 422 | "Supermarket", 423 | "Caterer", 424 | "Grocery delivery service" 425 | ], 426 | "place_types":[ 427 | [ 428 | "supermarket" 429 | ], 430 | [ 431 | "catering_service" 432 | ], 433 | [ 434 | "grocery_delivery_service" 435 | ] 436 | ] 437 | } 438 | ] 439 | -------------------------------------------------------------------------------- /livepopulartimes/crawler.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | LICENSE 6 | 7 | MIT License 8 | 9 | Copyright (c) 2020 Brian Chen 10 | 11 | Copyright (c) 2020 TheJoin95 12 | Copyright (c) 2018 m-wrzr 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in all 22 | copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | SOFTWARE. 31 | """ 32 | 33 | 34 | import calendar 35 | import json 36 | import urllib.request 37 | import urllib.parse 38 | import requests 39 | import re 40 | 41 | 42 | # urls for google api web service 43 | BASE_URL = "https://maps.googleapis.com/maps/api/place/" 44 | RADAR_URL = BASE_URL + "radarsearch/json?location={},{}&radius={}&types={}&key={}" 45 | NEARBY_URL = BASE_URL + "nearbysearch/json?location={},{}&radius={}&types={}&key={}" 46 | DETAIL_URL = BASE_URL + "details/json?placeid={}&key={}" 47 | 48 | # user agent for populartimes request 49 | 50 | HEADERS = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) " 51 | "AppleWebKit/537.36 (KHTML, like Gecko) " 52 | "Chrome/54.0.2840.98 Safari/537.36"} 53 | 54 | 55 | class PopulartimesException(Exception): 56 | """Exception raised for errors in the input. 57 | 58 | Attributes: 59 | expression -- input expression in which the error occurred 60 | message -- explanation of the error 61 | """ 62 | 63 | def __init__(self, expression, message): 64 | self.expression = expression 65 | self.message = message 66 | 67 | def get_places(query): 68 | """ 69 | :param query: search string for google 70 | :type query: string 71 | :return: List of places with name, place_id, address, co-ordinates, categories, and types. 72 | :rtype list: 73 | 74 | This will return a list of places with details according to a google query: does not make API call 75 | """ 76 | places = [] 77 | json = make_google_search_request(query) #consider adding other google search parameters 78 | json = json[0][1] 79 | inflist=[] 80 | for x in range(1, len(json)-1): 81 | info = index_get(json[x], 14) 82 | places.append({ 83 | "name": index_get(info, 11), 84 | "place_id": index_get(info, 78), 85 | "address": index_get(info, 39), 86 | "location": { 87 | "lat": index_get(info, 9, 2), 88 | "lng": index_get(info, 9, 3) 89 | }, 90 | "categories": index_get(info, 13), 91 | "place_types": index_get(info, 76), 92 | }) 93 | 94 | return places 95 | 96 | def get_popularity_for_day(popularity): 97 | """ 98 | Parses popularity from scrape to return popularity for day 99 | :param popularity: 100 | :return: popularity_for_day 101 | 102 | """ 103 | 104 | # Initialize empty matrix with 0s 105 | pop_json = [[0 for _ in range(24)] for _ in range(7)] 106 | wait_json = [[0 for _ in range(24)] for _ in range(7)] 107 | 108 | for day in popularity: 109 | 110 | day_no, pop_times = day[:2] 111 | 112 | if pop_times: 113 | for hour_info in pop_times: 114 | 115 | hour = hour_info[0] 116 | pop_json[day_no - 1][hour] = hour_info[1] 117 | 118 | # check if the waiting string is available and convert no minutes 119 | if len(hour_info) > 5: 120 | wait_digits = re.findall(r'\d+', hour_info[3]) 121 | 122 | if len(wait_digits) == 0: 123 | wait_json[day_no - 1][hour] = 0 124 | elif "min" in hour_info[3]: 125 | wait_json[day_no - 1][hour] = int(wait_digits[0]) 126 | elif "hour" in hour_info[3]: 127 | wait_json[day_no - 1][hour] = int(wait_digits[0]) * 60 128 | else: 129 | wait_json[day_no - 1][hour] = int(wait_digits[0]) * 60 + int(wait_digits[1]) 130 | 131 | # day wrap 132 | if hour_info[0] == 23: 133 | day_no = day_no % 7 + 1 134 | 135 | ret_popularity = [ 136 | { 137 | "name": list(calendar.day_name)[d], 138 | "data": pop_json[d] 139 | } for d in range(7) 140 | ] 141 | 142 | # waiting time only if applicable 143 | ret_wait = [ 144 | { 145 | "name": list(calendar.day_name)[d], 146 | "data": wait_json[d] 147 | } for d in range(7) 148 | ] if any(any(day) for day in wait_json) else [] 149 | 150 | # {"name" : "monday", "data": [...]} for each weekday as list 151 | return ret_popularity, ret_wait 152 | 153 | def index_get(array, *argv): 154 | """ 155 | checks if a index is available in the array and returns it 156 | :param array: the data array 157 | :param argv: index integers 158 | :return: None if not available or the return value 159 | """ 160 | 161 | try: 162 | 163 | for index in argv: 164 | array = array[index] 165 | 166 | return array 167 | 168 | # there is either no info available or no popular times 169 | # TypeError: rating/rating_n/populartimes wrong of not available 170 | except (IndexError, TypeError): 171 | return None 172 | 173 | def get_populartimes_by_place_id(api_key, place_id): 174 | """ 175 | sends request to Google Maps detail API to get a search string 176 | and uses standard proto buffer to get additional information 177 | on the current status of popular times 178 | :param api_key: api key 179 | :param place_id: unique place_id from google 180 | :return: json details 181 | """ 182 | 183 | # places api - detail search 184 | # https://developers.google.com/places/web-service/details?hl=de 185 | detail_str = DETAIL_URL.format(place_id, api_key) 186 | 187 | 188 | #should include USERAGENTS 189 | 190 | resp = json.loads(requests.get(detail_str, auth=('user', 'pass')).text) 191 | check_response_code(resp) 192 | detail = resp["result"] #A lot of other data such as place reviews and opening hours, etc can be scraped off of `detail` 193 | return format_and_add_param(detail, api_key, get_detail = True) 194 | 195 | def format_and_add_param(detail, api_key, get_detail): 196 | """ 197 | Formats details & makes call to add_param_from_search to add details 198 | :param detail: detail from Google Maps Details API 199 | :param api_key: api key 200 | :param get_detail: whether or not if populartimes should return all scrapable data in its own detail 201 | """ 202 | address = detail["formatted_address"] if "formatted_address" in detail else detail.get("vicinity", "") 203 | place_id = "{} {}".format(detail["name"], address) 204 | 205 | try: 206 | hours = detail["opening_hours"] 207 | 208 | except: 209 | hours = None 210 | detail_json = { 211 | 212 | "place_id": detail["place_id"], 213 | "name": detail["name"], 214 | "hours": hours, 215 | "place_types": detail["types"], 216 | "coordinates": detail["geometry"]["location"] 217 | } 218 | detail_json = add_param_from_search(detail_json, detail, *get_populartimes_from_search(place_id, get_detail)) 219 | 220 | return detail_json 221 | 222 | def make_google_search_request(query_string, proxy = False): 223 | params_url = { 224 | "tbm": "map", 225 | "tch": 1, 226 | "hl": "en", 227 | "q": urllib.parse.quote_plus(query_string), 228 | "pb": "!4m12!1m3!1d4005.9771522653964!2d-122.42072974863942!3d37.8077459796541!2m3!1f0!2f0!3f0!3m2!1i1125!2i976" 229 | "!4f13.1!7i20!10b1!12m6!2m3!5m1!6e2!20e3!10b1!16b1!19m3!2m2!1i392!2i106!20m61!2m2!1i203!2i100!3m2!2i4!5b1" 230 | "!6m6!1m2!1i86!2i86!1m2!1i408!2i200!7m46!1m3!1e1!2b0!3e3!1m3!1e2!2b1!3e2!1m3!1e2!2b0!3e3!1m3!1e3!2b0!3e3!" 231 | "1m3!1e4!2b0!3e3!1m3!1e8!2b0!3e3!1m3!1e3!2b1!3e2!1m3!1e9!2b1!3e2!1m3!1e10!2b0!3e3!1m3!1e10!2b1!3e2!1m3!1e" 232 | "10!2b0!3e4!2b1!4b1!9b0!22m6!1sa9fVWea_MsX8adX8j8AE%3A1!2zMWk6Mix0OjExODg3LGU6MSxwOmE5ZlZXZWFfTXNYOGFkWDh" 233 | "qOEFFOjE!7e81!12e3!17sa9fVWea_MsX8adX8j8AE%3A564!18e15!24m15!2b1!5m4!2b1!3b1!5b1!6b1!10m1!8e3!17b1!24b1!" 234 | "25b1!26b1!30m1!2b1!36b1!26m3!2m2!1i80!2i92!30m28!1m6!1m2!1i0!2i0!2m2!1i458!2i976!1m6!1m2!1i1075!2i0!2m2!" 235 | "1i1125!2i976!1m6!1m2!1i0!2i0!2m2!1i1125!2i20!1m6!1m2!1i0!2i956!2m2!1i1125!2i976!37m1!1e81!42b1!47m0!49m1" 236 | "!3b1" 237 | } 238 | 239 | search_url = "http://www.google.com/search?" + "&".join(k + "=" + str(v) for k, v in params_url.items()) 240 | # TODO check for 404 241 | # But this should never be a big problem because it is a search request 242 | # noinspection PyUnresolvedReferences 243 | if (proxy == False): 244 | resp = requests.get(search_url, headers=HEADERS) 245 | else: 246 | resp = requests.get(search_url, proxies = proxy, headers=HEADERS) 247 | 248 | data = resp.text.split('/*""*/')[0] 249 | 250 | # resp = urllib.request.urlopen(urllib.request.Request(url=search_url, data=None, headers=USER_AGENT), 251 | # context=gcontext) 252 | # data = resp.read().decode('utf-8').split('/*""*/')[0] 253 | 254 | # find eof json 255 | jend = data.rfind("}") 256 | if jend >= 0: 257 | data = data[:jend + 1] 258 | 259 | jdata = json.loads(data)["d"] 260 | return json.loads(jdata[4:]) 261 | 262 | def get_populartimes_from_search(formatted_address, get_detail=False, proxy = False): 263 | """ 264 | request information for a place and parse current popularity 265 | :param formatted_address: name and address string 266 | :return: 267 | """ 268 | 269 | jdata = make_google_search_request(formatted_address, proxy = proxy) 270 | 271 | # get info from result array, has to be adapted if backend api changes 272 | info = index_get(jdata, 0, 1, 0, 14) 273 | 274 | rating = index_get(info, 4, 7) 275 | rating_n = index_get(info, 4, 8) 276 | 277 | popular_times = index_get(info, 84, 0) 278 | 279 | # current_popularity is also not available if popular_times isn't 280 | current_popularity = index_get(info, 84, 7, 1) 281 | 282 | time_spent = index_get(info, 117, 0) 283 | 284 | detail = {} 285 | 286 | if (get_detail == True): 287 | detail = { 288 | "name": index_get(info, 11), 289 | "place_id": index_get(info, 78), 290 | "address": index_get(info, 39), 291 | "coordinates": { 292 | "lat": index_get(info, 9, 2), 293 | "lng": index_get(info, 9, 3) 294 | }, 295 | "categories": index_get(info, 13), 296 | "place_types": index_get(info, 76), 297 | "current_popularity": index_get(info, 84, 7, 1), 298 | "popular_times": index_get(info, 84, 0), 299 | } 300 | 301 | # extract wait times and convert to minutes 302 | if time_spent: 303 | nums = [float(f) for f in re.findall(r'\d*\.\d+|\d+', time_spent.replace(",", "."))] 304 | contains_min, contains_hour = "min" in time_spent, "hour" in time_spent or "hr" in time_spent 305 | 306 | time_spent = None 307 | 308 | if contains_min and contains_hour: 309 | time_spent = [nums[0], nums[1] * 60] 310 | elif contains_hour: 311 | time_spent = [nums[0] * 60, (nums[0] if len(nums) == 1 else nums[1]) * 60] 312 | elif contains_min: 313 | time_spent = [nums[0], nums[0] if len(nums) == 1 else nums[1]] 314 | 315 | time_spent = [int(t) for t in time_spent] 316 | 317 | return rating, rating_n, popular_times, current_popularity, time_spent, detail 318 | 319 | def add_param_from_search(detail_json, detail, rating, rating_n, popularity, current_popularity, time_spent, detailFromGoogle={}): 320 | """ 321 | check for optional return parameters using google search and add them to the result json 322 | :param detail_json: 323 | :param detail: 324 | :param rating: 325 | :param rating_n: number of ratings 326 | :param popularity: 327 | :param current_popularity: 328 | :param time_spent: 329 | :return: detail_json with info from google search scrape 330 | """ 331 | 332 | if rating: 333 | detail_json["rating"] = rating 334 | elif "rating" in detail: 335 | detail_json["rating"] = detail["rating"] 336 | 337 | if rating_n: 338 | detail_json["rating_n"] = rating_n 339 | 340 | if "international_phone_number" in detail: 341 | detail_json["international_phone_number"] = detail["international_phone_number"] 342 | 343 | if current_popularity: 344 | detail_json["current_popularity"] = current_popularity 345 | 346 | if popularity: 347 | popularity, wait_times = get_popularity_for_day(popularity) 348 | 349 | detail_json["populartimes"] = popularity 350 | 351 | if wait_times: 352 | detail_json["time_wait"] = wait_times 353 | 354 | if time_spent: 355 | detail_json["time_spent"] = time_spent 356 | 357 | if ("name" in detailFromGoogle): 358 | detail_json.update(detailFromGoogle) 359 | 360 | return detail_json 361 | 362 | def check_response_code(resp): 363 | """ 364 | check if query quota has been surpassed or other errors occured 365 | :param resp: json response 366 | :return: 367 | """ 368 | if resp["status"] == "OK" or resp["status"] == "ZERO_RESULTS": 369 | return 370 | 371 | if resp["status"] == "REQUEST_DENIED": 372 | raise PopulartimesException("Google Places " + resp["status"], 373 | "Request was denied, the API key is invalid.") 374 | 375 | if resp["status"] == "OVER_QUERY_LIMIT": 376 | raise PopulartimesException("Google Places " + resp["status"], 377 | "You exceeded your Query Limit for Google Places API Web Service, " 378 | "check https://developers.google.com/places/web-service/usage " 379 | "to upgrade your quota.") 380 | 381 | if resp["status"] == "INVALID_REQUEST": 382 | raise PopulartimesException("Google Places " + resp["status"], 383 | "The query string is malformed, " 384 | "check if your formatting for lat/lng and radius is correct.") 385 | 386 | if resp["status"] == "NOT_FOUND": 387 | raise PopulartimesException("Google Places " + resp["status"], 388 | "The place ID was not found and either does not exist or was retired.") 389 | 390 | raise PopulartimesException("Google Places " + resp["status"], 391 | "Unidentified error with the Places API, please check the response code") 392 | 393 | def get_populartimes_by_formatted_address(formatted_address, proxy = False): 394 | detail_json = {} 395 | detail = {} 396 | detail_json = add_param_from_search(detail_json, detail, *get_populartimes_from_search(formatted_address, get_detail=True, proxy=proxy)) 397 | return detail_json 398 | -------------------------------------------------------------------------------- /example_output(get_populartimes_by_PlaceID).json: -------------------------------------------------------------------------------- 1 | { 2 | "place_id":"ChIJ9VlacLNzhlQRTs_gpcgsa1Y", 3 | "name":"Safeway 4th & Vine", 4 | "address":"2315 W 4th Ave, Vancouver, BC V6K 1P2", 5 | "place_types":[ 6 | [ 7 | "supermarket" 8 | ], 9 | [ 10 | "bakery" 11 | ], 12 | [ 13 | "florist" 14 | ] 15 | ], 16 | "coordinates":{ 17 | "lat":49.2686111, 18 | "lng":-123.15861109999999 19 | }, 20 | "rating":3.7, 21 | "rating_n":839, 22 | "international_phone_number":"+1 604-737-9803", 23 | "populartimes":[ 24 | { 25 | "name":"Monday", 26 | "data":[ 27 | 0, 28 | 0, 29 | 0, 30 | 0, 31 | 0, 32 | 0, 33 | 0, 34 | 0, 35 | 11, 36 | 19, 37 | 28, 38 | 36, 39 | 42, 40 | 46, 41 | 51, 42 | 64, 43 | 82, 44 | 92, 45 | 81, 46 | 59, 47 | 38, 48 | 0, 49 | 0, 50 | 0 51 | ] 52 | }, 53 | { 54 | "name":"Tuesday", 55 | "data":[ 56 | 0, 57 | 0, 58 | 0, 59 | 0, 60 | 0, 61 | 0, 62 | 0, 63 | 0, 64 | 12, 65 | 22, 66 | 34, 67 | 46, 68 | 53, 69 | 54, 70 | 51, 71 | 54, 72 | 70, 73 | 84, 74 | 75, 75 | 47, 76 | 26, 77 | 0, 78 | 0, 79 | 0 80 | ] 81 | }, 82 | { 83 | "name":"Wednesday", 84 | "data":[ 85 | 0, 86 | 0, 87 | 0, 88 | 0, 89 | 0, 90 | 0, 91 | 0, 92 | 0, 93 | 9, 94 | 16, 95 | 25, 96 | 33, 97 | 37, 98 | 36, 99 | 37, 100 | 46, 101 | 62, 102 | 72, 103 | 62, 104 | 42, 105 | 31, 106 | 0, 107 | 0, 108 | 0 109 | ] 110 | }, 111 | { 112 | "name":"Thursday", 113 | "data":[ 114 | 0, 115 | 0, 116 | 0, 117 | 0, 118 | 0, 119 | 0, 120 | 0, 121 | 0, 122 | 11, 123 | 18, 124 | 27, 125 | 36, 126 | 42, 127 | 46, 128 | 47, 129 | 51, 130 | 61, 131 | 72, 132 | 71, 133 | 53, 134 | 30, 135 | 0, 136 | 0, 137 | 0 138 | ] 139 | }, 140 | { 141 | "name":"Friday", 142 | "data":[ 143 | 0, 144 | 0, 145 | 0, 146 | 0, 147 | 0, 148 | 0, 149 | 0, 150 | 0, 151 | 13, 152 | 21, 153 | 30, 154 | 39, 155 | 46, 156 | 52, 157 | 56, 158 | 63, 159 | 70, 160 | 75, 161 | 70, 162 | 55, 163 | 34, 164 | 0, 165 | 0, 166 | 0 167 | ] 168 | }, 169 | { 170 | "name":"Saturday", 171 | "data":[ 172 | 0, 173 | 0, 174 | 0, 175 | 0, 176 | 0, 177 | 0, 178 | 0, 179 | 0, 180 | 15, 181 | 27, 182 | 41, 183 | 53, 184 | 60, 185 | 64, 186 | 70, 187 | 78, 188 | 85, 189 | 82, 190 | 66, 191 | 44, 192 | 32, 193 | 0, 194 | 0, 195 | 0 196 | ] 197 | }, 198 | { 199 | "name":"Sunday", 200 | "data":[ 201 | 0, 202 | 0, 203 | 0, 204 | 0, 205 | 0, 206 | 0, 207 | 0, 208 | 0, 209 | 10, 210 | 20, 211 | 35, 212 | 53, 213 | 68, 214 | 79, 215 | 86, 216 | 94, 217 | 100, 218 | 92, 219 | 71, 220 | 49, 221 | 35, 222 | 0, 223 | 0, 224 | 0 225 | ] 226 | } 227 | ], 228 | "time_spent":[ 229 | 20, 230 | 20 231 | ], 232 | "categories":[ 233 | "Supermarket", 234 | "Bakery", 235 | "Florist" 236 | ], 237 | "current_popularity": 25, 238 | "popular_times":[ 239 | [ 240 | 7, 241 | [ 242 | [ 243 | 6, 244 | 0, 245 | "", 246 | "", 247 | "6 a.m." 248 | ], 249 | [ 250 | 7, 251 | 0, 252 | "", 253 | "", 254 | "7 a.m." 255 | ], 256 | [ 257 | 8, 258 | 10, 259 | "Usually not busy", 260 | "", 261 | "8 a.m." 262 | ], 263 | [ 264 | 9, 265 | 20, 266 | "Usually not too busy", 267 | "", 268 | "9 a.m." 269 | ], 270 | [ 271 | 10, 272 | 35, 273 | "Usually not too busy", 274 | "", 275 | "10 a.m." 276 | ], 277 | [ 278 | 11, 279 | 53, 280 | "Usually a little busy", 281 | "", 282 | "11 a.m." 283 | ], 284 | [ 285 | 12, 286 | 68, 287 | "Usually a little busy", 288 | "", 289 | "12 p.m." 290 | ], 291 | [ 292 | 13, 293 | 79, 294 | "Usually a little busy", 295 | "", 296 | "1 p.m." 297 | ], 298 | [ 299 | 14, 300 | 86, 301 | "Usually as busy as it gets", 302 | "", 303 | "2 p.m." 304 | ], 305 | [ 306 | 15, 307 | 94, 308 | "Usually as busy as it gets", 309 | "", 310 | "3 p.m." 311 | ], 312 | [ 313 | 16, 314 | 100, 315 | "Usually as busy as it gets", 316 | "", 317 | "4 p.m." 318 | ], 319 | [ 320 | 17, 321 | 92, 322 | "Usually as busy as it gets", 323 | "", 324 | "5 p.m." 325 | ], 326 | [ 327 | 18, 328 | 71, 329 | "Usually a little busy", 330 | "", 331 | "6 p.m." 332 | ], 333 | [ 334 | 19, 335 | 49, 336 | "Usually not too busy", 337 | "", 338 | "7 p.m." 339 | ], 340 | [ 341 | 20, 342 | 35, 343 | "Usually not too busy", 344 | "", 345 | "8 p.m." 346 | ], 347 | [ 348 | 21, 349 | 0, 350 | "", 351 | "", 352 | "9 p.m." 353 | ], 354 | [ 355 | 22, 356 | 0, 357 | "", 358 | "", 359 | "10 p.m." 360 | ], 361 | [ 362 | 23, 363 | 0, 364 | "", 365 | "", 366 | "11 p.m." 367 | ] 368 | ], 369 | 0 370 | ], 371 | [ 372 | 1, 373 | [ 374 | [ 375 | 6, 376 | 0, 377 | "", 378 | "", 379 | "6 a.m." 380 | ], 381 | [ 382 | 7, 383 | 0, 384 | "", 385 | "", 386 | "7 a.m." 387 | ], 388 | [ 389 | 8, 390 | 11, 391 | "Usually not busy", 392 | "", 393 | "8 a.m." 394 | ], 395 | [ 396 | 9, 397 | 19, 398 | "Usually not busy", 399 | "", 400 | "9 a.m." 401 | ], 402 | [ 403 | 10, 404 | 28, 405 | "Usually not too busy", 406 | "", 407 | "10 a.m." 408 | ], 409 | [ 410 | 11, 411 | 36, 412 | "Usually not too busy", 413 | "", 414 | "11 a.m." 415 | ], 416 | [ 417 | 12, 418 | 42, 419 | "Usually not too busy", 420 | "", 421 | "12 p.m." 422 | ], 423 | [ 424 | 13, 425 | 46, 426 | "Usually not too busy", 427 | "", 428 | "1 p.m." 429 | ], 430 | [ 431 | 14, 432 | 51, 433 | "Usually a little busy", 434 | "", 435 | "2 p.m." 436 | ], 437 | [ 438 | 15, 439 | 64, 440 | "Usually a little busy", 441 | "", 442 | "3 p.m." 443 | ], 444 | [ 445 | 16, 446 | 82, 447 | "Usually as busy as it gets", 448 | "", 449 | "4 p.m." 450 | ], 451 | [ 452 | 17, 453 | 92, 454 | "Usually as busy as it gets", 455 | "", 456 | "5 p.m." 457 | ], 458 | [ 459 | 18, 460 | 81, 461 | "Usually as busy as it gets", 462 | "", 463 | "6 p.m." 464 | ], 465 | [ 466 | 19, 467 | 59, 468 | "Usually a little busy", 469 | "", 470 | "7 p.m." 471 | ], 472 | [ 473 | 20, 474 | 38, 475 | "Usually not too busy", 476 | "", 477 | "8 p.m." 478 | ], 479 | [ 480 | 21, 481 | 0, 482 | "", 483 | "", 484 | "9 p.m." 485 | ], 486 | [ 487 | 22, 488 | 0, 489 | "", 490 | "", 491 | "10 p.m." 492 | ], 493 | [ 494 | 23, 495 | 0, 496 | "", 497 | "", 498 | "11 p.m." 499 | ] 500 | ], 501 | 0 502 | ], 503 | [ 504 | 2, 505 | [ 506 | [ 507 | 6, 508 | 0, 509 | "", 510 | "", 511 | "6 a.m." 512 | ], 513 | [ 514 | 7, 515 | 0, 516 | "", 517 | "", 518 | "7 a.m." 519 | ], 520 | [ 521 | 8, 522 | 12, 523 | "Usually not busy", 524 | "", 525 | "8 a.m." 526 | ], 527 | [ 528 | 9, 529 | 22, 530 | "Usually not too busy", 531 | "", 532 | "9 a.m." 533 | ], 534 | [ 535 | 10, 536 | 34, 537 | "Usually not too busy", 538 | "", 539 | "10 a.m." 540 | ], 541 | [ 542 | 11, 543 | 46, 544 | "Usually not too busy", 545 | "", 546 | "11 a.m." 547 | ], 548 | [ 549 | 12, 550 | 53, 551 | "Usually a little busy", 552 | "", 553 | "12 p.m." 554 | ], 555 | [ 556 | 13, 557 | 54, 558 | "Usually a little busy", 559 | "", 560 | "1 p.m." 561 | ], 562 | [ 563 | 14, 564 | 51, 565 | "Usually a little busy", 566 | "", 567 | "2 p.m." 568 | ], 569 | [ 570 | 15, 571 | 54, 572 | "Usually a little busy", 573 | "", 574 | "3 p.m." 575 | ], 576 | [ 577 | 16, 578 | 70, 579 | "Usually a little busy", 580 | "", 581 | "4 p.m." 582 | ], 583 | [ 584 | 17, 585 | 84, 586 | "Usually as busy as it gets", 587 | "", 588 | "5 p.m." 589 | ], 590 | [ 591 | 18, 592 | 75, 593 | "Usually a little busy", 594 | "", 595 | "6 p.m." 596 | ], 597 | [ 598 | 19, 599 | 47, 600 | "Usually not too busy", 601 | "", 602 | "7 p.m." 603 | ], 604 | [ 605 | 20, 606 | 26, 607 | "Usually not too busy", 608 | "", 609 | "8 p.m." 610 | ], 611 | [ 612 | 21, 613 | 0, 614 | "", 615 | "", 616 | "9 p.m." 617 | ], 618 | [ 619 | 22, 620 | 0, 621 | "", 622 | "", 623 | "10 p.m." 624 | ], 625 | [ 626 | 23, 627 | 0, 628 | "", 629 | "", 630 | "11 p.m." 631 | ] 632 | ], 633 | 0 634 | ], 635 | [ 636 | 3, 637 | [ 638 | [ 639 | 6, 640 | 0, 641 | "", 642 | "", 643 | "6 a.m." 644 | ], 645 | [ 646 | 7, 647 | 0, 648 | "", 649 | "", 650 | "7 a.m." 651 | ], 652 | [ 653 | 8, 654 | 9, 655 | "Usually not busy", 656 | "", 657 | "8 a.m." 658 | ], 659 | [ 660 | 9, 661 | 16, 662 | "Usually not busy", 663 | "", 664 | "9 a.m." 665 | ], 666 | [ 667 | 10, 668 | 25, 669 | "Usually not too busy", 670 | "", 671 | "10 a.m." 672 | ], 673 | [ 674 | 11, 675 | 33, 676 | "Usually not too busy", 677 | "", 678 | "11 a.m." 679 | ], 680 | [ 681 | 12, 682 | 37, 683 | "Usually not too busy", 684 | "", 685 | "12 p.m." 686 | ], 687 | [ 688 | 13, 689 | 36, 690 | "Usually not too busy", 691 | "", 692 | "1 p.m." 693 | ], 694 | [ 695 | 14, 696 | 37, 697 | "Usually not too busy", 698 | "", 699 | "2 p.m." 700 | ], 701 | [ 702 | 15, 703 | 46, 704 | "Usually not too busy", 705 | "", 706 | "3 p.m." 707 | ], 708 | [ 709 | 16, 710 | 62, 711 | "Usually a little busy", 712 | "", 713 | "4 p.m." 714 | ], 715 | [ 716 | 17, 717 | 72, 718 | "Usually a little busy", 719 | "", 720 | "5 p.m." 721 | ], 722 | [ 723 | 18, 724 | 62, 725 | "Usually a little busy", 726 | "", 727 | "6 p.m." 728 | ], 729 | [ 730 | 19, 731 | 42, 732 | "Usually not too busy", 733 | "", 734 | "7 p.m." 735 | ], 736 | [ 737 | 20, 738 | 31, 739 | "Usually not too busy", 740 | "", 741 | "8 p.m." 742 | ], 743 | [ 744 | 21, 745 | 0, 746 | "", 747 | "", 748 | "9 p.m." 749 | ], 750 | [ 751 | 22, 752 | 0, 753 | "", 754 | "", 755 | "10 p.m." 756 | ], 757 | [ 758 | 23, 759 | 0, 760 | "", 761 | "", 762 | "11 p.m." 763 | ] 764 | ], 765 | 0 766 | ], 767 | [ 768 | 4, 769 | [ 770 | [ 771 | 6, 772 | 0, 773 | "", 774 | "", 775 | "6 a.m." 776 | ], 777 | [ 778 | 7, 779 | 0, 780 | "", 781 | "", 782 | "7 a.m." 783 | ], 784 | [ 785 | 8, 786 | 11, 787 | "Usually not busy", 788 | "", 789 | "8 a.m." 790 | ], 791 | [ 792 | 9, 793 | 18, 794 | "Usually not busy", 795 | "", 796 | "9 a.m." 797 | ], 798 | [ 799 | 10, 800 | 27, 801 | "Usually not too busy", 802 | "", 803 | "10 a.m." 804 | ], 805 | [ 806 | 11, 807 | 36, 808 | "Usually not too busy", 809 | "", 810 | "11 a.m." 811 | ], 812 | [ 813 | 12, 814 | 42, 815 | "Usually not too busy", 816 | "", 817 | "12 p.m." 818 | ], 819 | [ 820 | 13, 821 | 46, 822 | "Usually not too busy", 823 | "", 824 | "1 p.m." 825 | ], 826 | [ 827 | 14, 828 | 47, 829 | "Usually not too busy", 830 | "", 831 | "2 p.m." 832 | ], 833 | [ 834 | 15, 835 | 51, 836 | "Usually a little busy", 837 | "", 838 | "3 p.m." 839 | ], 840 | [ 841 | 16, 842 | 61, 843 | "Usually a little busy", 844 | "", 845 | "4 p.m." 846 | ], 847 | [ 848 | 17, 849 | 72, 850 | "Usually a little busy", 851 | "", 852 | "5 p.m." 853 | ], 854 | [ 855 | 18, 856 | 71, 857 | "Usually a little busy", 858 | "", 859 | "6 p.m." 860 | ], 861 | [ 862 | 19, 863 | 53, 864 | "Usually a little busy", 865 | "", 866 | "7 p.m." 867 | ], 868 | [ 869 | 20, 870 | 30, 871 | "Usually not too busy", 872 | "", 873 | "8 p.m." 874 | ], 875 | [ 876 | 21, 877 | 0, 878 | "", 879 | "", 880 | "9 p.m." 881 | ], 882 | [ 883 | 22, 884 | 0, 885 | "", 886 | "", 887 | "10 p.m." 888 | ], 889 | [ 890 | 23, 891 | 0, 892 | "", 893 | "", 894 | "11 p.m." 895 | ] 896 | ], 897 | 0 898 | ], 899 | [ 900 | 5, 901 | [ 902 | [ 903 | 6, 904 | 0, 905 | "", 906 | "", 907 | "6 a.m." 908 | ], 909 | [ 910 | 7, 911 | 0, 912 | "", 913 | "", 914 | "7 a.m." 915 | ], 916 | [ 917 | 8, 918 | 13, 919 | "Usually not busy", 920 | "", 921 | "8 a.m." 922 | ], 923 | [ 924 | 9, 925 | 21, 926 | "Usually not too busy", 927 | "", 928 | "9 a.m." 929 | ], 930 | [ 931 | 10, 932 | 30, 933 | "Usually not too busy", 934 | "", 935 | "10 a.m." 936 | ], 937 | [ 938 | 11, 939 | 39, 940 | "Usually not too busy", 941 | "", 942 | "11 a.m." 943 | ], 944 | [ 945 | 12, 946 | 46, 947 | "Usually not too busy", 948 | "", 949 | "12 p.m." 950 | ], 951 | [ 952 | 13, 953 | 52, 954 | "Usually a little busy", 955 | "", 956 | "1 p.m." 957 | ], 958 | [ 959 | 14, 960 | 56, 961 | "Usually a little busy", 962 | "", 963 | "2 p.m." 964 | ], 965 | [ 966 | 15, 967 | 63, 968 | "Usually a little busy", 969 | "", 970 | "3 p.m." 971 | ], 972 | [ 973 | 16, 974 | 70, 975 | "Usually a little busy", 976 | "", 977 | "4 p.m." 978 | ], 979 | [ 980 | 17, 981 | 75, 982 | "Usually a little busy", 983 | "", 984 | "5 p.m." 985 | ], 986 | [ 987 | 18, 988 | 70, 989 | "Usually a little busy", 990 | "", 991 | "6 p.m." 992 | ], 993 | [ 994 | 19, 995 | 55, 996 | "Usually a little busy", 997 | "", 998 | "7 p.m." 999 | ], 1000 | [ 1001 | 20, 1002 | 34, 1003 | "Usually not too busy", 1004 | "", 1005 | "8 p.m." 1006 | ], 1007 | [ 1008 | 21, 1009 | 0, 1010 | "", 1011 | "", 1012 | "9 p.m." 1013 | ], 1014 | [ 1015 | 22, 1016 | 0, 1017 | "", 1018 | "", 1019 | "10 p.m." 1020 | ], 1021 | [ 1022 | 23, 1023 | 0, 1024 | "", 1025 | "", 1026 | "11 p.m." 1027 | ] 1028 | ], 1029 | 0 1030 | ], 1031 | [ 1032 | 6, 1033 | [ 1034 | [ 1035 | 6, 1036 | 0, 1037 | "", 1038 | "", 1039 | "6 a.m." 1040 | ], 1041 | [ 1042 | 7, 1043 | 0, 1044 | "", 1045 | "", 1046 | "7 a.m." 1047 | ], 1048 | [ 1049 | 8, 1050 | 15, 1051 | "Usually not busy", 1052 | "", 1053 | "8 a.m." 1054 | ], 1055 | [ 1056 | 9, 1057 | 27, 1058 | "Usually not too busy", 1059 | "", 1060 | "9 a.m." 1061 | ], 1062 | [ 1063 | 10, 1064 | 41, 1065 | "Usually not too busy", 1066 | "", 1067 | "10 a.m." 1068 | ], 1069 | [ 1070 | 11, 1071 | 53, 1072 | "Usually a little busy", 1073 | "", 1074 | "11 a.m." 1075 | ], 1076 | [ 1077 | 12, 1078 | 60, 1079 | "Usually a little busy", 1080 | "", 1081 | "12 p.m." 1082 | ], 1083 | [ 1084 | 13, 1085 | 64, 1086 | "Usually a little busy", 1087 | "", 1088 | "1 p.m." 1089 | ], 1090 | [ 1091 | 14, 1092 | 70, 1093 | "Usually a little busy", 1094 | "", 1095 | "2 p.m." 1096 | ], 1097 | [ 1098 | 15, 1099 | 78, 1100 | "Usually a little busy", 1101 | "", 1102 | "3 p.m." 1103 | ], 1104 | [ 1105 | 16, 1106 | 85, 1107 | "Usually as busy as it gets", 1108 | "", 1109 | "4 p.m." 1110 | ], 1111 | [ 1112 | 17, 1113 | 82, 1114 | "Usually as busy as it gets", 1115 | "", 1116 | "5 p.m." 1117 | ], 1118 | [ 1119 | 18, 1120 | 66, 1121 | "Usually a little busy", 1122 | "", 1123 | "6 p.m." 1124 | ], 1125 | [ 1126 | 19, 1127 | 44, 1128 | "Usually not too busy", 1129 | "", 1130 | "7 p.m." 1131 | ], 1132 | [ 1133 | 20, 1134 | 32, 1135 | "Usually not too busy", 1136 | "", 1137 | "8 p.m." 1138 | ], 1139 | [ 1140 | 21, 1141 | 0, 1142 | "", 1143 | "", 1144 | "9 p.m." 1145 | ], 1146 | [ 1147 | 22, 1148 | 0, 1149 | "", 1150 | "", 1151 | "10 p.m." 1152 | ], 1153 | [ 1154 | 23, 1155 | 0, 1156 | "", 1157 | "", 1158 | "11 p.m." 1159 | ] 1160 | ], 1161 | 0 1162 | ] 1163 | ] 1164 | } 1165 | -------------------------------------------------------------------------------- /example_output(get_populartimes_by_address).json: -------------------------------------------------------------------------------- 1 | { 2 | "rating":3.8, 3 | "rating_n":244, 4 | "populartimes":[ 5 | { 6 | "name":"Monday", 7 | "data":[ 8 | 0, 9 | 0, 10 | 0, 11 | 0, 12 | 0, 13 | 0, 14 | 0, 15 | 0, 16 | 0, 17 | 21, 18 | 35, 19 | 51, 20 | 63, 21 | 69, 22 | 68, 23 | 63, 24 | 58, 25 | 55, 26 | 52, 27 | 45, 28 | 33, 29 | 21, 30 | 0, 31 | 0 32 | ] 33 | }, 34 | { 35 | "name":"Tuesday", 36 | "data":[ 37 | 0, 38 | 0, 39 | 0, 40 | 0, 41 | 0, 42 | 0, 43 | 0, 44 | 0, 45 | 0, 46 | 23, 47 | 42, 48 | 53, 49 | 53, 50 | 51, 51 | 55, 52 | 62, 53 | 64, 54 | 60, 55 | 54, 56 | 44, 57 | 30, 58 | 15, 59 | 0, 60 | 0 61 | ] 62 | }, 63 | { 64 | "name":"Wednesday", 65 | "data":[ 66 | 0, 67 | 0, 68 | 0, 69 | 0, 70 | 0, 71 | 0, 72 | 0, 73 | 0, 74 | 0, 75 | 13, 76 | 25, 77 | 39, 78 | 50, 79 | 55, 80 | 56, 81 | 58, 82 | 62, 83 | 64, 84 | 60, 85 | 49, 86 | 34, 87 | 21, 88 | 0, 89 | 0 90 | ] 91 | }, 92 | { 93 | "name":"Thursday", 94 | "data":[ 95 | 0, 96 | 0, 97 | 0, 98 | 0, 99 | 0, 100 | 0, 101 | 0, 102 | 0, 103 | 0, 104 | 15, 105 | 27, 106 | 39, 107 | 50, 108 | 56, 109 | 61, 110 | 66, 111 | 71, 112 | 72, 113 | 64, 114 | 48, 115 | 30, 116 | 15, 117 | 0, 118 | 0 119 | ] 120 | }, 121 | { 122 | "name":"Friday", 123 | "data":[ 124 | 0, 125 | 0, 126 | 0, 127 | 0, 128 | 0, 129 | 0, 130 | 0, 131 | 0, 132 | 0, 133 | 19, 134 | 32, 135 | 45, 136 | 53, 137 | 57, 138 | 63, 139 | 73, 140 | 83, 141 | 86, 142 | 77, 143 | 60, 144 | 40, 145 | 22, 146 | 0, 147 | 0 148 | ] 149 | }, 150 | { 151 | "name":"Saturday", 152 | "data":[ 153 | 0, 154 | 0, 155 | 0, 156 | 0, 157 | 0, 158 | 0, 159 | 0, 160 | 0, 161 | 0, 162 | 18, 163 | 31, 164 | 48, 165 | 67, 166 | 82, 167 | 92, 168 | 92, 169 | 84, 170 | 71, 171 | 57, 172 | 46, 173 | 34, 174 | 21, 175 | 0, 176 | 0 177 | ] 178 | }, 179 | { 180 | "name":"Sunday", 181 | "data":[ 182 | 0, 183 | 0, 184 | 0, 185 | 0, 186 | 0, 187 | 0, 188 | 0, 189 | 0, 190 | 0, 191 | 18, 192 | 31, 193 | 48, 194 | 67, 195 | 84, 196 | 96, 197 | 100, 198 | 94, 199 | 81, 200 | 63, 201 | 44, 202 | 28, 203 | 16, 204 | 0, 205 | 0 206 | ] 207 | } 208 | ], 209 | "time_spent":[ 210 | 20, 211 | 20 212 | ], 213 | "name":"H-Mart Dunbar", 214 | "place_id":"ChIJ17BpxUFzhlQR3YxY0OE4BiA", 215 | "address":"5557 Dunbar St, Vancouver, BC V6N 1W5", 216 | "coordinates":{ 217 | "lat":49.2359526, 218 | "lng":-123.185576 219 | }, 220 | "categories":[ 221 | "Korean grocery store", 222 | "Asian grocery store", 223 | "Supermarket" 224 | ], 225 | "place_types":[ 226 | [ 227 | "korean_grocery_store" 228 | ], 229 | [ 230 | "asian_grocery_store" 231 | ], 232 | [ 233 | "supermarket" 234 | ] 235 | ], 236 | "current_popularity": 32, 237 | "popular_times":[ 238 | [ 239 | 7, 240 | [ 241 | [ 242 | 6, 243 | 0, 244 | "", 245 | "", 246 | "6 a.m." 247 | ], 248 | [ 249 | 7, 250 | 0, 251 | "", 252 | "", 253 | "7 a.m." 254 | ], 255 | [ 256 | 8, 257 | 0, 258 | "", 259 | "", 260 | "8 a.m." 261 | ], 262 | [ 263 | 9, 264 | 18, 265 | "Usually not busy", 266 | "", 267 | "9 a.m." 268 | ], 269 | [ 270 | 10, 271 | 31, 272 | "Usually not too busy", 273 | "", 274 | "10 a.m." 275 | ], 276 | [ 277 | 11, 278 | 48, 279 | "Usually not too busy", 280 | "", 281 | "11 a.m." 282 | ], 283 | [ 284 | 12, 285 | 67, 286 | "Usually a little busy", 287 | "", 288 | "12 p.m." 289 | ], 290 | [ 291 | 13, 292 | 84, 293 | "Usually as busy as it gets", 294 | "", 295 | "1 p.m." 296 | ], 297 | [ 298 | 14, 299 | 96, 300 | "Usually as busy as it gets", 301 | "", 302 | "2 p.m." 303 | ], 304 | [ 305 | 15, 306 | 100, 307 | "Usually as busy as it gets", 308 | "", 309 | "3 p.m." 310 | ], 311 | [ 312 | 16, 313 | 94, 314 | "Usually as busy as it gets", 315 | "", 316 | "4 p.m." 317 | ], 318 | [ 319 | 17, 320 | 81, 321 | "Usually as busy as it gets", 322 | "", 323 | "5 p.m." 324 | ], 325 | [ 326 | 18, 327 | 63, 328 | "Usually a little busy", 329 | "", 330 | "6 p.m." 331 | ], 332 | [ 333 | 19, 334 | 44, 335 | "Usually not too busy", 336 | "", 337 | "7 p.m." 338 | ], 339 | [ 340 | 20, 341 | 28, 342 | "Usually not too busy", 343 | "", 344 | "8 p.m." 345 | ], 346 | [ 347 | 21, 348 | 16, 349 | "Usually not busy", 350 | "", 351 | "9 p.m." 352 | ], 353 | [ 354 | 22, 355 | 0, 356 | "", 357 | "", 358 | "10 p.m." 359 | ], 360 | [ 361 | 23, 362 | 0, 363 | "", 364 | "", 365 | "11 p.m." 366 | ] 367 | ], 368 | 0 369 | ], 370 | [ 371 | 1, 372 | [ 373 | [ 374 | 6, 375 | 0, 376 | "", 377 | "", 378 | "6 a.m." 379 | ], 380 | [ 381 | 7, 382 | 0, 383 | "", 384 | "", 385 | "7 a.m." 386 | ], 387 | [ 388 | 8, 389 | 0, 390 | "", 391 | "", 392 | "8 a.m." 393 | ], 394 | [ 395 | 9, 396 | 21, 397 | "Usually not too busy", 398 | "", 399 | "9 a.m." 400 | ], 401 | [ 402 | 10, 403 | 35, 404 | "Usually not too busy", 405 | "", 406 | "10 a.m." 407 | ], 408 | [ 409 | 11, 410 | 51, 411 | "Usually a little busy", 412 | "", 413 | "11 a.m." 414 | ], 415 | [ 416 | 12, 417 | 63, 418 | "Usually a little busy", 419 | "", 420 | "12 p.m." 421 | ], 422 | [ 423 | 13, 424 | 69, 425 | "Usually a little busy", 426 | "", 427 | "1 p.m." 428 | ], 429 | [ 430 | 14, 431 | 68, 432 | "Usually a little busy", 433 | "", 434 | "2 p.m." 435 | ], 436 | [ 437 | 15, 438 | 63, 439 | "Usually a little busy", 440 | "", 441 | "3 p.m." 442 | ], 443 | [ 444 | 16, 445 | 58, 446 | "Usually a little busy", 447 | "", 448 | "4 p.m." 449 | ], 450 | [ 451 | 17, 452 | 55, 453 | "Usually a little busy", 454 | "", 455 | "5 p.m." 456 | ], 457 | [ 458 | 18, 459 | 52, 460 | "Usually a little busy", 461 | "", 462 | "6 p.m." 463 | ], 464 | [ 465 | 19, 466 | 45, 467 | "Usually not too busy", 468 | "", 469 | "7 p.m." 470 | ], 471 | [ 472 | 20, 473 | 33, 474 | "Usually not too busy", 475 | "", 476 | "8 p.m." 477 | ], 478 | [ 479 | 21, 480 | 21, 481 | "Usually not too busy", 482 | "", 483 | "9 p.m." 484 | ], 485 | [ 486 | 22, 487 | 0, 488 | "", 489 | "", 490 | "10 p.m." 491 | ], 492 | [ 493 | 23, 494 | 0, 495 | "", 496 | "", 497 | "11 p.m." 498 | ] 499 | ], 500 | 0 501 | ], 502 | [ 503 | 2, 504 | [ 505 | [ 506 | 6, 507 | 0, 508 | "", 509 | "", 510 | "6 a.m." 511 | ], 512 | [ 513 | 7, 514 | 0, 515 | "", 516 | "", 517 | "7 a.m." 518 | ], 519 | [ 520 | 8, 521 | 0, 522 | "", 523 | "", 524 | "8 a.m." 525 | ], 526 | [ 527 | 9, 528 | 23, 529 | "Usually not too busy", 530 | "", 531 | "9 a.m." 532 | ], 533 | [ 534 | 10, 535 | 42, 536 | "Usually not too busy", 537 | "", 538 | "10 a.m." 539 | ], 540 | [ 541 | 11, 542 | 53, 543 | "Usually a little busy", 544 | "", 545 | "11 a.m." 546 | ], 547 | [ 548 | 12, 549 | 53, 550 | "Usually a little busy", 551 | "", 552 | "12 p.m." 553 | ], 554 | [ 555 | 13, 556 | 51, 557 | "Usually a little busy", 558 | "", 559 | "1 p.m." 560 | ], 561 | [ 562 | 14, 563 | 55, 564 | "Usually a little busy", 565 | "", 566 | "2 p.m." 567 | ], 568 | [ 569 | 15, 570 | 62, 571 | "Usually a little busy", 572 | "", 573 | "3 p.m." 574 | ], 575 | [ 576 | 16, 577 | 64, 578 | "Usually a little busy", 579 | "", 580 | "4 p.m." 581 | ], 582 | [ 583 | 17, 584 | 60, 585 | "Usually a little busy", 586 | "", 587 | "5 p.m." 588 | ], 589 | [ 590 | 18, 591 | 54, 592 | "Usually a little busy", 593 | "", 594 | "6 p.m." 595 | ], 596 | [ 597 | 19, 598 | 44, 599 | "Usually not too busy", 600 | "", 601 | "7 p.m." 602 | ], 603 | [ 604 | 20, 605 | 30, 606 | "Usually not too busy", 607 | "", 608 | "8 p.m." 609 | ], 610 | [ 611 | 21, 612 | 15, 613 | "Usually not busy", 614 | "", 615 | "9 p.m." 616 | ], 617 | [ 618 | 22, 619 | 0, 620 | "", 621 | "", 622 | "10 p.m." 623 | ], 624 | [ 625 | 23, 626 | 0, 627 | "", 628 | "", 629 | "11 p.m." 630 | ] 631 | ], 632 | 0 633 | ], 634 | [ 635 | 3, 636 | [ 637 | [ 638 | 6, 639 | 0, 640 | "", 641 | "", 642 | "6 a.m." 643 | ], 644 | [ 645 | 7, 646 | 0, 647 | "", 648 | "", 649 | "7 a.m." 650 | ], 651 | [ 652 | 8, 653 | 0, 654 | "", 655 | "", 656 | "8 a.m." 657 | ], 658 | [ 659 | 9, 660 | 13, 661 | "Usually not busy", 662 | "", 663 | "9 a.m." 664 | ], 665 | [ 666 | 10, 667 | 25, 668 | "Usually not too busy", 669 | "", 670 | "10 a.m." 671 | ], 672 | [ 673 | 11, 674 | 39, 675 | "Usually not too busy", 676 | "", 677 | "11 a.m." 678 | ], 679 | [ 680 | 12, 681 | 50, 682 | "Usually a little busy", 683 | "", 684 | "12 p.m." 685 | ], 686 | [ 687 | 13, 688 | 55, 689 | "Usually a little busy", 690 | "", 691 | "1 p.m." 692 | ], 693 | [ 694 | 14, 695 | 56, 696 | "Usually a little busy", 697 | "", 698 | "2 p.m." 699 | ], 700 | [ 701 | 15, 702 | 58, 703 | "Usually a little busy", 704 | "", 705 | "3 p.m." 706 | ], 707 | [ 708 | 16, 709 | 62, 710 | "Usually a little busy", 711 | "", 712 | "4 p.m." 713 | ], 714 | [ 715 | 17, 716 | 64, 717 | "Usually a little busy", 718 | "", 719 | "5 p.m." 720 | ], 721 | [ 722 | 18, 723 | 60, 724 | "Usually a little busy", 725 | "", 726 | "6 p.m." 727 | ], 728 | [ 729 | 19, 730 | 49, 731 | "Usually not too busy", 732 | "", 733 | "7 p.m." 734 | ], 735 | [ 736 | 20, 737 | 34, 738 | "Usually not too busy", 739 | "", 740 | "8 p.m." 741 | ], 742 | [ 743 | 21, 744 | 21, 745 | "Usually not too busy", 746 | "", 747 | "9 p.m." 748 | ], 749 | [ 750 | 22, 751 | 0, 752 | "", 753 | "", 754 | "10 p.m." 755 | ], 756 | [ 757 | 23, 758 | 0, 759 | "", 760 | "", 761 | "11 p.m." 762 | ] 763 | ], 764 | 0 765 | ], 766 | [ 767 | 4, 768 | [ 769 | [ 770 | 6, 771 | 0, 772 | "", 773 | "", 774 | "6 a.m." 775 | ], 776 | [ 777 | 7, 778 | 0, 779 | "", 780 | "", 781 | "7 a.m." 782 | ], 783 | [ 784 | 8, 785 | 0, 786 | "", 787 | "", 788 | "8 a.m." 789 | ], 790 | [ 791 | 9, 792 | 15, 793 | "Usually not busy", 794 | "", 795 | "9 a.m." 796 | ], 797 | [ 798 | 10, 799 | 27, 800 | "Usually not too busy", 801 | "", 802 | "10 a.m." 803 | ], 804 | [ 805 | 11, 806 | 39, 807 | "Usually not too busy", 808 | "", 809 | "11 a.m." 810 | ], 811 | [ 812 | 12, 813 | 50, 814 | "Usually not too busy", 815 | "", 816 | "12 p.m." 817 | ], 818 | [ 819 | 13, 820 | 56, 821 | "Usually a little busy", 822 | "", 823 | "1 p.m." 824 | ], 825 | [ 826 | 14, 827 | 61, 828 | "Usually a little busy", 829 | "", 830 | "2 p.m." 831 | ], 832 | [ 833 | 15, 834 | 66, 835 | "Usually a little busy", 836 | "", 837 | "3 p.m." 838 | ], 839 | [ 840 | 16, 841 | 71, 842 | "Usually a little busy", 843 | "", 844 | "4 p.m." 845 | ], 846 | [ 847 | 17, 848 | 72, 849 | "Usually a little busy", 850 | "", 851 | "5 p.m." 852 | ], 853 | [ 854 | 18, 855 | 64, 856 | "Usually a little busy", 857 | "", 858 | "6 p.m." 859 | ], 860 | [ 861 | 19, 862 | 48, 863 | "Usually not too busy", 864 | "", 865 | "7 p.m." 866 | ], 867 | [ 868 | 20, 869 | 30, 870 | "Usually not too busy", 871 | "", 872 | "8 p.m." 873 | ], 874 | [ 875 | 21, 876 | 15, 877 | "Usually not busy", 878 | "", 879 | "9 p.m." 880 | ], 881 | [ 882 | 22, 883 | 0, 884 | "", 885 | "", 886 | "10 p.m." 887 | ], 888 | [ 889 | 23, 890 | 0, 891 | "", 892 | "", 893 | "11 p.m." 894 | ] 895 | ], 896 | 0 897 | ], 898 | [ 899 | 5, 900 | [ 901 | [ 902 | 6, 903 | 0, 904 | "", 905 | "", 906 | "6 a.m." 907 | ], 908 | [ 909 | 7, 910 | 0, 911 | "", 912 | "", 913 | "7 a.m." 914 | ], 915 | [ 916 | 8, 917 | 0, 918 | "", 919 | "", 920 | "8 a.m." 921 | ], 922 | [ 923 | 9, 924 | 19, 925 | "Usually not busy", 926 | "", 927 | "9 a.m." 928 | ], 929 | [ 930 | 10, 931 | 32, 932 | "Usually not too busy", 933 | "", 934 | "10 a.m." 935 | ], 936 | [ 937 | 11, 938 | 45, 939 | "Usually not too busy", 940 | "", 941 | "11 a.m." 942 | ], 943 | [ 944 | 12, 945 | 53, 946 | "Usually a little busy", 947 | "", 948 | "12 p.m." 949 | ], 950 | [ 951 | 13, 952 | 57, 953 | "Usually a little busy", 954 | "", 955 | "1 p.m." 956 | ], 957 | [ 958 | 14, 959 | 63, 960 | "Usually a little busy", 961 | "", 962 | "2 p.m." 963 | ], 964 | [ 965 | 15, 966 | 73, 967 | "Usually a little busy", 968 | "", 969 | "3 p.m." 970 | ], 971 | [ 972 | 16, 973 | 83, 974 | "Usually as busy as it gets", 975 | "", 976 | "4 p.m." 977 | ], 978 | [ 979 | 17, 980 | 86, 981 | "Usually as busy as it gets", 982 | "", 983 | "5 p.m." 984 | ], 985 | [ 986 | 18, 987 | 77, 988 | "Usually a little busy", 989 | "", 990 | "6 p.m." 991 | ], 992 | [ 993 | 19, 994 | 60, 995 | "Usually a little busy", 996 | "", 997 | "7 p.m." 998 | ], 999 | [ 1000 | 20, 1001 | 40, 1002 | "Usually not too busy", 1003 | "", 1004 | "8 p.m." 1005 | ], 1006 | [ 1007 | 21, 1008 | 22, 1009 | "Usually not too busy", 1010 | "", 1011 | "9 p.m." 1012 | ], 1013 | [ 1014 | 22, 1015 | 0, 1016 | "", 1017 | "", 1018 | "10 p.m." 1019 | ], 1020 | [ 1021 | 23, 1022 | 0, 1023 | "", 1024 | "", 1025 | "11 p.m." 1026 | ] 1027 | ], 1028 | 0 1029 | ], 1030 | [ 1031 | 6, 1032 | [ 1033 | [ 1034 | 6, 1035 | 0, 1036 | "", 1037 | "", 1038 | "6 a.m." 1039 | ], 1040 | [ 1041 | 7, 1042 | 0, 1043 | "", 1044 | "", 1045 | "7 a.m." 1046 | ], 1047 | [ 1048 | 8, 1049 | 0, 1050 | "", 1051 | "", 1052 | "8 a.m." 1053 | ], 1054 | [ 1055 | 9, 1056 | 18, 1057 | "Usually not busy", 1058 | "", 1059 | "9 a.m." 1060 | ], 1061 | [ 1062 | 10, 1063 | 31, 1064 | "Usually not too busy", 1065 | "", 1066 | "10 a.m." 1067 | ], 1068 | [ 1069 | 11, 1070 | 48, 1071 | "Usually not too busy", 1072 | "", 1073 | "11 a.m." 1074 | ], 1075 | [ 1076 | 12, 1077 | 67, 1078 | "Usually a little busy", 1079 | "", 1080 | "12 p.m." 1081 | ], 1082 | [ 1083 | 13, 1084 | 82, 1085 | "Usually as busy as it gets", 1086 | "", 1087 | "1 p.m." 1088 | ], 1089 | [ 1090 | 14, 1091 | 92, 1092 | "Usually as busy as it gets", 1093 | "", 1094 | "2 p.m." 1095 | ], 1096 | [ 1097 | 15, 1098 | 92, 1099 | "Usually as busy as it gets", 1100 | "", 1101 | "3 p.m." 1102 | ], 1103 | [ 1104 | 16, 1105 | 84, 1106 | "Usually as busy as it gets", 1107 | "", 1108 | "4 p.m." 1109 | ], 1110 | [ 1111 | 17, 1112 | 71, 1113 | "Usually a little busy", 1114 | "", 1115 | "5 p.m." 1116 | ], 1117 | [ 1118 | 18, 1119 | 57, 1120 | "Usually a little busy", 1121 | "", 1122 | "6 p.m." 1123 | ], 1124 | [ 1125 | 19, 1126 | 46, 1127 | "Usually not too busy", 1128 | "", 1129 | "7 p.m." 1130 | ], 1131 | [ 1132 | 20, 1133 | 34, 1134 | "Usually not too busy", 1135 | "", 1136 | "8 p.m." 1137 | ], 1138 | [ 1139 | 21, 1140 | 21, 1141 | "Usually not too busy", 1142 | "", 1143 | "9 p.m." 1144 | ], 1145 | [ 1146 | 22, 1147 | 0, 1148 | "", 1149 | "", 1150 | "10 p.m." 1151 | ], 1152 | [ 1153 | 23, 1154 | 0, 1155 | "", 1156 | "", 1157 | "11 p.m." 1158 | ] 1159 | ], 1160 | 0 1161 | ] 1162 | ] 1163 | } 1164 | --------------------------------------------------------------------------------