TL;DR
- Analyzes footfall near the British Museum using Google Maps βPopular Timesβ.
- Flow: collect nearby businesses, extract hourly popularity, average by hour, visualize with heatmaps.
- Stack: Python (
requests,pandas),seaborn/matplotlib; sample code + images included. - Categories covered: restaurants, cafΓ©s, galleries, pubs, theatres (cafΓ©s peak at lunch).
- Powered by Scrapingdogβs Google Maps API.
Google Maps is a great storehouse of information for businesses looking to extract phone numbers for lead generation, customer reviews for sentimental analysis, images for real-estate mapping, and footfall for analyzing the peak hours of certain places at a particular time.
In this article, we will scrape and analyze Google Mapsβ Popular Times data for businesses in London during peak hours and visualize it using graphs and heat maps.
Process
In our first step, we would be scraping the list of businesses from Google Maps in a particular area of interest.
The area around the British Museum looks good for exploring the businesses of London. Here is the list of companies we are going to analyze:
Restaurants β Indian Restaurants, French Bistros
CafΓ©s
Art Galleries and Museums
Pubs and Bars
Theatres and Entertainment Venues
We will get the search listings for the above businesses by using our Google Maps API.
You can refer to thisΒ documentationΒ for more information on using Scrapingdog Google Maps API.
I am sharing a sample code of how I collected the businesses for each type.
1import requests2import json3import seaborn as sns4import matplotlib.pyplot as plt5import pandas as pd6 7API_KEY = "APIKEY"8BASE_URL = "https://api.scrapingdog.com/google_maps"9PLACES_URL = "https://api.scrapingdog.com/google_maps/places"10 11# Fetch business data12def get_data():13 response = requests.get(f"{BASE_URL}?ll=@51.5181901,-0.1525433,5z&query=Indian Restaurants Near The British Museum&api_key={API_KEY}")14 data = response.json()15 businesses = []16 17 for place in data.get("search_results", []):18 title = place.get("title")19 place_id = place.get("place_id")20 21 if not title or not place_id:22 continue23 24 try:25 place_response = requests.get(f"{PLACES_URL}?place_id={place_id}&api_key={API_KEY}")26 place_data = place_response.json()27 popularity_scores = place_data.get("place_results", {}).get("popular_times", {}).get("graph_results", {})28 except Exception as e:29 print(f"Error fetching details for {title}: {e}")30 popularity_scores = {}31 32 businesses.append({33 "title": title,34 "place_id": place_id,35 "popularity_scores": popularity_scores36 })37 38 # Save data to JSON39 with open("businessLists.json", "w") as f:40 json.dump(businesses, f, indent=2)41 print("File written successfully!")42 43 return businessesStep by Step Process
Importing Required Libraries
requestsΒ β Used to make HTTP requests to the API.jsonΒ β Handles JSON data (parsing & saving).seabornΒ &Βmatplotlib.pyplotΒ β Used for visualization (not used in this function but likely needed for later).pandasΒ β Used for data analysis (not used in this function but likely needed for later).
Setting API Constants
1API_KEY = "APIKEY"2BASE_URL = "https://api.scrapingdog.com/google_maps"3PLACES_URL = "https://api.scrapingdog.com/google_maps/places"API_KEYΒ β Placeholder for the API key (should be replaced with a real key).BASE_URLΒ β Endpoint for Google Maps search results.PLACES_URLΒ β Endpoint for fetching details of a specific place.
Making an API Request for Nearby Restaurants and Extracting the Search Results
1response = requests.get(f"{BASE_URL}?ll=@51.5181901,-0.1525433,5z&query=Indian Restaurants Near The British Museum&api_key={API_KEY}")2data = response.json()3 4businesses = []5for place in data.get("search_results", []):6 title = place.get("title")7 place_id = place.get("place_id")Sends aΒ GET requestΒ toΒ
BASE_URL.Initializes an empty listΒ
businessesΒ to store extracted data.Loops through theΒ search resultsΒ (list of restaurants) and extractsΒ
title(name of the restaurant) andΒplace_id(unique identifier for each restaurant).
Fetching Details for Each Restaurant
1try:2 place_response = requests.get(f"{PLACES_URL}?place_id={place_id}&api_key={API_KEY}")3 place_data = place_response.json()4 popularity_scores = place_data.get("place_results", {}).get("popular_times", {}).get("graph_results", {})5except Exception as e:6 print(f"Error fetching details for {title}: {e}")7 popularity_scores = {}Sends a second requestΒ toΒ
PLACES_URLΒ using the extractedΒplace_idΒ to fetchΒ detailed informationΒ about the restaurant.Extracts popularity dataΒ (
graph_results) from the response.Handles errorsΒ β If the request fails, prints an error message and assigns anΒ empty dictionaryΒ
{}Β toΒpopularity_scores.
Finally, we fetched the details for each business in theΒ businessesΒ array and saved the data into a JSON file.
You might be thinking what isΒ popularity_scoresΒ stored here? If you check out theΒ Google Maps Places APIΒ at Scrapingdog, you will find the below response for each API call on a business. This is none other than popular times for a business on different weekdays.
1"popular_times": {2 "graph_results": {3 "sunday": [4 {5 "time": "6 AM",6 "info": "",7 "busyness_score": 08 },9 {10 "time": "7 AM",11 "info": "Usually not busy",12 "busyness_score": 713 },14 {15 "time": "8 AM",16 "info": "Usually not busy",17 "busyness_score": 1818 },19 {20 "time": "9 AM",21 "info": "Usually not too busy",22 "busyness_score": 3723 },24 {25 "time": "10 AM",26 "info": "Usually not too busy",27 "busyness_score": 3928 },29 {30 "time": "11 AM",31 "info": "Usually not too busy",32 "busyness_score": 3733 },34 {35 "time": "12 PM",36 "info": "Usually not too busy",37 "busyness_score": 2738 },39 {40 "time": "1 PM",41 "info": "Usually not too busy",42 "busyness_score": 3043 },44 {45 "time": "2 PM",46 "info": "Usually not too busy",47 "busyness_score": 4848 },49 {50 "time": "3 PM",51 "info": "Usually a little busy",52 "busyness_score": 5153 },54 {55 "time": "4 PM",56 "info": "Usually not too busy",57 "busyness_score": 4358 },59 {60 "time": "5 PM",61 "info": "",62 "busyness_score": 063 },64 {65 "time": "6 PM",66 "info": "",67 "busyness_score": 068 },69 {70 "time": "7 PM",71 "info": "",72 "busyness_score": 073 },74 {75 "time": "8 PM",76 "info": "",77 "busyness_score": 078 },79 {80 "time": "9 PM",81 "info": "",82 "busyness_score": 083 },84 {85 "time": "10 PM",86 "info": "",87 "busyness_score": 088 },89 {90 "time": "11 PM",91 "info": "",92 "busyness_score": 093 }94 ],95 ....96 }97 }Next, we will call this function and run our pipeline.
1businesses = get_data()2df = prepare_heatmap_data(businesses)3if not df.empty:4 generate_heatmap(df)TheΒ prepare_heatmap_dataΒ andΒ generate_heatmapΒ data will help us in organizing the data for heatmap and generating the graphic respectively.
1def normalize_hour_string(hour):2 # Replace non-breaking spaces and standardize to the format used in 'columns'3 return hour.replace('\u202f', ' ').strip()4 5def prepare_heatmap_data(businesses):6 columns = ["6 AM", "7 AM", "8 AM", "9 AM", "10 AM", "11 AM", "12 PM", "1 PM", "2 PM", "3 PM", "4 PM", "5 PM", "6 PM", "7 PM", "8 PM", "9 PM", "10 PM", "11 PM", "12 AM", "1 AM", "2 AM", "3 AM", "4 AM", "5 AM"]7 heatmap_data = []8 9 for business in businesses:10 title = business["title"]11 daily_scores = {hour: [] for hour in columns} # Initialize all hours with empty lists12 13 for day, hours in business["popularity_scores"].items():14 for hour_data in hours:15 hour_index = normalize_hour_string(hour_data['time'])16 if 'busyness_score' in hour_data:17 daily_scores[hour_index].append(hour_data['busyness_score'])18 19 # Calculate the average popularity per hour across the week or fill with zero20 daily_avg = [(sum(scores) / len(scores) if scores else 0) for scores in daily_scores.values()]21 heatmap_data.append({"business": title, **dict(zip(columns, daily_avg))})22 23 return pd.DataFrame(heatmap_data)Step-by-step explanation
Normalize Hour Strings:Β ReplacesΒ non-breaking spaces (
u202f) in hour strings.
Prepare Heatmap Data:
Defines hourly time slots (6 AM β 5 AM the next day).
InitializesΒ
heatmap_dataΒ list to store processed business popularity scores.Loops through each business.
Initializes an empty list for each hourΒ to store multiple daysβ data.
Iterates through popularity scores andΒ appends busyness valuesΒ for each hour.
ComputesΒ average busynessΒ per hour.
Converts the result into aΒ Pandas DataFrame.
Finally, we generate the heatmap.
1# Generate heatmap2def generate_heatmap(df):3 plt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] # for unicode characters4 plt.rcParams['axes.unicode_minus'] = False # for minus sign characters5 columns = ["6 AM", "7 AM", "8 AM", "9 AM", "10 AM", "11 AM", "12 PM", "1 PM", "2 PM", "3 PM", "4 PM", "5 PM", "6 PM", "7 PM", "8 PM", "9 PM", "10 PM", "11 PM", "12 AM", "1 AM", "2 AM", "3 AM", "4 AM", "5 AM"]6 heat_data = df.set_index("business").reindex(columns=columns)7 8 plt.figure(figsize=(15, 8))9 sns.heatmap(heat_data, cmap='YlOrRd', annot=True, fmt=".0f", linewidths=.5)10 plt.title('Popularity by Hour')11 plt.xlabel('Hour of Day')12 plt.ylabel('Business')13 plt.xticks(rotation=45)14 plt.tight_layout()15 plt.savefig("heatmap.png")16 plt.show()Step-by-step explanation:
Sets up MatplotlibΒ for Unicode support.
Formats DataFrameΒ for heatmap plotting.
Creates the heatmapΒ with Seaborn.
UsesΒ βYlOrRdβΒ color gradient (yellow-to-red).
Annotates heatmap cellsΒ with numerical values.
Saves the heatmap as an imageΒ (
heatmap.png).
Running this program will give you the following heatmap.
Similarly, you can run the programs for other queries also.
French Bistros
Cafes
Most of these cafes peak near lunchtime.
Art Galleries and Museums
Pubs and Bars
Theatres and Entertainment Venues
Conclusion
Google Maps generates interesting data on businesses that can be used for creating studies and research.
In this article, we analyzed the popular times for various businesses in London using Scrapingdog Google Maps API. I hope you found it helpful! If you did, please consider sharing it on social media.
If you want to analyze free local business data, you can use this tool, extract 20 local businesses' data from a location.
Thanks for reading! π