前言: 前段时间听过了纯真ip数据库,只知道是一个qqwry.dat文件,里面有一些网友收集的数据,可以用来查询ip的大致位置,即ip定位。正好,我最近在一个项目里面看到了这个数据库,它就是将qqwry.dat集成到springboot项目里面,做成一个查询服务来使用的。所以,我也想来试试,但是我不是直接使用qqwry.dat这个文件。我换一种方式,我才用将qqwry.dat中的ip数据导入mysql数据库中,通过数据库的查询来提供ip位置查询功能。

gitee代码地址: 纯真ip库服务搭建

基本介绍

纯真ip数据库

纯真数据库收集了包括中国电信、中国移动、中国联通、长城宽带、聚友宽带等 ISP 的 IP 地址数据,包括网吧数据。希望能够通过大家的共同努力打造一个没有未知数据,没有错误数据的QQ IP。IP数据库每5天更新快速,请大家定期更新IP数据库!

因为IP地址数据是民间收集的,各运营商也会不时的更改IP段,所以有点遗漏、错误是难免的。随数据库附送IP解压,查询软件。假如发现IP地址有不对的,或想提供新的IP地址,请到纯真IP小秘书提供IP地址数据,或者登陆纯真时空论坛“电脑板块”中的“代理及IP板”块告诉我们。以便及时更新IP数据库。谢谢!

这个数据库目前2020.12月版本的含有52万+条ip数据。包括了0.0.0.0-255.255.255.255的所有ip,但是我们都知道ipv4地址总数是:2^32,大概42亿多条。所以,这里的52多万条只是一部分,它是按照ip地址段来区分的,可以这样来理解:把0.0.0.0-255.255.255.255想象成坐标轴上一个长度为42亿的区间,然后分成52万份(ip段),每一份属于一个地区。 它的作用就算是这个,通过ip知道此ip属于哪个地区(当然了,精度不是太高,不适合于精确定位。)

注意:
1.一个ip段是连续的,包括一个ip段起始地址和一个ip段结束地址。
2.纯真数据库对于国外的ip地址,只能定位到国家,对于国内的ip地址是可以有省市县的,毕竟主要收集的还是国内的地址。

关于ip定位的精度:

因为ip地址(发达国家就占据了大部分了)不够用,所以它实际上是动态分配的,所以一个人的ip一直都会变化的,但是在一段时间内是不变的(政府、学校、公司等机构会具有固定的ip,个人一般没有这个权力),所以定位精度不是很好,但是如果你在国内发现某个人的ip地址在国外,这并不是数据出了问题,而是因为他的ip地址基本就是在国外。但是,ip地址的分配记录是保存下来的,如果有法律权限是可以查看的,因此上网需要规范言行,互联网不是法外之地,举头三尺有神明!

如何使用
我们的ip地址必在0.0.0.0-255.255.255.255之间(这里不考虑ipv6的地址),所以只要判断某个ip是否在ipstart和ipend之间就行了(即一个ip段内),就知道它属于哪里了。

sql查询方式即为:

注:INET_ATON函数 会将一个有效的ip地址转成整数,所以不用自己手动转换ip地址了,直接传递点分式ip地址就行了。

但是不推荐这样使用,前面那种方式是直接将ip转为整数存入数据库,下面这种方式是直接存入ip的点分式,然后查询时转换。这样的话,速度非常慢,因为它相当于要对每一个ip地址做转换才能查询,这是错误的使用方式。

数据准备

首先,需要准备qqwry.dat这个文件,因为数据在它里面。但是,我发现了一个更好用的软件,这里就不直接使用这个文件了。

注意:鉴于网络上面资源下载容易踩坑,这里推荐到这个网站去下载,点击页面中的下载即可。如果有能力的话,也可以选择捐助作者。

下载完成之后,解压安装这个软件,就可以使用ip查询了。但是它是一个桌面软件,我们这里的目的是做成web服务,即像这个网站一样的查询服务。这样的ip查询服务有很多,但是如果使用量大的话,自己搭建一个还是非常好的,成本低、可靠!并且,这个数据库是一直在更新的,而且更新频率也还高,说明它的效果还是很好的。

测试一下,结果同上面的web页面

这里我们不直接使用这个软件,而是使用它的一个功能,在主界面上有一个解压按钮,点击将ip数据导出成文本(我这里命名成qqwry.txt)。


qqwry.txt的内容格式如下

因为这个qqwry.txt的格式,直接在数据库中查询不方便,所以需要处理一下,它目前的格式是:4列(ip段起始地址 ip段结束地址 area地区 location位置)。
如果直接将它们存入数据库中,那么查询就会变得麻烦,而且基本上也无法使用索引了,对于性能影响很大。这里我们采用网上别人的方式,将ip起始地址和ip结束地址转成数字。

所以需要先使用程序对上面的文本数据进行处理一下,我看别人的博客处理数据都是基于php的,但是我并不会世界上最好的语言,所以我就采用java来处理了。

注意:由于java语言没有无符号数的概念,所以ip地址并不能转成java的int型。ipv4地址是32位,int型也是32位,但是java的int中第一位是符号位,所以实际可以表示的大小是其它语言中int的一半。这也导致了,如果将ip地址转换为int的话,会有许多数据溢出,变成负数,所以这里需要将ip地址转成long型。

数据处理

数据处理代码

package re;import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.stream.Collectors;public class IPRecord {private static final String regex = "\\s+";private static final int limit = 4;   // split方法的这个参数的作用很值得深入学习一下private static final String ipRegex = "\\.";private static class Record {String ipStartStr;String ipEndStr;long ipStart;long ipEnd;String area;String location;public Record(String[] record) {this.ipStartStr = record[0];this.ipEndStr = record[1];this.ipStart = ip2Long(record[0]);   // 将ip地址字符串转换成longthis.ipEnd = ip2Long(record[1]);     // 将ip地址字符串转换成longthis.area = record[2];this.location = record[3];}@Overridepublic String toString() {return ipStart + "," + ipEnd + "," + area + "," + location + "," + ipStartStr + "," + ipEndStr;  // 转成csv格式}public String toRedis() {return "ZADD\tqqwry\t" + ipEnd + "\t" + "{\"ipstart\":" + ipStart + ",\"ipend\":" + ipEnd+ ",\"area\":" + "\"" + area + "\""+ ",\"location\":" + "\"" + location + "\""+ ",\"ipstartstr\":" + "\"" + ipStartStr + "\""+ ",\"ipstartend\":" + "\"" + ipEndStr+ "\"}";}}public static void main(String[] args) throws IOException {// qqwry.tx 的文件路径Path path = Paths.get("C:/Users/Alfred/Desktop/data/test/qqwry.txt");// 使用指定字符集(UTF-8)读取文件List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);//  输出测试,注意不要省略 limit,因为总共有 52万+行数据,直接输出的话,压力比较大。
//      lines.stream().map(IPRecord::split).limit(10).map(Record::toJson).forEach(System.out::println);// 将文件转为csv文件存放long start = System.currentTimeMillis();lines = lines.stream().map(IPRecord::split).map(Record::toString).limit(528129)     // 有效数据行数,剩下的是一些空行和最后的统计信息.collect(Collectors.toList());//       String csvHeader = "ipstart,ipend, area, location, ipstartstr, ipendstr\n";
//      Files.write(Paths.get("C:/Users/Alfred/Desktop/data/test/qqwry.csv"), csvHeader.getBytes(), StandardOpenOption.CREATE);            //写入csv文件的头部Files.write(Paths.get("C:/Users/Alfred/Desktop/data/test/qqwry_redis.csv"), lines, StandardCharsets.UTF_8, StandardOpenOption.CREATE);  //写入csv文件的主体部分System.out.println("Execute Success! Time: " + (System.currentTimeMillis()-start) + " ms");}public static Record split(String record) {return new Record(record.split(regex, limit));}/*** int类型是32位的,但是无法直接将ip和int类型进行转换,因为可能会溢出。* int类型是有符号的,除非这里有无符号整型,但是Java是没有这个的,所以*    这里需要转换成long型。* */public static long ip2Long(String ip) {String[] strs = ip.split(ipRegex);int[] bs = new int[4];for (int i = 0; i < 4; i++) {bs[i] = Integer.parseInt(strs[i]);  // Byte.parseByte(strs[i]); 这个方法转成的字节范围是 -128-127,真是太坑了!}return ((bs[3] & 0xFFL)      ) +        // 这个转换方法是从jdk源码里面偷来的,挺好用的!((bs[2] & 0xFFL) <<  8) +((bs[1] & 0xFFL) << 16) +((bs[0] & 0xFFL) << 24);}
}// 默认的split(regex)方法会以split(regex,0)调用
// 当limit N 大于0时,会匹配N-1次,数组长度最大为N,返回数组的最后一个元素会把数组最后的所有字符包括进来
// 当limit N 小于0时,会匹配尽可能多次,返回最大长度的数组
// 当limit N 等于0时,会匹配尽可能多次,返回最大长度的数组,并且末尾的空串不会包含进来//推荐使用正则表达式,速度飞起
//String record = "0.0.0.0         0.255.255.255   IANA 保留地址";
//String regex = "\\s+";
//long start = System.currentTimeMillis();
//for (String s : record.split(regex)) {//  System.out.println(s);
//}
//System.out.println(System.currentTimeMillis()-start);//这种方式太慢了,简直难以忍受!
//List<String> strList = new ArrayList<>();
//boolean flag = true;
//StringBuilder sb = new StringBuilder();
//
//char[] chs = record.toCharArray();
//for (int i = 0; i < chs.length; i++) {//  if (chs[i] != ' ') {//      sb.append(chs[i]);
//      if (i == chs.length - 1) {//          strList.add(sb.toString());
//          break;
//      }
//      flag = true;
//  } else {//      if (flag) {//          strList.add(sb.toString());
//          sb.delete(0, sb.length());
//      }
//      flag = false;
//  }
//}
//strList.forEach(System.out::println);

注意: toString方法是转成符合数据库导入格式的数据,toRedis是转成符号redis导入的格式(我不太确定这种方式的效率,并且我最终没有采用这种方法)。

说明:

1.这里的处理逻辑很简单,就是将文件分行读入内存,然后对每一行切分成4块,然后调用编写的ip2Long方法进行处理即可。
2.如果可以使用正则表达式,尽量用正则表达式,特别是这种数据处理,速度差距非常之大!
3.这个ip2Long方法,是我从jdk的ObjectInpustream里面拿来的,随便修改了一下,原来的代码使用的是byte,但是这里如果传入一个尝试把一个大于127的数转成int会抛出异常,所以使用int数组代替了byte数组,并且由于我这里只有32位,所以我就删除了后32位的处理代码。

执行结果:
这个notepad++打开大文件感觉还行,40多m也能流畅使用。我这里使用excel打开就比较慢了,而且中文乱码了,就不放截图了。如果使用excel打开的话和普通的excel文件显示没有什么区别。我这里转成csv的原因是:
a).csv文件本身就是文本格式,使用程序直接输出即可。
b).我使用的sqlyog社区版无法导入excel数据,只能导入csv格式的数据。

csv格式数据

redis格式的数据,直接就是一条命令,但是它总共是52万+的数据,想必一条一条的执行,时间上也会接受不了,但是这个给我了启发,我后来在redis中使用了这种格式来存储值(json数据)。

redis格式数据(没有使用)

数据入库

因为我使用的是sqlyog,数据导入有一点麻烦,如果使用其它的数据库客户端应该会简单一些。

建库建表

这个建表语句参考了别人的博客,做了一些修改(添加了原始的ip起始地址和结束地址、修改了表的字符编码)

CREATE TABLE `ip_data` (
`ipstart`  INT(10) UNSIGNED NOT NULL,                                                # 开始ip地址
`ipend`  INT(10) UNSIGNED NOT NULL,                                                  # 结束ip地址
`area`  VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,       # 区域
`location`  VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,  # 位置
`ipstartstr` VARCHAR(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,  # 开始ip地址的点分表示方式
`ipendstr` VARCHAR(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,    # 结束ip地址的点分表示方式
PRIMARY KEY (`ipstart`),
INDEX `ip` (`ipstart`, `ipend`) USING BTREE
)
ENGINE=INNODB
DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_general_ci;

导入数据

我直接使用sqlyog的导入csv,但是没有成功。它的图形界面导入功能感觉就很鸡肋,我就没成功过,最后没办法,从网上查找到了如下命令,然后导入了csv文件,速度还行!

LOAD DATA LOCAL INFILE "C:/Users/Alfred/Desktop/data/test/qqwry.csv"
INTO TABLE  `poem_land`.`qqwry_ip`  FIELDS TERMINATED BY ',' LINES TERMINATED BY '\r\n';

数据查询

数据行数量

简单查看几行数据

查询数据

好了,还算不错!

数据存入redis

关系型数据库的查询效果可能不够理想,这里使用redis这个nosql来提高查询性能,首先要做的还是数据导入。我发现这个数据导入真的是麻烦哎,昨天这个东西搞了我好久,当然也因为我对redis并不熟悉。
上面说了,我没有采用那种一条一条执行命令的方式,而是使用了一种更加快速的方式——redis的管道技术。网上看了好多资料才搞成,第一次听说这个东西,效果确实很好!这里我们要做的是使用redis的管道技术将mysql数据导入到redis。

导入数据命令

sudo mysql -uroot -p'password' poem_land --skip-column-names --raw < data.sql | ./redis-4.0.11/src/redis-cli -h host -p port -a password --pipe

参数:
password:mysql数据库密码、redis密码
host:redis所在主机
port:redis所用端口
data.sql:一个sql文件,执行它会将数据库数据转成适合于redis导入的形式。

data.sql 文件

注意: redis_member 就是存入redis的值,这里我采用数据库的CONCAT函数把所有列拼成了一个json字符串的形式,这样的话查询就可以获取到全部数据了,我是第一次这样用,不知道其它更好的方式(不过,导入时间却耗费了好几分钟,估计是拼接字符串影响了性能)。

SELECT CONCAT('*4\r\n','$',LENGTH(redis_cmd),'\r\n',redis_cmd,'\r\n','$',LENGTH(redis_key),'\r\n',redis_key,'\r\n','$',LENGTH(redis_score),'\r\n',redis_score,'\r\n','$',LENGTH(redis_member),'\r\n',redis_member,'\r'
) FROM (SELECT 'ZADD' AS redis_cmd,'qqwry' AS redis_key,ipend AS redis_score,CONCAT('{"ipstart":', ipstart, ',"ipend":', ipend,',"area":"', `area`, '","location":"', location, '"}') AS redis_member FROM `poem_land`.`qqwry_ip`
) AS NAME

导入结果

查询数据

Springboot结合使用

我最终的目的,还是要提供一个查询的接口来使用。现在就将它作为服务提供了,让我们开始吧!

控制器代码

这里我使用的是mybatis_plus和它的生成器,所以代码是自动生成的,基本上只需要看这个Controller的代码就行了。

package org.dragon.ip_seek.controller;import java.util.Map;
import java.util.Objects;
import java.util.Set;import javax.servlet.http.HttpServletRequest;import org.dragon.ip_seek.entity.QqwryIp;
import org.dragon.ip_seek.service.IQqwryIpService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;/*** <p>*  前端控制器* </p>** @author Alfred* @since 2020-12-08*/
@RestController
@RequestMapping("/seeker/")
public class QqwryIpController {private static final Logger logger = LoggerFactory.getLogger(QqwryIpController.class);private static final String ipRegex = "\\.";   // 匹配ip的正则表达式@Autowiredprivate IQqwryIpService iqqwryIpService;    // mysql 服务调用@Autowiredprivate RedisTemplate<String, String> redisTemplate;  // redis 服务调用@GetMapping(value = "/poem1", produces = MediaType.APPLICATION_JSON_VALUE)public String test01(HttpServletRequest request) {// 本地局域网测试,ip地址是内网地址,无法使用,这里我先传递一个模拟的ip地址。String host = request.getRemoteHost();logger.info("当前用户的ip地址为:{}", host);Map<String, String> ipInfo = iqqwryIpService.getIpInfo("60.174.231.10");ipInfo.forEach((k, v) -> {logger.info("K:{} V:{}", k, v);});return "{\"poem\": \"一日不见兮,思之如狂。\"}";}@GetMapping(value = "/poem_mysql", produces = MediaType.APPLICATION_JSON_VALUE)public Map<String, Object> test02(HttpServletRequest request) {String host = request.getRemoteHost();      // 本地局域网测试,ip地址是内网地址,无法使用,这里我先传递一个模拟的ip地址。// 如果部署到云服务器上,去掉此行即可。host = "60.174.231.10";LambdaQueryWrapper<QqwryIp> wrapper = Wrappers.lambdaQuery();// 忘记了,eclipse自己的编译器不支持这个 SFunction接口wrapper.select(QqwryIp::getArea, QqwryIp::getLocation).apply("INET_ATON({0}) between ipstart and ipend", host);Map<String, Object> info = iqqwryIpService.getMap(wrapper);Map<String, Object> ipInfoMap = new java.util.LinkedHashMap<>();   // 我想要指定map序列化的键值对顺序为:ip area locationipInfoMap.put("ip", host);if (Objects.nonNull(info)) {ipInfoMap.putAll(info);}logger.info("通过mysql查询 ip: {}", ipInfoMap.get("ip"));return ipInfoMap; // "{\"poem\": \"一日不见兮,思之如狂。\"}";}/*** 采用 Redis 缓存的方式进行服务调用* @param <T>* */@GetMapping(value = "/poem_redis", produces = MediaType.APPLICATION_JSON_VALUE)public Map<String, Object> test03(HttpServletRequest request) {String host = request.getRemoteHost();host = "116.62.188.176";   // 本来想测试一个错误数据的:256.256.266.255,结果long都溢出了,直接变成了0// 从redis中查询数据Set<String> resultSet = redisTemplate.opsForZSet().rangeByScore("qqwry", ip2Long(host), Double.MAX_VALUE, 0, 1);logger.info("通过reids查询 ip: {}", host);return transfer(resultSet, host);}/*** 对redis查询数据做一个转换,使之格式更加友好* */@SuppressWarnings("unchecked")private Map<String, Object> transfer(Set<String> resultSet, String host) {Map<String, Object> ipInfoMap = new java.util.LinkedHashMap<>();   // 我想要指定map序列化的键值对顺序为:ip area locationipInfoMap.put("ip", host);if (resultSet.isEmpty()) {ipInfoMap.put("area", "NAN");ipInfoMap.put("location", "NAN");return ipInfoMap;}String result = resultSet.toArray(new String[0])[0];Map<String, Object> info = null;ObjectMapper mapper = new ObjectMapper();try {info = mapper.readValue(result, Map.class);  // 这里泛型会被擦除,直接赋值会报编译器警告,但是我也不知道Class<T>这种参数该如何传递了,就先抑制一下编译器警告吧if (Objects.nonNull(info)) {ipInfoMap.putAll(info);}} catch (JsonMappingException e) {e.printStackTrace();} catch (JsonProcessingException e) {e.printStackTrace();}return ipInfoMap;}private long ip2Long(String ip) {String[] strs = ip.split(ipRegex);int[] bs = new int[4];for (int i = 0; i < 4; i++) {bs[i] = Integer.parseInt(strs[i]);  // Byte.parseByte(strs[i]); 这个方法转成的字节范围是 -128-127,真是太坑了!}return ((bs[3] & 0xFFL)      ) +        // 这个转换方法是从jdk源码里面偷来的,挺好用的!((bs[2] & 0xFFL) <<  8) +((bs[1] & 0xFFL) << 16) +((bs[0] & 0xFFL) << 24);}
}

说明:

  1. test01是传统的mybatis的写法,后来发现mybatis_plus可以使用更加简单的方法,就换成了test02,但是保留了test01。
  2. test03是从redis中进行数据查询。

效果演示

启动项目

test01——mysql

这里查询的结果,我是使用在日志上了,本来是打算在后台使用的,但是暂时也没啥想法,就算了。

test02——mysql

test03——redis

注意:因为我本地测试,它获取的是本地的ip地址(127.0.0.1),所以我就在后台硬编码了一个公网ip地址来查询。


PS

我最近看到了一个基于java的开源软件——JMeter。它可以用来做压力测试,但是我并不会。但是,这并不妨碍我来玩一玩!

下面的内容仅供参考,我只是玩一玩!

步骤:
启动JMeter,然后创建一个线程组(1000个线程,50s,循环两次),添加一个Http请求,添加一个查看结果树,添加一个聚合报告,启动。

接口:poem_mysql

接口:poem_redis

说明
这里是两千个请求,从聚合报告里面看似乎redis确实性能是远远超过了mysql(我听别人说也是这样的),但是由于这个测试不严谨,我也不会JMeter,所以就不作为一个有效的参考了。

参数资源:

纯真数据库查询
查询网ip查询
通过管道传输快速将MySQL的数据导入Redis
使用redis有序集合搭建自有ip定位解析库
将mysql表数据批量导入redis zset结构中
IPLook解析纯真数据库,我没有使用这个软件,因为我是手动解析数据
mysql快速导入大量数据问题
一个简单的方式,但是有效率问题
ip location介绍
redis的简单配置
jackson json map object 之间互相转换

利用纯真ip库搭建ip查询服务相关推荐

  1. Java实现IP库归属地查询

    一.IP库文件下载,各产品对比 二.IP库的实现有很多种,我这里采用的是GeoIP2 Jar 包依赖引入 下载好IP库文件 代码实现 运行结果 三.工程代码下载 参考文章 一.IP库文件下载,各产品对 ...

  2. php基于新浪ip库获取城市,PHP基于新浪IP库获取IP详细地址的方法

    [导读]这篇文章主要介绍了PHP基于新浪IP库获取IP详细地址的方法,涉及php正则.curl及编码转换相关操作技巧 本文实例讲述了PHP基于新浪IP库获取IP详细地址的方法.代码如下<?php ...

  3. python 使用ip库定位IP

    # python 示例代码 import os     from ipip import IP     from ipip import IPX IP.load(os.path.abspath(&qu ...

  4. 利用微信公众号搭建天气查询

    要求: 1.注册微信公众号平台 2.注册新浪云并安装python2.7应用 要实现利用微信公众号来查询天气,首先得将微信公众号平台与欣康运 转载于:https://www.cnblogs.com/bu ...

  5. redis缓存原理与实现_基于Redis实现范围查询的IP库缓存设计方案

    点击上方"码农沉思录"  发现更多精彩我先说下结果.我现在还不敢放线上去测,这是本地测的数据,我4g内存的电脑本地开redis,一次都没写完过全部数据,都是写一半后不是redis挂 ...

  6. php获取用户ip地理位置利用淘宝IP库

    利用淘宝IP库获取用户ip地理位置 我们查ip的时候都是利用ip138查询的,不过那个有时候是不准确的,还不如自己引用淘宝的ip库来查询,这样准确度还高一些.不多说了,介绍一下: 淘宝IP地址库 淘宝 ...

  7. ip库下载 mysql_IP地址库最新下载(最详细准确的ip数据库)

    IP地址库最新下载(最详细准确的ip数据库) (2010-03-21 10:07:05) 标签: 财经 [翼南]IP数据库 下载地址: http://www.wingsofts.com/html_cn ...

  8. ip对应的区域查询(php版)(转)

    <?php /** *@ Date 2010.12.21 *@ Author 王刚 *@ Email 373882774@qq.com *@ QQ 373882774 *@ Blog http: ...

  9. python查询ip归属地_基于Python的免费IP地址归属地查询

    一.开通接口 IP地址归属地查询服务使用聚合数据提供的免费接口,每天可以100次免费调用.可以通过 https://www.juhe.cn/docs/api/id/1 注册及开通. 二.请求接口 #! ...

  10. 使用淘宝IP库获取用户ip地理位置

    为什么80%的码农都做不了架构师?>>>    以前用过GOOGLE地图和百度地图获取过用户地理位置,现在又多了一个方法,那就是使用淘宝IP库获取用户ip地理位置,一起来看代码吧. ...

最新文章

  1. (转)所有iOS设备的屏幕分辨率
  2. python网络爬虫实战 吕文翔_实战Python网络爬虫
  3. react-native热更新插件react-native-code-push
  4. HDOJ 1896 Stones
  5. 北美程序员面试常见问题
  6. 入门几款好的UML工具介绍
  7. ASP.NET Cache缓存管理基于web的缓存
  8. tp5模板使用php函数,tp5 自定义公共函数,前台模板调用
  9. Gibbs 采样的应用
  10. Jekyll搭建lanmps.com站点
  11. 颜色对照表(四)(16进制、RGB、CMYK、HSV、中英文名)
  12. 【毕业设计】python+opencv+机器学习车牌识别
  13. pyltp实体识别_命名实体识别,使用pyltp提取文本中的地址
  14. 学计算机颈椎,电脑颈
  15. 风险加权资产(RWA)分析管理系统
  16. 网络管理人员应该掌握的技术
  17. 战双帕弥什登入显示服务器错误,战双帕弥什无法登陆怎么办 进不了游戏解决方法...
  18. compare用法java_Java中的Compare和Comparator的使用方法
  19. 央企建筑施工企业职工教育培训数字化解决方案
  20. WSDM 2022最佳论文候选:港大提出多行为对比元学习的推荐系统

热门文章

  1. vscode中微信小程序的代码智能提示
  2. C程序设计(第四版)谭浩强著-学习笔记
  3. 如何ajax上传文件,如何实现Ajax文件上传功能
  4. 【软件开发】软件开发注意事项
  5. 汉诺塔函数python_Python使用函数模拟“汉诺塔”过程
  6. Hierarchical Features Driven Residual Learning for Depth Map Super-Resolution 2019TIP 论文阅读
  7. 点集凸包算法python实现
  8. javase和java区别_javase和java有什么区别
  9. oracle基于脚本的安装失败,脚本建库导致 Oracle 组件未安装故障案例
  10. 超详细|一篇搞定操作系统——处理器管理