Farming phone numbers with Python and the Google Places API

Headerimg

Only care about the source code? Checkout the Github Repo

Background & Motivation

One day a friend asked me something along the lines of:

“Do you think you could grab the local phone number area code from an address?”

A simple question. “Yeah!” I answered. A quick Google search can get you that info. Just look around Google Maps and figure out which is most common.

What if you have 100 addresses? What if you not only want the area code, but a list of phone numbers from that area? This would get extremely tedious.

Enter the Places API.

Places API

One of the first thoughts that entered my mind is that this could be automated with Python. A problem I foresaw is that finding an adequate data source would be difficult.

So where would you go if you need to gather a large amount of geographical data? Google Maps seemed like a great candidate.

A quick Google search and some digging later, we find the Places API, specifically the Place Details request:

Place Details

This looks spot on. So we can issue a Place Details request and gather information including the phone number. Perfect.

There’s one problem though, if we look at the above screenshot, to issue a Place Details request we will need a place_id from a Place Search.

If we remember our original question, we’ll need to gather phone numbers for nearby locations, rather than the address itself (which may not possess a phone number entry within Google’s data).

This brings us to the Nearby Search request:

Nearby Search

Essentially, we can gather a long list of nearby place_id parameters, then feed them into a Place Details search request to grab the phone number for each nearby location.

Let’s take a look at the API parameters required for this request:

Nearby Search Parameters

There are a few things to note here:

  • We’ll need to specify an output type in the URL. I’ll choose JSON since I don’t care to mess around with XML.
  • If we look at the required parameters we can specify a radius (in meters) to gather results from. This is cool because we can custom tailor the amount of results we want (or don’t want) to gather.
  • Lastly, apart from the usual key parameter, there is also a location parameter that is required to issue the request. This must be served in the longitude,latitude format. Crap, so one last hurdle. How do we get a latitude/longitude from an address?

Luckily, an additional API method exists for us to gather the latitude and longitude values we’ll need to issue a Nearby Search request for our address. This is a simple text search where we can use our original address as the arugment, therefore compelting the whole process.

Taking a look at the Text Search Request:

Text Search

Now let’s see what kind of output this will return:

Text Search Example Output

Okay, kind of confusing, but with all this information gathered, the flow for gathering phone numbers will look something like this:

Automation Process Flow

Essentially, we’ll take an address, translate that to latitude/longitude, find the nearby list of place_id parameters, and then finally grab a list of phone numbers from those nearby places.

You might be thinking that we can simply issue an original Place Details request from the place_id parameter that’s tied to our address, but this wouldn’t work since we want nearby phone numbers, and to grab the phone number of nearby locations, we’ll need the place_id parameter for all the nearby locations.

It’s a little convuluded, but it’s the simplest way I could orchestrate it. If I’m missing a step that simplifies (and reduces the cost) of the whole process, Tell me!

Automating the Process with Python

Now for the fun part; scripting this out and automating it.

Let’s start with the fundamental stepping stone we’ll need to kick the process off; getting latitude and longitude. I’ll call this function getLatLng:

getLatLng():

def getLatLng(addr):
   latlongPayload = {'key': args.APIKEY, 'query': addr, 'fields': "lat,lng"}
   longlat = requests.get("https://maps.googleapis.com/maps/api/place/textsearch/json?", params=latlongPayload)
   try:
      lat = longlat.json()['results'][0]['geometry']['location']['lat']
      lng = longlat.json()['results'][0]['geometry']['location']['lng']
      return str(lat) + "," + str(lng)
   ## Catch errors
   except (IndexError, UnboundLocalError):
         print("No lat long found")
         pass

For context, I’ve already setup a parser that is taking arguments that we can access via args.<value>.

I’ll next use the Python requests module to issue a Text Search request using the latlongPayload array we’ve constructed. Note the field value in that array, which tells Google that we only care about the lat and lng values.

After some indicing we can simply return the latitude and longitude of this address, in a pre-composed format that the Nearby Search request will need in our next step.

Lastly, I’ll throw in some error handling to catch errors that may occur if Google can’t find a lat/lng value for a given address.

To issue a request to Google APIs you’ll need a devloper key. For more information on how to get one check out this documentation.

With this written, I’ll go ahead and give the getLatLng method a try:

Running getLatLng

Nice! So we have a working Lat/Lng. Let’s move on to scripting the Nearby Search request.

getPlaceIDs():

def getPlaceIDs(addr):
   nearbyPayload = {'key': args.APIKEY, 'location': getLatLng(addr), 'radius': 150}
   nearbyReq = requests.get("https://maps.googleapis.com/maps/api/place/nearbysearch/json?", params=nearbyPayload)
   nearbysearches = nearbyReq.json()['results']
   placeIDList = []
   
   ## Iterate through JSON and add place_ids to list if not empty
   for i in nearbysearches:
      if i['place_id'] is not None:
         placeIDList.append(i['place_id'])

   return placeIDList

Nearby Search requests can quickly become expensive. If you’d like your search to return specific values (and cut down on prices), I would recommend checking out the Find Place request documentation

We can reuse our getLatLng method by simply including it within our passed along payload of nearbyPayload. We’ll also set the radius to 150 for a decent amount of results.

After our nearbyPayload value is set, we can simply issue the Nearby Search request. Now we will indice to the results portion of the JSON, and itereate through the data. If the place_id value does not possess a NoneType value, we will append it to our placeIDList list that will be returned once the entire list of nearby locations has been iterated through.

With our function completed, let’s try to grab a list of place_id parameters:

Running getPlaceIDs

Awesome. This brings us to the final stretch, with our working list of nearby place_id values, all we will need to do is issue a Place Details request for each and gather the phone number varaible.

I’ll create a makePhoneNumberList function to automate this final process.

makePhoneNumberList():

def makePhoneNumberList():
   phoneNumsList = []
   for i in getPlaceIDs(address):
      placeIDpayload = {'key': args.APIKEY, 'place_id': i, 'fields': "formatted_phone_number"}
      phoneQuery = requests.get('https://maps.googleapis.com/maps/api/place/details/json?', params=placeIDpayload)
      phoneNumbs = phoneQuery.json()['result']
      phoneNumsList.append(phoneNumbs.get("formatted_phone_number"))
      
   return phoneNumsList

We begin by iterating through our list of Nearby Place IDs (gathered through use of our previous functions).

Notice that we specify the formatted_phone_number field as the only desired return value for the request.

For each Place ID in our list, we issue a Place Details request and append the returned phone number to a list called phoneNumsList.

Running the makePhoneNumberList function:

Running makePhoneNumberList

A little clunky with the returned NoneType values, but it gets the job done.

The Final Product.

With all of our functions in place, and some extra scripting to provide meaningful output, our final result looks like the following:

Running the final script

Conclusion

And that’s it. A lightweight (and potentially costly) way to mass grab phone numbers from a single address. You can check out the full documentation from the Github Repo.

How useful is this? I have no idea. It was a great excuse to work on learning Python. Maybe you’ll find a cool use for the script, but hopefully at least it provides a decent guide on using some of the Google Places APIs.