Deciding which keywords warrant your full attention is the one of the first tasks in any local SEO campaign.

You can have great content and an amazing backlink profile, but if no one is searching for what you're writing about, what's the point?

Keyword research is an essential first step that should guide the rest of a local SEO campaign.  We need to know what keywords have traffic, whether that traffic has any commercial intent, and how difficult it would be to rank for those keywords.

This is a fairly common task for any SEO, but we'll go one step further for local SEO and find data that is relevant for specific cities.

In this post I'll show how that works in Persuaded.io, and if you're technically minded, I'll walk through how you can implement similar functionality yourself.

Technical Stuff Ahead

This article will feature some technical details as we delve behind the scenes to see how the keyword suggestion feature works.

Local Keyword Research

Persuaded.io provides Google Maps ranking data, but until recently, we left it up to the user to decide what search queries they wanted to track.  In the last upgrade, however, we added a keyword suggestions feature.

Keyword suggestions and related data localized for Cary, North Carolina.

So how does this feature work under the hood?

The data itself comes from the excellent DataForSEO API.  There are two DataForSEO endpoints to understand if you want to build a similar process into your own workflow.

The Keyword Suggestions API accepts a seed keyword and produces a list of suggestions based on similar queries.  You can provide a location name or code to restrict the suggestions to a particular country, but this doesn't extend to states or cities.

    # An example of requesting keyword suggestions.
    response = requests.post(
        "https://api.dataforseo.com/v3/dataforseo_labs/keyword_suggestions/live",
        auth=(app.config["DATAFORSEO_USER"], app.config["DATAFORSEO_TOKEN"]),
        json=[{"keyword": "plumber", "location_name": "United States"}],
    )

In turn, you can feed the suggested keywords into the Search Volume API and fetch monthly traffic as well as CPC info for each of the keywords.  Just as with the Suggestion API, you can provide a location name or code, but most importantly, this extends to the city level!

     # An example of requesting search volume data.
     response = requests.post(
         "https://api.dataforseo.com/v3/keywords_data/google/search_volume/live",
         auth=(app.config["DATAFORSEO_USER"], app.config["DATAFORSEO_TOKEN"]),
         json=[{"keywords": ["plumber"], "location_code": 1021278}]
     )

Basically, this means we get suggestions at the national level, and then refine those suggestions with more localized volume and CPC info.

Location Names and Codes

This all sounds fairly straightforward, but the localization step is a little tricky.

If you're in local SEO, that means you're probably working with Google My Business listings as a starting point.  If you want to get localized data, you'll need to translate a GMB location address into an equivalent that DataForSEO can understand.

For keyword suggestions, this is pretty simple, because it operates at the national level.

Volume and CPC info, however, requires a location code.  The code 1021278, for example, represents Raleigh, North Carolina.

DataForSEO has a large list of these location names and codes available in JSON format. As large as the list is, it doesn't account for small towns.  This means we'll need to improvise when a GMB listing uses a city that doesn't appear in the list.

Our solution here is to find the latitude and longitude of the GMB listing and use that to determine the nearest city that does appear on the DataForSEO list.  You'll first have to enrich the DataForSEO city list with coordinates, using any number of publicly available data-sets.

top1k = {}
# The top-1k.csv file contains the 1000 largest cities in
# the U.S. along with their lat/lng.
with open("top-1k.csv", "r") as f:
    for line in f:
        parts = line.split(",")
        city = parts[0]
        state = parts[1]

        top1k[city + ", " + state] = parts


# The locations.json file contains all of the available
# location names/codes from DataForSEO, which we'll use
# to match up with the lat/lng file using city/state.
data = json.loads(open("locations.json", "r").read())
i = 0
for row in data["tasks"][0]["result"]:
    if "United States" in row["location_name"]:
        parts = row["location_name"].split(",")

        if len(parts) < 2:
            continue

        key = parts[0] + ", " + parts[1]

        if key in top1k:
            print(
                ",".join(
                    (
                        str(row["location_code"]),
                        parts[0],
                        parts[1],
                        top1k[key][-2],
                        top1k[key][-1].strip(),
                    )
                )
            )

You'll already have the latitude and longitude of the GMB listing if you're using the Google Places API.  If not, you can use a reverse geocoding service like Geocodio.

Finding the Nearest Location Code

The rest is relatively simple once you have a list of API location codes that are enriched with latitude/longitude.  

Let's say you want to get keyword suggestions, traffic, and CPC estimates, specific to the area around a GMB listing.  With the data we've already generated, you can find the nearest city that has a DataForSEO location code.

The way you go about this depends on how you've stored the data, but for example, it might look something like this in SQL.

SELECT
    location.code,
    sqrt(pow(location.lat - :lat, 2) + pow(location.lng - :lng, 2)) AS dist
FROM location ORDER BY dist LIMIT 1

Expanding the Localized Search Suggestions

As you can tell from this post, while the search suggestions feature works internationally, localized data is only available within the United States.

The next step is to expand our lookup list to include cities in other countries, so that we're showing localized search traffic and CPC volumes, instead of national numbers.

I hope you enjoyed this post, and have a chance to try the DataForSEO API yourself!