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

题记:2017第一弹~

简介

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

具体生成器函数如下:

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里面包含了世界上所有的公网网段地址,形如:

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地址段,我们可以将其写入一个文件

#
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格式的)国家 、省(自治区或直辖市)、市(县)、运营商等等
  • 返回数据格式:
{"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数据,格式化后便于我们对数据特点进行分析:

{
    "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

部分代码:

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文件,如下

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

首先创建数据库和表:

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观察有没有成功

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
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'')

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

数据入库的代码:

#!/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)

采集的记录实例:

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)

感受下:

7621 rows in set (0.01 sec)

你可能感兴趣的:(使用淘宝IP库为智能DNS收集中国ISP信息)