使用淘宝IP库为智能DNS收集中国ISP信息

题记:2017第一弹~

简介

本站是先收集了中国所有的公有IP地址段,众所周知中国的IP地址是由APNIC(亚太网络信息中心)分配的,APNIC专门负责亚洲和太平洋地区的IP地址和AS号分配,受到 IANA(互联网地址分配机构) 的管理。所以本站先从http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest下载到本地作为src.html
这里将所有的中国公网IP地址信息都进行提取过滤

具体生成器函数如下:

1
2
3
4
5
6
7
8
9
10
def net_catch():
with open('src.html', 'r') as f:
for line in f:
if line.startswith('apnic|CN|ipv4'):
'''
the line example:
apnic|IN|ipv4|103.27.84.0|1024|20130701|assigned
'''
net,cnt = line.strip().split('|')[3:5]
yield net,int(32-log(float(cnt))/log(2))

src.html里面包含了世界上所有的公网网段地址,形如:

1
2
3
4
5
6
apnic|LK|ipv4|203.189.184.0|2048|20060515|allocated
apnic|CN|ipv4|203.189.192.0|8192|20110412|allocated
apnic|BD|ipv4|203.189.224.0|2048|20000111|allocated
apnic|CN|ipv4|203.189.232.0|1024|20151113|allocated
apnic|CN|ipv4|203.189.236.0|1024|20151113|allocated
apnic|CN|ipv4|203.189.240.0|1024|20151113|allocated

上述条目由如下字段构成:

分配机构 | 国家代码|ip版本| 网段 | 包含ip地址数量 | 分配时间 | 状态

我们只需要找到所有以apnic|CN|ipv4开始的行,并以int(32-log(float(cnt))/log(2)这个计算网段的掩码即可,并生成网段地址和掩码

在这个生成器之上我们就可以轻易生成中国所有的公网IP地址段,我们可以将其写入一个文件

1
2
3
4
5
6
7
8
#
def net_list_file():
if exists('netlist.file'):
os.remove('netlist.file')
with open('netlist.file','a+') as f:
for net,mask in net_catch():
f.write('/'.join([net,str(mask)]) + '\n')
print 'all net has been write to file'

利用淘宝IP地址库对网段信息进行查询

对淘宝IP库的简介

目前提供的服务包括:

  1. 根据用户提供的IP地址,快速查询出该IP地址所在的地理信息和地理相关的信息,包括国家、省、市和运营商。
  2. 用户可以根据自己所在的位置和使用的IP地址更新我们的服务内容。
    优势:
  3. 提供国家、省、市、县、运营商全方位信息,信息维度广,格式规范。
  4. 提供完善的统计分析报表,省准确度超过99.8%,市准确度超过96.8%,数据质量有保障

由于淘宝ip地址库支持rest api,所以我们很方便对我们想要的信息进行采集,先看下他的接口

  • 请求接口(GET):
    /service/getIpInfo.php?ip=[ip地址字串]
  • 响应信息:
    (json格式的)国家 、省(自治区或直辖市)、市(县)、运营商等等
  • 返回数据格式:
    1
    2
    3
    4
    {"code":0,"data":{"ip":"210.75.225.254","country":"\u4e2d\u56fd","area":"\u534e\u5317",
    "region":"\u5317\u4eac\u5e02","city":"\u5317\u4eac\u5e02","county":"","isp":"\u7535\u4fe1",
    "country_id":"86","area_id":"100000","region_id":"110000","city_id":"110000",
    "county_id":"-1","isp_id":"100017"}}

这是一串json数据,格式化后便于我们对数据特点进行分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"code": 0,
"data": {
"ip": "210.75.225.254",
"country": "中国",
"area": "华北",
"region": "北京市",
"city": "北京市",
"county": "",
"isp": "电信",
"country_id": "86",
"area_id": "100000",
"region_id": "110000",
"city_id": "110000",
"county_id": "-1",
"isp_id": "100017"
}
}

其中code的值的含义为,0:成功,1:失败。
data中包含了我们想要的信息,有ip地址(GET查询的IP地址,下面的信息都是针对此ip地址),国家,地区,省,市,isp信息,国家id,地区id,省id,市id,isp id信息

我们可以用之前得到的所有中国公网IP地址段,或者用他们的第一个主机位,借助这个地址库进行查询,并收集isp信息,我们可以以ISP name为名,并将查询到的isp信息追加写入到对应的文件中

我在抓取的过程中发现有的IP是没有ISP的,即isp为一个空值,我在逻辑的处理上将其写入到了一个error.txt

部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def ip_resolve(ip,mask,retry_num=2):
r = requests.get('http://ip.taobao.com/service/getIpInfo.php?ip={}'.format(
ip),timeout=0.09)
if r.status_code == 200 and r.json().get('code') == 0:
isp_name = r.json()['data']['isp']
if isp_name:
write2file(ip,mask,isp_name)
else:
error2file(ip, mask)
elif retry_num > 0:
time = retry_num -1
ip_resolve(ip, mask,time)
else:
error2file(ip,mask)
def error2file(ip,mask):
with open('error.txt','a+') as f:
f.write(ip + '/' + str(mask) + 'catch error\n')
def write2file(ip,mask,isp_name):
print isp_name
if isp_name:
ip.split('.')[:3].append('0')
with open(isp_name+'.acl','a+') as f:
f.write(ip + '/' + str(mask) + '\n')
else:
error2file(ip,mask)

由于淘宝ip库限制了REST API查询的速率10QPS,所以目前我只设置了延时timeout时间为0.09s,此处应该还需要考虑代理,然而我并没有这么做

view设置

脚本运行后会在目录下生成很多acl文件,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
263网络.acl 阿里云.acl 歌华网络.acl 可口可乐网络.acl 上海信息网络.acl 网宿科技.acl 有线通.acl
async_net_catch.py 安徽省教育厅.acl 光环新网.acl 宽捷.acl 世纪互联.acl 网易网络.acl 中电飞华.acl
china Telecom.acl 佰隆网络.acl 广电网.acl 蓝讯通信技术.acl 视通宽带.acl 维赛网络.acl 中电华通.acl
china Unicom.acl 百度网络.acl 国研网.acl 联通.acl 视讯宽带.acl 维速.acl 中国互联网信息中心.acl
error.txt 百吉数据.acl 湖南广电.acl 联通新国信.acl 首信网.acl 沃通电子商务.acl 中国科技网.acl
Hong Kong Internet Exchange.acl 北龙中网.acl 华瑞信通.acl 临网通讯.acl 数讯信息技术.acl 新飞金信.acl 中国一汽.acl
Hurricane Electric.acl 比通联合网络.acl 华数.acl 零色沸点网络.acl 太平洋电信.acl 新浪网络.acl 中国在线.acl
level 3.acl 博路电信.acl 华夏光网.acl 龙腾佳讯.acl 腾讯网络.acl 新网.acl 中企通信.acl
net_ip_list.sh 长城互联网.acl 华宇宽带.acl 南凌科技.acl 天地通电信.acl 信天游.acl 中信网络.acl
netlist.file 畅捷通信.acl 吉林油田通信.acl 鹏博士.acl 天地祥云.acl 燕大正洋.acl 中原油田.acl
NEWWORLDTEL.acl 城市网络.acl 教育网.acl 平煤神马集团.acl 天威宽带.acl 移动.acl 众屹赢时通信.acl
PCCW.acl 地面通信息网络.acl 金桥网.acl 日升天信科技.acl 天盈信息技术.acl 屹立由数据.acl 重庆广电.acl
SOFTLAYER.acl 电信.acl 京宽网络.acl 睿江科技.acl 铁通.acl 盈通网络.acl
src.html 方正网络.acl 经济信息网.acl 森华易腾.acl 铜牛集团.acl 油田宽带.acl
阿里巴巴.acl 飞华领航.acl 康盛新创.acl 上海大众汽车.acl 网联光通.acl 有孚网络.acl

一个文件代表一个isp信息,而智能DNS就是根据这些acl文件,也就是线路ip库来匹配,对于不同的Local DNS从而返回不同的解析记录,我们可以由此进行设置view视图,在主配置文件中include 这些acl文件即可,在此不在赘述,详情可见本站的第一篇bind view的使用

数据入库

并发方面由于臭名昭著的GIL,我就简单使用了进程池进行处理,生成四个子进程对数据进行操作,父进程则阻塞在p.join等待子进程全部完成

由于python解释器存在GIL的限制,python的多线程不适用于cpu密集型的操作,但是这里数据采集是典型的IO bound所以我用多线程尝试了下,效率还是可观的,但是一开始我采用的是concurrent.futuresThreadPoolExecutor这个经过封装的线程池,但是莫名遇到很多问题,还是用了原生的线程。

数据的入库我用了一个生产者和消费者的模型,多线程采集数据之后压入Queue,之后启动一个消费者线程去连接mysql,将数据写入mysql

首先创建数据库和表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql> CREATE DATABASE tabaoip;
mysql> show create table c_ip_addr_info\G;
*************************** 1. row ***************************
Table: c_ip_addr_info
Create Table: CREATE TABLE `c_ip_addr_info` (
`c_ip` varchar(50) NOT NULL,
`mask` tinyint(3) unsigned NOT NULL,
`c_area` varchar(50) DEFAULT NULL,
`c_city` varchar(50) DEFAULT NULL,
`c_isp` varchar(60) DEFAULT NULL,
`c_area_id` bigint(20) DEFAULT NULL,
`c_city_id` bigint(20) DEFAULT NULL,
`c_isp_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`c_ip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.01 sec)

使用python去连接mysql观察有没有成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import MySQLdb
reload(sys)
sys.setdefaultencoding( "utf-8" )
HOSTNAME = 'localhost'
DATABASE = 'tabaoip'
USERNAME = 'root'
PASSWORD = 'zhxfei..192'
DBURI = 'mysql://{}:{}@{}/{}'.format(USERNAME,PASSWORD,HOSTNAME,DATABASE)
TABLENAME= 'c_ip_addr_info'
con = MySQLdb.connect(HOSTNAME,USERNAME,PASSWORD,DATABASE,charset="utf8")
def db_test():
sql_content = 'desc {}'.format(TABLENAME)
with con as cur:
cur.execute(sql_content)
for line in cur.fetchall():
print line
1
2
3
4
5
6
7
8
9
zhxfei@HP-ENVY:~/learning/workspace1$ python mysql_insert.py
(u'c_ip', u'varchar(50)', u'NO', u'PRI', None, u'')
(u'mask', u'tinyint(3) unsigned', u'NO', u'', None, u'')
(u'c_area', u'varchar(50)', u'YES', u'', None, u'')
(u'c_city', u'varchar(50)', u'YES', u'', None, u'')
(u'c_isp', u'varchar(60)', u'YES', u'', None, u'')
(u'c_area_id', u'bigint(20)', u'YES', u'', None, u'')
(u'c_city_id', u'bigint(20)', u'YES', u'', None, u'')
(u'c_isp_id', u'bigint(20)', u'YES', u'', None, u'')

准备好数据库就可以采集数据了

数据入库的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#!/usr/bin/python
#coding:utf-8
import sys
import time
import MySQLdb
from MySQLdb import IntegrityError
import requests
from math import log
from pdb import set_trace
from Queue import Queue
from os.path import exists
from threading import Thread
from concurrent.futures import ThreadPoolExecutor
reload(sys)
sys.setdefaultencoding( "utf-8" )
HOSTNAME = 'localhost'
DATABASE = 'tabaoip'
USERNAME = 'root'
PASSWORD = 'zhxfei..192'
DBURI = 'mysql://{}:{}@{}/{}'.format(USERNAME,PASSWORD,HOSTNAME,DATABASE)
TABLENAME= 'c_ip_addr_info'
con = MySQLdb.connect(HOSTNAME,USERNAME,PASSWORD,DATABASE,charset="utf8")
def into_db(c_ip,mask,c_area,c_city,c_isp,c_area_id,c_city_id,c_isp_id):
sql_content = "insert into {} values ('{}',{},'{}','{}','{}',{},{},{})".format(
TABLENAME,c_ip,mask,c_area,c_city,c_isp,
c_area_id,c_city_id,c_isp_id)
with con as cur:
try:
cur.execute(sql_content)
except IntegrityError:
pass
def db_test():
sql_content = 'desc {}'.format(TABLENAME)
with con as cur:
cur.execute(sql_content)
for line in cur.fetchall():
print line
def ip_resolve(ip,mask):
'''resolve isp_name use ip.taobao.com'''
# print 'ip_resolve execute {}'.format(ip)
r = requests.get(
'http://ip.taobao.com/service/getIpInfo.php?ip={}'.format(
ip,timeout=0.1))
if r.status_code == 200 and r.json().get('code') == 0:
data = r.json()
c_ip = data['data']['ip']
c_area = data['data']['area']
c_city = data['data']['city']
c_isp = data['data']['isp']
c_area_id = int(data['data']['area_id']) if data['data']['area_id'] else -1
c_city_id = int(data['data']['city_id']) if data['data']['city_id'] else -1
c_isp_id = int(data['data']['isp_id']) if data['data']['isp_id'] else -1
res_q.put((c_ip,mask,c_area,c_city,c_isp,c_area_id,c_city_id,c_isp_id))
# elif retry_num>0:
# count = retry_num - 1
# return ip_resolve(ip,mask,count)
else:
return ip_resolve(ip,mask)
def net_catch():
'''find the network segment and netmask from src.html'''
with open('src.html', 'r') as f:
for line in f:
if line.startswith('apnic|CN|ipv4'):
'''
the line example:
apnic|IN|ipv4|103.27.84.0|1024|20130701|assigned
'''
net,cnt = line.strip().split('|')[3:5]
yield net,int(32-log(float(cnt))/log(2))
def sql_worker(res_q):
while True:
data = res_q.get()
# if data:
c_ip,mask,c_area,c_city,c_isp,c_area_id,c_city_id,c_isp_id = data
into_db(c_ip,mask,c_area,c_city,c_isp,c_area_id,c_city_id,c_isp_id)
print '{} insert OK'.format(c_ip)
res_q.task_done()
t = time.time()
res_q = Queue()
t = Thread(target=sql_worker,args=(res_q,))
t.daemon = True
t.start()
tasks = []
for ip,mask in net_catch():
task = Thread(target=ip_resolve,args=(ip,mask,))
task.start()
tasks.append(task)
for task in tasks:
task.join()
print 'tasks over!'
res_q.join()
#db_test()
print "All done,Cost : {}".format(time.time() - t)

采集的记录实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql> select * from c_ip_addr_info limit 10;
+----------+------+--------+-----------+--------+-----------+-----------+----------+
| c_ip | mask | c_area | c_city | c_isp | c_area_id | c_city_id | c_isp_id |
+----------+------+--------+-----------+--------+-----------+-----------+----------+
| 1.0.1.0 | 24 | 华东 | 福州市 | 电信 | 300000 | 350100 | 100017 |
| 1.0.2.0 | 23 | 华东 | 福州市 | 电信 | 300000 | 350100 | 100017 |
| 1.0.32.0 | 19 | 华南 | 广州市 | 电信 | 800000 | 440100 | 100017 |
| 1.0.8.0 | 21 | 华南 | 广州市 | 电信 | 800000 | 440100 | 100017 |
| 1.1.0.0 | 24 | 华东 | 福州市 | 电信 | 300000 | 350100 | 100017 |
| 1.1.10.0 | 23 | 华南 | 广州市 | 电信 | 800000 | 440100 | 100017 |
| 1.1.12.0 | 22 | 华南 | 广州市 | 电信 | 800000 | 440100 | 100017 |
| 1.1.16.0 | 20 | 华南 | 广州市 | 电信 | 800000 | 440100 | 100017 |
| 1.1.2.0 | 23 | 华东 | 福州市 | 电信 | 300000 | 350100 | 100017 |
| 1.1.32.0 | 19 | 华南 | 广州市 | 电信 | 800000 | 440100 | 100017 |
+----------+------+--------+-----------+--------+-----------+-----------+----------+
10 rows in set (0.00 sec)

感受下:

1
7621 rows in set (0.01 sec)

坚持原创技术分享,您的支持将鼓励我继续创作!