描述
本文實現:
1、離線查詢IP地址
2、IP地址精確到區域
3、IP地址支持國外IP
此時需要一個創建,比如我輸入一個8.8.8.8的IP立馬就需要返回給我一個中文地址信息,
類似于百度的IP搜索:
113.111.186.123
如果現在離線環境或者在線環境上本地部署一套IP地址查詢,可以查閱本文章。
本文本地部署的結果演示(注意!本文演示的項目不包含任何一個在線API接口!!!
):
首先感謝GItHub地址:https://github.com/ljxi/GeoCN
作者的在線演示地址:
https://ipv4.netart.cn/
https://ipv4.netart.cn/113.111.186.123
這個倉庫是用的Python 的 Fast API 開發的,此時我需要一個Java的,本文會有Java的項目地址。
還有一種技術,只有springboot的,返回數據不是準確,感興趣的可以自己去看看:
ip2region倉庫地址:https://gitee.com/lionsoul/ip2region
ip2region集成文章:https://cloud.tencent.com/developer/article/2373096
開始
1、下載IP庫
mmdb 倉庫:https://github.com/P3TERX/GeoLite.mmdb/releases(不主要)
mmdb IP庫下載:https://github.com/P3TERX/GeoLite.mmdb/releases
只需要下載以下兩個mmdb文件
GeoLite2-City.mmdb
GeoLite2-ASN.mmdb
如果你的網絡出不了國,可以下載國內的:https://download.csdn.net/download/u014641168/91554209
還需下載:
GeoCN.mmdb
這個文件在作者的倉庫中就能找到,地址:https://github.com/ljxi/GeoCN/releases/tag/Latest
如果打不開,看國內:https://download.csdn.net/download/u014641168/91554230 (我忘了整合到一塊,將就著看吧)
代碼
python
首先作者的代碼時Python的,Python版本是3.9的,需要手動安裝插件庫
pip install ipaddress
pip install maxminddb
還有fastapi
1、代碼如下:
import ipaddress
import maxminddb
from fastapi import FastAPI, Requestcity_reader = maxminddb.open_database('C:\\Users\\ad\\Desktop\\ip\\GeoLite2-City.mmdb')
asn_reader = maxminddb.open_database('C:\\Users\\ad\\Desktop\\ip\\GeoLite2-ASN.mmdb')
cn_reader = maxminddb.open_database('C:\\Users\\ad\\Desktop\\ip\\GeoCN.mmdb')
lang = ["zh-CN","en"]
asn_map = {9812:"東方有線",9389:"中國長城",17962:"天威視訊",17429:"歌華有線",7497:"科技網",24139:"華數",9801:"中關村",4538:"教育網",24151:"CNNIC",38019:"中國移動",139080:"中國移動",9808:"中國移動",24400:"中國移動",134810:"中國移動",24547:"中國移動",56040:"中國移動",56041:"中國移動",56042:"中國移動",56044:"中國移動",132525:"中國移動",56046:"中國移動",56047:"中國移動",56048:"中國移動",59257:"中國移動",24444:"中國移動",24445:"中國移動",137872:"中國移動",9231:"中國移動",58453:"中國移動",4134:"中國電信",4812:"中國電信",23724:"中國電信",136188:"中國電信",137693:"中國電信",17638:"中國電信",140553:"中國電信",4847:"中國電信",140061:"中國電信",136195:"中國電信",17799:"中國電信",139018:"中國電信",133776:"中國電信",58772:"中國電信",146966:"中國電信",63527:"中國電信",58539:"中國電信",58540:"中國電信",141998:"中國電信",138169:"中國電信",139203:"中國電信",58563:"中國電信",137690:"中國電信",63838:"中國電信",137694:"中國電信",137698:"中國電信",136167:"中國電信",148969:"中國電信",134764:"中國電信",134770:"中國電信",148981:"中國電信",134774:"中國電信",136190:"中國電信",140647:"中國電信",132225:"中國電信",140485:"中國電信",4811:"中國電信",131285:"中國電信",137689:"中國電信",137692:"中國電信",140636:"中國電信",140638:"中國電信",140345:"中國電信",38283:"中國電信",140292:"中國電信",140903:"中國電信",17897:"中國電信",134762:"中國電信",139019:"中國電信",141739:"中國電信",141771:"中國電信",134419:"中國電信",140276:"中國電信",58542:"中國電信",140278:"中國電信",139767:"中國電信",137688:"中國電信",137691:"中國電信",4809:"中國電信",58466:"中國電信",137687:"中國電信",134756:"中國電信",134760:"中國電信",133774:"中國電信",133775:"中國電信",4816:"中國電信",134768:"中國電信",58461:"中國電信",58519:"中國電信",58520:"中國電信",131325:"中國電信",4837:"中國聯通",4808:"中國聯通",134542:"中國聯通",134543:"中國聯通",10099:"中國聯通",140979:"中國聯通",138421:"中國聯通",17621:"中國聯通",17622:"中國聯通",17816:"中國聯通",140726:"中國聯通",17623:"中國聯通",136958:"中國聯通",9929:"中國聯通",58519:"中國聯通",140716:"中國聯通",4847:"中國聯通",136959:"中國聯通",135061:"中國聯通",139007:"中國聯通",59019:"金山云",135377:"優刻云",45062:"網易云",137718:"火山引擎",37963:"阿里云",45102:"阿里云國際",45090:"騰訊云",132203:"騰訊云國際",55967:"百度云",38365:"百度云",58519:"華為云", 55990:"華為云",136907:"華為云",4609:"澳門電訊",134773:"珠江寬頻",1659:"臺灣教育網",8075:"微軟云",17421:"中華電信",3462:"HiNet",13335:"Cloudflare",55960:"亞馬遜云",14618:"亞馬遜云",16509:"亞馬遜云",15169:"谷歌云",396982:"谷歌云",36492:"谷歌云",
}def get_as_info(number):r = asn_map.get(number)if r:return rdef get_des(d):for i in lang:if i in d['names']:return d['names'][i]return d['names']['en']def get_country(d):r = get_des(d)if r in ["香港", "澳門", "臺灣"]:return "中國" + rreturn rdef province_match(s):arr=['內蒙古','黑龍江','河北','山西','吉林','遼寧','江蘇','浙江','安徽','福建','江西','山東','河南','湖北','湖南','廣東','海南','四川','貴州','云南','陜西','甘肅','青海','廣西','西藏','寧夏','新疆','北京','天津','上海','重慶']for i in arr:if i in s:return ireturn ''def de_duplicate(regions):regions = filter(bool,regions)ret = [][ret.append(i) for i in regions if i not in ret]return retdef get_addr(ip, mask):network = ipaddress.ip_network(f"{ip}/{mask}", strict=False)first_ip = network.network_addressreturn f"{first_ip}/{mask}"def get_maxmind(ip: str):ret = {"ip":ip}asn_info = asn_reader.get(ip)if asn_info:as_ = {"number":asn_info["autonomous_system_number"],"name":asn_info["autonomous_system_organization"]}info = get_as_info(as_["number"])if info:as_["info"] = inforet["as"] = as_city_info, prefix = city_reader.get_with_prefix_len(ip)ret["addr"] = get_addr(ip, prefix)if not city_info:return retif "country" in city_info:country_code = city_info["country"]["iso_code"]country_name = get_country(city_info["country"])ret["country"] = {"code":country_code,"name":country_name}if "registered_country" in city_info:registered_country_code = city_info["registered_country"]["iso_code"]ret["registered_country"] = {"code":registered_country_code,"name":get_country(city_info["registered_country"])}regions = [get_des(i) for i in city_info.get('subdivisions', [])]if "city" in city_info:c = get_des(city_info["city"])if (not regions or c not in regions[-1])and c not in country_name:regions.append(c)regions = de_duplicate(regions)if regions:ret["regions"] = regionsreturn retdef get_cn(ip:str, info={}):ret, prefix = cn_reader.get_with_prefix_len(ip)if not ret:returninfo["addr"] = get_addr(ip, prefix)regions = de_duplicate([ret["province"],ret["city"],ret["districts"]])if regions:info["regions"] = regionsinfo["regions_short"] = de_duplicate([province_match(ret["province"]),ret["city"].replace('市',''),ret["districts"]])if "as" not in info:info["as"] = {}info["as"]["info"] = ret['isp']if ret['net']:info["type"] = ret['net']return retdef get_ip_info(ip):info = get_maxmind(ip)if "country" in info and info["country"]["code"] == "CN" and ("registered_country" not in info or info["registered_country"]["code"] == "CN"):get_cn(ip,info)return infodef query():while True:try:ip = input('IP: \t').strip()info = get_ip_info(ip)print(f"網段:\t{info['addr']}")if "as" in info:print(f"ISP:\t",end=' ')if "info" in info["as"]:print(info["as"]["info"],end=' ')else:print(info["as"]["name"],end=' ')if "type" in info:print(f"({info['type']})",end=' ')print(f"ASN{info['as']['number']}",end=' ')print(info['as']["name"])if "registered_country" in info and ("country" not in info or info["country"]["code"] != info["registered_country"]["code"]):print(f"注冊地:\t{info['registered_country']['name']}")if "country" in info:print(f"使用地:\t{info['country']['name']}")if "regions" in info:print(f"位置: \t{' '.join(info['regions'])}")except Exception as e:print(e)raise efinally:print("\n")app = FastAPI()@app.get("/")
def api(request: Request, ip: str = None):if not ip:xff = request.headers.get("x-forwarded-for")if xff:ip = xff.split(",")[0]else:ip = request.headers.get("x-real-ip") or request.client.hostreturn get_ip_info(ip.strip())@app.get("/{ip}")
def path_api(ip):return get_ip_info(ip)if __name__ == '__main__':query()import uvicornuvicorn.run(app, host="0.0.0.0", port=8080, server_header=False, proxy_headers=True)
2、啟動
uvicorn main:app --port 9000 --reload
3、瀏覽器訪問
http://localhost:9000/113.111.186.123
Java
作者的Python代碼不滿足我的需求,手動轉成Java格式
我的jdk1.8,maven 3.6
1、引入依賴
<!-- MaxMind DB Reader -->
<dependency><groupId>com.maxmind.db</groupId><artifactId>maxmind-db</artifactId><version>2.1.0</version>
</dependency>
2、代碼類:
ASInfo.java
import lombok.Data;/*** @author: tongyao* @since: 2025-07-31 09:32*/
@Data
public class ASInfo {private Integer number;private String name;private String info;
}
CountryInfo.java
import lombok.Data;/*** @author: tongyao* @since: 2025-07-31 09:32*/
@Data
public class CountryInfo {private String code;private String name;
}
GeoCNResponse.java
import lombok.Data;@Data
public class GeoCNResponse {private String province;private String city;private String districts;private String isp;private String net;private int prefixLen;// 移除MaxMindDbConstructor注解public GeoCNResponse(String province,String city,String districts,String isp,String net,int prefixLen) {this.province = province;this.city = city;this.districts = districts;this.isp = isp;this.net = net;this.prefixLen = prefixLen;}
}
IPInfo.java
import lombok.Data;
import java.util.List;@Data
public class IPInfo {private String ip;private String addr;private String error;private String type;private CountryInfo country;private CountryInfo registeredCountry;private List<String> regions;private List<String> regionsShort;private ASInfo as;
}
GeoIPConfig.java
import com.maxmind.db.Reader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.File;
import java.io.IOException;@Configuration
public class GeoIPConfig {@Beanpublic Reader cityReader() throws IOException {return new Reader(new File("C:\\Users\\ad\\Desktop\\ip\\GeoLite2-City.mmdb"));}@Beanpublic Reader asnReader() throws IOException {return new Reader(new File("C:\\Users\\ad\\Desktop\\ip\\GeoLite2-ASN.mmdb"));}@Beanpublic Reader cnReader() throws IOException {return new Reader(new File("C:\\Users\\ad\\Desktop\\ip\\GeoCN.mmdb"));}
}
GeoIPService.java
import javax.servlet.http.HttpServletRequest;
import java.util.Map;public interface GeoIPService {IPInfo getIpInfo(String ip);IPInfo getIpInfo(HttpServletRequest request);
}
GeoIPServiceImpl.java
import com.maxmind.db.Reader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.InetAddress;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class GeoIPServiceImpl implements GeoIPService {private final Reader cityReader;private final Reader asnReader;private final Reader cnReader;private final List<String> lang = Arrays.asList("zh-CN", "en");private final List<String> provinces = Arrays.asList("內蒙古", "黑龍江", "河北", "山西", "吉林", "遼寧", "江蘇", "浙江","安徽", "福建", "江西", "山東", "河南", "湖北", "湖南", "廣東","海南", "四川", "貴州", "云南", "陜西", "甘肅", "青海", "廣西","西藏", "寧夏", "新疆", "北京", "天津", "上海", "重慶");private static final Map<Integer, String> ASN_MAP = new HashMap<Integer, String>() {{// 有線電視/網絡服務商put(9812, "東方有線");put(9389, "中國長城");put(17962, "天威視訊");put(17429, "歌華有線");put(7497, "科技網");put(24139, "華數");put(9801, "中關村");put(4538, "教育網");put(24151, "CNNIC");// 中國移動put(38019, "中國移動");put(139080, "中國移動");put(9808, "中國移動");put(24400, "中國移動");put(134810, "中國移動");put(24547, "中國移動");put(56040, "中國移動");put(56041, "中國移動");put(56042, "中國移動");put(56044, "中國移動");put(132525, "中國移動");put(56046, "中國移動");put(56047, "中國移動");put(56048, "中國移動");put(59257, "中國移動");put(24444, "中國移動");put(24445, "中國移動");put(137872, "中國移動");put(9231, "中國移動");put(58453, "中國移動");// 中國電信put(4134, "中國電信");put(4812, "中國電信");put(23724, "中國電信");put(136188, "中國電信");put(137693, "中國電信");put(17638, "中國電信");put(140553, "中國電信");put(4847, "中國電信");put(140061, "中國電信");put(136195, "中國電信");put(17799, "中國電信");put(139018, "中國電信");put(133776, "中國電信");put(58772, "中國電信");put(146966, "中國電信");put(63527, "中國電信");put(58539, "中國電信");put(58540, "中國電信");put(141998, "中國電信");put(138169, "中國電信");put(139203, "中國電信");put(58563, "中國電信");put(137690, "中國電信");put(63838, "中國電信");put(137694, "中國電信");put(137698, "中國電信");put(136167, "中國電信");put(148969, "中國電信");put(134764, "中國電信");put(134770, "中國電信");put(148981, "中國電信");put(134774, "中國電信");put(136190, "中國電信");put(140647, "中國電信");put(132225, "中國電信");put(140485, "中國電信");put(4811, "中國電信");put(131285, "中國電信");put(137689, "中國電信");put(137692, "中國電信");put(140636, "中國電信");put(140638, "中國電信");put(140345, "中國電信");put(38283, "中國電信");put(140292, "中國電信");put(140903, "中國電信");put(17897, "中國電信");put(134762, "中國電信");put(139019, "中國電信");put(141739, "中國電信");put(141771, "中國電信");put(134419, "中國電信");put(140276, "中國電信");put(58542, "中國電信");put(140278, "中國電信");put(139767, "中國電信");put(137688, "中國電信");put(137691, "中國電信");put(4809, "中國電信");put(58466, "中國電信");put(137687, "中國電信");put(134756, "中國電信");put(134760, "中國電信");put(133774, "中國電信");put(133775, "中國電信");put(4816, "中國電信");put(134768, "中國電信");put(58461, "中國電信");put(58519, "中國電信");put(58520, "中國電信");put(131325, "中國電信");// 中國聯通put(4837, "中國聯通");put(4808, "中國聯通");put(134542, "中國聯通");put(134543, "中國聯通");put(10099, "中國聯通");put(140979, "中國聯通");put(138421, "中國聯通");put(17621, "中國聯通");put(17622, "中國聯通");put(17816, "中國聯通");put(140726, "中國聯通");put(17623, "中國聯通");put(136958, "中國聯通");put(9929, "中國聯通");put(58519, "中國聯通");put(140716, "中國聯通");put(4847, "中國聯通");put(136959, "中國聯通");put(135061, "中國聯通");put(139007, "中國聯通");// 云服務商put(59019, "金山云");put(135377, "優刻云");put(45062, "網易云");put(137718, "火山引擎");put(37963, "阿里云");put(45102, "阿里云國際");put(45090, "騰訊云");put(132203, "騰訊云國際");put(55967, "百度云");put(38365, "百度云");put(58519, "華為云");put(55990, "華為云");put(136907, "華為云");// 其他運營商put(4609, "澳門電訊");put(134773, "珠江寬頻");put(1659, "臺灣教育網");put(8075, "微軟云");put(17421, "中華電信");put(3462, "HiNet");put(13335, "Cloudflare");put(55960, "亞馬遜云");put(14618, "亞馬遜云");put(16509, "亞馬遜云");put(15169, "谷歌云");put(396982, "谷歌云");put(36492, "谷歌云");}};@Autowiredpublic GeoIPServiceImpl(Reader cityReader, Reader asnReader, Reader cnReader) {this.cityReader = cityReader;this.asnReader = asnReader;this.cnReader = cnReader;}@Overridepublic IPInfo getIpInfo(String ip) {IPInfo info = new IPInfo();info.setIp(ip);try {InetAddress ipAddress = InetAddress.getByName(ip);// 處理ASN信息processAsnInfo(ipAddress, info);// 處理City信息Map<String, Object> cityInfo = cityReader.get(ipAddress,Map.class);if (cityInfo != null) {processCityInfo(cityInfo, info);// 如果是中國IP,處理GeoCN數據庫if (isChinaIp(info)) {processCnInfo(ipAddress, info);}}} catch (Exception e) {e.printStackTrace();info.setError(e.getMessage());}return info;}@Overridepublic IPInfo getIpInfo(HttpServletRequest request) {String ip = Optional.ofNullable(request.getHeader("X-Forwarded-For")).map(xff -> xff.split(",")[0]).orElse(request.getRemoteAddr());return getIpInfo(ip);}private void processAsnInfo(InetAddress ipAddress, IPInfo info) throws IOException {Map<String, Object> asnInfo = asnReader.get(ipAddress,Map.class);if (asnInfo == null) return;ASInfo as = new ASInfo();as.setNumber(Integer.parseInt(asnInfo.get("autonomous_system_number").toString()));as.setName(asnInfo.get("autonomous_system_organization").toString());String ispName = getAsInfo(as.getNumber());if (ispName != null) {as.setInfo(ispName);}info.setAs(as);}private void processCityInfo(Map<String, Object> cityInfo, IPInfo info) {// 處理網絡前綴Integer prefix = (Integer) cityInfo.get("prefix_len");if (prefix == null) prefix = 24; // 默認值info.setAddr(getAddr(info.getIp(), prefix));// 處理國家信息if (cityInfo.get("country") != null) {Map<String, Object> country = (Map<String, Object>) cityInfo.get("country");CountryInfo countryInfo = new CountryInfo();countryInfo.setCode((String) country.get("iso_code"));countryInfo.setName(getCountryName(country));info.setCountry(countryInfo);}// 處理注冊國家信息if (cityInfo.get("registered_country") != null) {Map<String, Object> regCountry = (Map<String, Object>) cityInfo.get("registered_country");CountryInfo regCountryInfo = new CountryInfo();regCountryInfo.setCode((String) regCountry.get("iso_code"));regCountryInfo.setName(getCountryName(regCountry));info.setRegisteredCountry(regCountryInfo);}// 處理地區信息List<String> regions = new ArrayList<>();if (cityInfo.get("subdivisions") != null) {List<Map<String, Object>> subdivisions = (List<Map<String, Object>>) cityInfo.get("subdivisions");for (Map<String, Object> subd : subdivisions) {regions.add(getDescription(subd));}}// 處理城市信息if (cityInfo.get("city") != null) {Map<String, Object> city = (Map<String, Object>) cityInfo.get("city");String cityName = getDescription(city);if (regions.isEmpty() || !regions.get(regions.size() - 1).contains(cityName)) {regions.add(cityName);}}info.setRegions(deDuplicate(regions));}private void processCnInfo(InetAddress ipAddress, IPInfo info) throws IOException {Map<String, Object> cnData = cnReader.get(ipAddress,Map.class);GeoCNResponse cnInfo = new GeoCNResponse((String) cnData.get("province"),(String) cnData.get("city"),(String) cnData.get("districts"),(String) cnData.get("isp"),(String) cnData.get("net"),(Integer) cnData.getOrDefault("prefix_len", 24));if (cnInfo == null) return;// 更新地址信息info.setAddr(getAddr(info.getIp(), cnInfo.getPrefixLen()));// 處理地區信息List<String> regions = new ArrayList<>();if (cnInfo.getProvince() != null) regions.add(cnInfo.getProvince());if (cnInfo.getCity() != null) regions.add(cnInfo.getCity());if (cnInfo.getDistricts() != null) regions.add(cnInfo.getDistricts());info.setRegions(deDuplicate(regions));// 處理簡短地區信息List<String> regionsShort = new ArrayList<>();if (cnInfo.getProvince() != null) regionsShort.add(provinceMatch(cnInfo.getProvince()));if (cnInfo.getCity() != null) regionsShort.add(cnInfo.getCity().replace("市", ""));if (cnInfo.getDistricts() != null) regionsShort.add(cnInfo.getDistricts());info.setRegionsShort(deDuplicate(regionsShort));// 更新ISP信息if (info.getAs() == null) {info.setAs(new ASInfo());}info.getAs().setInfo(cnInfo.getIsp());// 設置網絡類型if (cnInfo.getNet() != null) {info.setType(cnInfo.getNet());}}// 輔助方法 (與Python版本完全一致的功能)private String getAsInfo(Integer number) {return ASN_MAP.get(number);}private String getDescription(Map<String, Object> geoObj) {if (geoObj.get("names") == null) return "";Map<String, String> names = (Map<String, String>) geoObj.get("names");for (String l : lang) {if (names.containsKey(l)) {return names.get(l);}}return names.get("en");}private String getCountryName(Map<String, Object> country) {String name = getDescription(country);if (Arrays.asList("香港", "澳門", "臺灣").contains(name)) {return "中國" + name;}return name;}private String provinceMatch(String s) {for (String province : provinces) {if (s.contains(province)) {return province;}}return "";}private List<String> deDuplicate(List<String> regions) {return regions.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());}private String getAddr(String ip, int prefix) {try {// 簡化實現,實際應使用類似Python的ipaddress計算return ip + "/" + prefix;} catch (Exception e) {return ip + "/" + prefix;}}private boolean isChinaIp(IPInfo info) {return info.getCountry() != null &&"CN".equals(info.getCountry().getCode()) &&(info.getRegisteredCountry() == null ||"CN".equals(info.getRegisteredCountry().getCode()));}
}
接口中調用即可。
項目倉庫地址
https://gitee.com/Super_TongYao/offline-ip-library.git
Java運行后訪問:http://localhost:9000/api/ip/8.8.8.8