DEV Community

Cizze R
Cizze R

Posted on

How to Geolocate Any IP Address in Python with 3 Lines of Code

How to Geolocate Any IP Address in Python with 3 Lines of Code

If you need to know where your users are coming from — whether to show the right currency, pre-fill country selectors, or flag suspicious logins — you need an IP geolocation service. While setting this up might sound like a weekend project involving bulky database downloads and licensing agreements, you can actually solve it in minutes with a simple API call.

Using the IP Geolocation Lookup on Apify, you get sub-second lookups without the overhead of maintaining database files or paying massive monthly minimums to commercial vendors. Here is how to build a production-grade geolocation pipeline in Python.

The Geo-IP Database Headache

Most developers start by downloading a free MaxMind GeoLite2 database. It seems like the right approach until you hit the operational realities.

First, the file is large — often hundreds of megabytes — which bloats your Docker images or serverless function deployment sizes. Second, IP distributions change constantly; if you do not write a cron job to download updates every week, your location data quickly degrades. Finally, the licensing is restrictive, requiring attribution and strict compliance checks that complicate commercial apps.

An API-based solution solves this instantly. By delegating lookups to an external service, your application stays lean, database updates are handled transparently, and you only pay for what you actually query.

Quick Start with Python

The IP Geolocation Lookup takes an IP address (or a list of IPs for batch processing) and returns structured JSON with the country, region, city, postal code, latitude, longitude, timezone, internet service provider (ISP), and autonomous system number (ASN).

Here is how to run a synchronous lookup in Python using the standard requests library.

import requests

API_TOKEN = "YOUR_APIFY_TOKEN"
ACTOR_ID = "weeknds~ip-geolocation-lookup"

def geolocate_ip(ip_address):
    url = f"https://api.apify.com/v2/acts/{ACTOR_ID}/runs?token={API_TOKEN}"

    # We pass the target IP in the input payload
    payload = {
        "ip": ip_address
    }

    # Start the actor run
    response = requests.post(url, json=payload)
    response.raise_for_status()
    run_data = response.json()
    run_id = run_data["data"]["id"]

    # In a real app, wait a brief moment or use sync endpoints
    # For sub-second response, the API allows sync runs
    sync_url = f"https://api.apify.com/v2/acts/{ACTOR_ID}/run-sync-get-dataset-items?token={API_TOKEN}"
    sync_response = requests.post(sync_url, json=payload)
    sync_response.raise_for_status()

    results = sync_response.json()
    return results[0] if results else None

# Example usage
geo_data = geolocate_ip("8.8.8.8")
if geo_data:
    print(f"Country: {geo_data.get('country')}")
    print(f"City: {geo_data.get('city')}")
    print(f"ISP: {geo_data.get('isp')}")
Enter fullscreen mode Exit fullscreen mode

The output JSON contains everything you need to map the user's location:

{
  "ip": "8.8.8.8",
  "country": "United States",
  "countryCode": "US",
  "region": "California",
  "regionCode": "CA",
  "city": "Mountain View",
  "zip": "94043",
  "lat": 37.4223,
  "lon": -122.0847,
  "timezone": "America/Los_Angeles",
  "isp": "Google LLC",
  "org": "Google Public DNS",
  "as": "AS15169 Google LLC"
}
Enter fullscreen mode Exit fullscreen mode

Detecting Fast-Travel Login Fraud

One of the most practical security features you can build with IP geolocation is a "fast-travel" detector. If a user logs into your system from New York, and then logs in ten minutes later from London, it is physically impossible for them to have traveled that distance. One of those sessions is fraudulent.

We can calculate the physical distance between two IP addresses using the Haversine formula on their latitudes and longitudes, then divide by the time elapsed to check if the travel speed is realistic.

import math
from datetime import datetime

def haversine_distance(lat1, lon1, lat2, lon2):
    # Radius of the Earth in kilometers
    R = 6371.0

    # Convert latitude and longitude to radians
    phi1 = math.radians(lat1)
    phi2 = math.radians(lat2)
    delta_phi = math.radians(lat2 - lat1)
    delta_lambda = math.radians(lon2 - lon1)

    a = (math.sin(delta_phi / 2.0) ** 2 +
         math.cos(phi1) * math.cos(phi2) *
         math.sin(delta_lambda / 2.0) ** 2)

    c = 2.0 * math.atan2(math.sqrt(a), math.sqrt(1.0 - a))
    distance = R * c
    return distance

def analyze_login_security(user_id, current_ip, last_login_ip, last_login_time):
    # Geolocate both IPs
    current_geo = geolocate_ip(current_ip)
    last_geo = geolocate_ip(last_login_ip)

    if not current_geo or not last_geo:
        return {"action": "allow", "reason": "Could not verify locations"}

    # Calculate distance
    dist_km = haversine_distance(
        current_geo["lat"], current_geo["lon"],
        last_geo["lat"], last_geo["lon"]
    )

    # Calculate time difference in hours
    time_diff = datetime.now() - last_login_time
    hours_elapsed = time_diff.total_seconds() / 3600.0

    if hours_elapsed <= 0:
        hours_elapsed = 0.01  # Prevent division by zero

    # Calculate travel speed in km/h
    required_speed = dist_km / hours_elapsed

    # Commercial passenger jets fly around 900 km/h
    if required_speed > 950.0 and dist_km > 100.0:
        return {
            "action": "block",
            "reason": f"Impossible travel speed: {required_speed:.1f} km/h. "
                      f"Moved {dist_km:.1f} km in {hours_elapsed:.2f} hours.",
            "current_location": f"{current_geo['city']}, {current_geo['country']}",
            "last_location": f"{last_geo['city']}, {last_geo['country']}"
        }

    return {
        "action": "allow",
        "reason": f"Travel speed {required_speed:.1f} km/h is within safe bounds."
    }
Enter fullscreen mode Exit fullscreen mode

This simple check runs entirely on your server and blocks unauthorized session hijacking without requiring complex machine learning setups.

Multi-Currency and Localization Routing

E-commerce websites often lose customers because they display pricing in the wrong currency or fail to localize language options. Instead of forcing users to navigate a heavy dropdown menu, you can use the visitor's IP address to automatically direct them to the correct localized store or pre-set their currency.

Here is a practical function that determines the currency and local tax tier based on the incoming request IP.

def get_localization_config(ip_address):
    geo = geolocate_ip(ip_address)
    if not geo:
        return {
            "currency": "USD",
            "tax_rate": 0.0,
            "region_code": "US",
            "language": "en"
        }

    country_code = geo.get("countryCode", "US").upper()

    # Map country codes to local configurations
    # This avoids complex third-party localized lookup tables
    config_map = {
        "US": {"currency": "USD", "tax_rate": 0.0, "language": "en"},
        "CA": {"currency": "CAD", "tax_rate": 0.05, "language": "en"},
        "GB": {"currency": "GBP", "tax_rate": 0.20, "language": "en"},
        "DE": {"currency": "EUR", "tax_rate": 0.19, "language": "de"},
        "FR": {"currency": "EUR", "tax_rate": 0.20, "language": "fr"},
        "SE": {"currency": "SEK", "tax_rate": 0.25, "language": "sv"},
        "JP": {"currency": "JPY", "tax_rate": 0.10, "language": "ja"}
    }

    # Fallback to Euro for other EU countries
    eu_countries = {"AT", "BE", "CY", "EE", "FI", "GR", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PT", "SK", "SI", "ES"}

    if country_code in config_map:
        return {
            "currency": config_map[country_code]["currency"],
            "tax_rate": config_map[country_code]["tax_rate"],
            "region_code": country_code,
            "language": config_map[country_code]["language"]
        }
    elif country_code in eu_countries:
        return {
            "currency": "EUR",
            "tax_rate": 0.20,  # Estimated fallback VAT
            "region_code": country_code,
            "language": "en"
        }
    else:
        # Global fallback
        return {
            "currency": "USD",
            "tax_rate": 0.0,
            "region_code": country_code,
            "language": "en"
        }
Enter fullscreen mode Exit fullscreen mode

This localization config can be fed directly to your frontend template engine, ensuring that users see localized pricing on their very first page load.

Pricing

The IP Geolocation Lookup costs $0.001 per run. This means you can process 1,000 geolocation checks for exactly $1. For scaling applications, this is highly economical compared to major dedicated enterprise vendors that enforce high monthly platform fees and require annual commitments.

Integrating the API

The integration is lightweight and fits easily into any web framework. You do not need to install complex system libraries or manage local binary files. A standard HTTP post request is all it takes to keep your data current and accurate.

Top comments (0)