使用淘宝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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容

  • 名词延伸 通俗的说,域名就相当于一个家庭的门牌号码,别人通过这个号码可以很容易的找到你。如果把IP地址比作一间房子...
    杨大虾阅读 20,597评论 2 57
  • 0. 介绍 本文源自《图解TCP/IP》第四、五章读书笔记。一篇文章让你了解IP协议。阅读的时候,注意一般知识点结...
    天才木木阅读 5,144评论 0 14
  • 互联网有很多接口可以实现通过ip查询到具体的位置,如下: 通过淘宝IP地址库获取IP位置 1. 请求接口(GET)...
    温走马阅读 962评论 0 0
  • 第二章 物理层 频分复用:频分复用的用户在同样的时间占用不同的带宽资源(频率带宽) 时分复用:时分复用的用户在不同...
    PramaWells阅读 3,622评论 1 3
  • 从下午到现在一直在床上躺着,除了吃晚饭基本上都在睡觉,时不时会发出一阵长长的伴着浓痰的咳嗽,每一声咳都连着肺,咳完...
    伍月小姐阅读 715评论 6 4