Geolocate multiple IP addresses

It seems to be possible to get this data directly in Mathematica using entities. Here's an example:

Let's get a list of IP addresses to work with:

ips = 
 First@*First@*HostLookup /@ {"wolfram.com", "stackexchange.com", 
   "google.com", "baidu.com", "www.elysee.fr", "global.jaxa.jp", 
   "qwant.com", "eso.org"}
(* {"206.123.112.238", "151.101.1.69", "216.58.198.206", \
"220.181.57.217", "207.123.33.126", "202.32.9.55", "194.187.168.99", \
"134.171.75.1"} *)

Transform them into entities:

entityList = Entity["IPAddress", #] & /@ ips;

The coordinates are available as a property:

EntityProperties@First[entityList]
(* {EntityProperty["IPAddress", "Address"], 
 EntityProperty["IPAddress", "Country"], 
 EntityProperty["IPAddress", "FullIPv6Address"], 
 EntityProperty["IPAddress", "HostCoordinates"], 
 EntityProperty["IPAddress", "HostLocation"], 
 EntityProperty["IPAddress", "HostOrganization"], 
 EntityProperty["IPAddress", "HostSegment"], 
 EntityProperty["IPAddress", "IPv4Address"], 
 EntityProperty["IPAddress", "IPv6Address"], 
 EntityProperty["IPAddress", "Name"]} *)

Let's get them all at once:

coords = 
   EntityValue[entityList, "HostCoordinates"]; // AbsoluteTiming
(* {0.976854, Null} *)

And plot them:

GeoGraphics[GeoMarker@GeoPosition[coords], GeoRange -> "World"]

Mathematica graphics

Optionally use DeleteMissing[coords] to remove failed lookups.


Timing using the benchmark dataset:

coord = 
   EntityValue[Entity["IPAddress", #] & /@ iplist, "HostCoordinates"]; // AbsoluteTiming
(* {3.68313, Null} *)

Do not apply this method to long lists, where Batch mode would be more efficient and less abusive to the API provider. See this other answer.

3 API Integrated solution (One API call per IP)

ClearAll[IP2Location];
Options[IP2Location] = {Method -> Automatic};
SetAttributes[IP2Location, Listable]
IP2Location[ip_String | IPAddress[ip_String], OptionsPattern[]] := 
 Block[{url, response, methodlist, method, latlon},
  methodlist = {"nekudo", "freegeoip", "ip-api"};
  method = If[
    OptionValue[Method] === Automatic || FreeQ[methodlist, OptionValue[Method]],
    RandomChoice[methodlist],
    OptionValue[Method]
    ];
  url = URLBuild@Switch[method,
     "nekudo", <|"Scheme" -> "http", "Domain" -> "geoip.nekudo.com", "Path" -> {"api", ip}|>,
     "freegeoip", <|"Scheme" -> "http", "Domain" -> "freegeoip.net", "Path" -> {"json", ip}|>,
     "ip-api", <|"Scheme" -> "http", "Domain" -> "ip-api.com", "Path" -> {"json", ip}|>
     ];
  response = Import[url, "RawJSON"];
  latlon = ToExpression@Values[
     Switch[method,
       "nekudo", Query["location", {"latitude", "longitude"}],
       "freegeoip", Query[{"latitude", "longitude"}],
       "ip-api", Query[{"lat", "lon"}]
       ][response]
     ];
  (*Echo[response,method];*)
  GeoLocation[latlon]
  ]

Performance

First@AbsoluteTiming@IP2Location[iplist]
(*37.6867*)

Failed in 7 sites.


Batch mode API call, for best performance

From the documentation at http://ip-api.com/docs/api:batch "Batch JSON", a batch processing with the ability to query multiple IP addresses in one HTTP request, significantly faster than submitting individual queries. A batch request requires a POST request to http://ip-api.com/batch with a Body string in JSON array format, containing up to 100 objects. Therefore, here the ipLongList arguments is Partition into as many ipShortList as necessary.

Free for non-commercial use only!

BatchIP2Location[ipLongList : List[_String ..]] := 
 AssociationThread[ipLongList,
  Flatten@Table[
    Query[Values, GeoPosition][
     ImportString[
      URLRead[
       HTTPRequest[
        "http://ip-api.com/batch",
        <|
         Method -> "POST",
         "Query" -> {"fields" -> "lat,lon"},
         "Body" -> 
          ExportString[
           Map[{"query" -> #} &, ipShortList], 
           "JSON"]
         |>]
       , "Body"], {"RawJSON"}]
     ],
    {ipShortList, Partition[ipLongList, UpTo[99]]}
    ]]

Performance

Length@iplist
(* 358 *)
First@AbsoluteTiming@BatchIP2Location@iplist
(* 0.525389 *)