Python 利用 IP 地理库生成随机天朝 IP

首先从 MaxMind 下载一份按照国家分类的 IP 数据库 (csv 格式)。

数据库截图

首先要做的是把里面所有的国内 IP 段提取出来;手动打开 LocationsBlocks-IPv4 文件看了下,得知在 Blocks-IPv4 文件内,第一列是 IP 段,第二列是地理 ID;而在 Locations 内,天朝的地理 ID 为 1814991

虽然说这些列的序号可能会根据以后更新而打乱,但是目前来看 MaxMind 不会经常调整这个列的序号。毕竟我们用的是个 Quick & Dirty 的解决方案,更新起来也是挺方便的。

提取出来后需要写出到一个可被随机访问的文件(数据库?)内。一个简单的格式就是将每个 IP 段打包为 5 个字节 (IP 占 4 个,掩码占 1 个),随机抽取的时候可以直接定位到需要的位置。最后在文件开始处标记共有多少项,连获取文件大小都可以省掉。

from struct import pack

# Define indexes
I_NETWORK     = 0
I_GEONAME_ID  = 1

# 从 GeoLite2-Country-Locations-*.csv 获取天朝的地理 ID
GEO_CHINA     = 1814991

f_ipv4     = open('GeoLite2-Country-Blocks-IPv4.csv', 'r')
f_output   = open('GeoChinaIp.dat', 'wb')

# 跳过 CSV 表头
f_ipv4.readline()

count = 0
f_output.write(pack('I', count))
while True:
    address = f_ipv4.readline()
    if address == '': break
    
    I = address.split(',')
    if not I[I_GEONAME_ID]: continue
    network =     I[I_NETWORK   ]
    geo_id  = int(I[I_GEONAME_ID])
    
    if geo_id == GEO_CHINA:
        count = count + 1
        ip, mask = network.split('/')
        a, b, c, d = [int(x) for x in ip.split('.')]
        f_output.write(pack('BBBBB', a, b, c, d, int(mask)))

f_output.seek(0, 0)
f_output.write(pack('I', count))

f_output.close()
f_ipv4.close()

获取到我们自己的国内 IP 段大全后,就可以写一个通用的 IP 地址生成器了。

因为 IP 其实讲白了就是一个 4 字节的数字,而掩码则可以视为这个段的大小。为了简化计算,直接将 IP 地址作为一个整数来运算即可;段开始地址加上 rand(0, size - 1) 就得到一个 IP 了。

在实际实验中,偶尔有时候会发生生成的 IP 不能通过网易的检测的情况。因此,需要加入一个主动连接网易服务器进行检测的函数。在这里是利用的网页版 API;为了不增加复杂度,我选择事先在浏览器对提交数据进行加密,然后在 Python 内直接传输。若是生成的 IP 不行,那就再生成一个。

在测试的时候能发现,虽然 MaxMind 提供的地理数据库与网易检测用的地理数据库兼容不是特别的完美,几乎每 3 次有一次失败。当然因为是随机抽样检测,这个数据并不能代表整个数据库,但这个成功率也是足够用来作为我们自动更新构造 IP 的小程序了。

最后为了方便一次性生成可用的文件,给程序加上两个参数:其一为输入文件路径,将其的所有 <ip> 都替换为随机生成的一个国内 IP;其二为输出文件路径,将替换后的文件内容写入。

from __future__ import print_function

from re      import sub
from sys     import argv, stdout
from struct  import unpack
from random  import randint

try:
    # Python 2
    from httplib import HTTPConnection
    from urllib import urlencode
except:
    # Python 3
    from urllib.parse import urlencode
    from http.client import HTTPConnection

# Run following in http://music.163.com/ to get payload; where 64634 is any id that does not work outside China.
# with(asrsea("{\"ids\":\"[64634]\",\"br\":128000,\"csrf_token\":\"\"}","010001","00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7","0CoJUm6Qyw8W8jud")) JSON.stringify({ params: encText, encSecKey });
payload = {"params":"1jGcS1W77hIKvP/5xvJN4DRJP4qWjWdVXio+Iy4ztTwUQpfehnmLdxKkz8y7tUO1kkQXqe0Cv3wCRrgTfqjoa+ripG5hqvZ1+YUYODcz7es=","encSecKey":"21bfcacc1ee1459e04b0a9970ea7b6775524af3309168549f82084266721a2f29dba89f9be2c84ca627f8369eb64f61aa2002e6bea90a29651445154e96cb0052e12077d6c5094dfcc72f22c07879abc6a5eb69e50ab6fb4293140036b65a465fcd6ca5a54d0672c8a1ec393bc9f22771d4a17762a8d8792e32c522e826ff46f"}
payload = urlencode(payload)

def check_ip(ip):
    """Connect to netease and check if the ip is availiable.
    
    Args:
        ip (str): IP Address to be checked
        
    Returns:
        bool: True if valid, False otherwise.
    """
    conn = HTTPConnection('music.163.com', 80)
    headers = {
        "Accept":"*/*",
        "Content-Type":"application/x-www-form-urlencoded",
        "Content-Length":len(payload),
        "Referer":"http//music.163.com/",
        "User-Agent":"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)",
        "X-Real-IP": ip
    }
    conn.request('POST', '/weapi/song/enhance/player/url?csrf_token=', payload, headers)
    resp = conn.getresponse()
    r = str(resp.read())
    conn.close()
    return '"url":"http' in r

def main():
    """IP Picker
    
    Pick a random IP from `GeoChinaIp.dat`,
    fill the template file and generate a new file.
    
    Usage: pick_ip.py <tpl_file> <output_file>
    
    All "<ip>" from <tpl_file> will be replaced with a random picked IP.
    """
    if len(argv) != 3:
        print('IP Picker')
        print('Pick a random IP from GeoChinaIp.dat, ')
        print('fill the template file and generate a new file.\n')
        print('Usage: %s <tpl_file> <output_file>\n' % argv[0])
        print('All "<ip>" from <tpl_file> will be replaced with a random picked IP.')
        return

    with open(argv[1], 'r') as f:
        tpl = f.read()

    f_input = open('GeoChinaIp.dat', 'rb')

    count ,= unpack('I', f_input.read(4))
    
    def pick_ip(unused = 0):
        """Pick a random Chinese IP.
        
        Args:
            unused (any): Not used
            
        Returns:
            A valid ip that have passed netease check.
        """
        while True:
            f_input.seek(4 + randint(0, count - 1) * 5, 0)
            ip, mask = unpack('>IB', f_input.read(5))

            size = pow(2, 32 - mask)

            a = (ip >> 24) & 255
            b = (ip >> 16) & 255
            c = (ip >>  8) & 255
            d = (ip >>  0) & 255

            print('%d.%d.%d.%d/%d; size = 0x%x' % (a, b, c, d, mask, size))

            ip += randint(0, size - 1)

            a = (ip >> 24) & 255
            b = (ip >> 16) & 255
            c = (ip >>  8) & 255
            d = (ip >>  0) & 255

            str_ip = '%d.%d.%d.%d' % (a, b, c, d)
            print('IP: %s, verifying...' % str_ip, end='')
            
            if check_ip(str_ip):
                print(' OK!');
                break
            print(' Failed!')
        
        return str_ip
    
    result = sub(r'<ip>', pick_ip, tpl)
    f_input.close()
    
    with open(argv[2], 'w') as f:
        f.write(result)
    
if __name__ == "__main__":
    main()

项目文件下载(包括 MaxMind 2017.04.04 版 csv 数据库):

v2:梦姬储存


v1,使用新版 Python 会提示解析出错,仅供存档用途。

Jixun的头像

Jixun