Last updated: Oct 16, 2023

In this tutorial, we will be covering 2 different libraries that can be used to find a geolocation for any IP address. Both options have distinct advantages.

ip2geotools

ip2geotools provides an API for multiple geolocation databases. A free version is also available. This library will make an HTTP request each time a new geolocation is requested. This will add a bit of latency and, if used too aggressively, could potentially lead to throttling or your IP being blocked. That being said, I’ve never pushed the limits of the free version, but it’s something to keep in mind. Obviously, a paid version will be more reliable and have more accurate data.

First, we need to install the module:

pip install ip2geotools

Next, we need to create a new Python file:

touch ip2geotools_location.py

Inside our new file, we need to import the module:

from ip2geotools.databases.noncommercial import DbIpCity

Next, the DbIpCity class has a get method in which we can pass in our IP address. Since we’re using the free version, we can pass in “free” for the api_key parameter:

...
ip_location = DbIpCity.get("98.155.130.10", api_key="free")

Once the API request is made, a number of IP location properties are returned. The following print statements show the values for the requested IP address:

...
# ip_location.country: US
print(f"ip_location.country: {ip_location.country}")

# ip_location.region: Hawaii
print(f"ip_location.region: {ip_location.region}")

# ip_location.city: Kaneohe
print(f"ip_location.city: {ip_location.city}")

# ip_location.latitude: 21.418555
print(f"ip_location.latitude: {ip_location.latitude}")

# ip_location.longitude: -157.804184
print(f"ip_location.longitude: {ip_location.longitude}")

Notice that ip2geotools includes a latitude and longitude. geoip2 does not include this.

One other advantage regarding this library is the minimal code changes that would be necessary if you decided to upgrade to a paid version. You can also choose from a list of commercial providers like DbIpWeb, MaxMind, Ip2Location, and more.

geoip2

In this section, we will be using geoip2 to find a geolocation of any IP Address.

geoip2 is a Python package provided by Maxmind. Two different products are offered with the geoip2 package. The first is a web service which is a pay per use product. You will need to sign up for a User ID and license key to use this. The second product is a free version in which the database will be downloaded and stored locally. The free version is what we’ll be covering here. The advantage to using this option is that having a local database file eliminates latency and there is no risk of throttling.

Previously, you could go straight to the downloads page to download the database. Now, you’re required to create an account. Click “Sign Up for GeoLite2” on the GeoLite2 Free Geolocation Data page. After signing up and receiving an email verification, you should be able to login and access the downloads page. The URL should look something like the following:

Downloads Page URL (the “account id” path will vary depending on your actual Account ID)

https://www.maxmind.com/en/accounts/<account id>/geoip/downloads

Scroll down to the section that says “GeoLite2 City”, then click “Download GZIP” in the right column. The page should look similar to the following:

MaxMind GeoLite2 Downloads page

Once you have successfully downloaded the archive, create a new db folder in your project root:

mkdir db

Move downloaded archive into the db folder. Use the following commands (or your preferred tool) to unarchive the database:

Note: the name of the archived file will vary depending on when it was downloaded.

cd db
tar -xvzf GeoLite2-City_20230929.tar.gz

The database file will be the one with an mmdb extension. In order to read from this database, we’ll need to create a new Python file and import the geoip2 package.

Create the file:

touch geoip_lookup.py

Import the package:

import geoip2.database

Next, we will create a new reader variable and use that to open the database:

reader = geoip2.database.Reader('./GeoLite2-City_20230929/GeoLite2-City.mmdb')

Next, given an IP address, we can get the IP geolocation info.

response = reader.city('98.155.130.10')

Now that we have the geolocation contained in the response variable, we can print the data we’re looking for:

# response.country.iso_code: US
print("response.country.iso_code: {}".format(response.country.iso_code))

# response.subdivisions.most_specific.name: Hawaii
print(
    "response.subdivisions.most_specific.name: {}".format(
        response.subdivisions.most_specific.name
    )
)

# response.subdivisions.most_specific.iso_code: HI
print(
    "response.subdivisions.most_specific.iso_code: {}".format(
        response.subdivisions.most_specific.iso_code
    )
)

# response.city.name: Kailua
print("response.city.name: {}".format(response.city.name))

# response.postal.code: 96734
print("response.postal.code: {}".format(response.postal.code))

reader.close()

Notice that “subdivisions” is what contains the state info.

Maxmind also follows the standards set by International Organization for Standardization (ISO), which is reflected in the iso_code properties. More info regarding country ISO codes can be found here.

Final note: other databases and a web service (mentioned earlier) are provided by Maxmind. The paid alternatives are generally more accurate and updated more frequently.

Here is the contents of the full script:

import geoip2.database

reader = geoip2.database.Reader('./GeoLite2-City_20230929/GeoLite2-City.mmdb')

response = reader.city('98.155.130.10')

# response.country.iso_code: US
print("response.country.iso_code: {}".format(response.country.iso_code))

# response.subdivisions.most_specific.name: Hawaii
print(
    "response.subdivisions.most_specific.name: {}".format(
        response.subdivisions.most_specific.name
    )
)

# response.subdivisions.most_specific.iso_code: HI
print(
    "response.subdivisions.most_specific.iso_code: {}".format(
        response.subdivisions.most_specific.iso_code
    )
)

# response.city.name: Kailua
print("response.city.name: {}".format(response.city.name))

# response.postal.code: 96734
print("response.postal.code: {}".format(response.postal.code))

reader.close()

Posted in