純真 IP 數據庫(QQWry.dat)是國內常用的 IP 歸屬地離線庫。本文展示將其文本導出版本轉為內存索引,利用 bisect 二分查找實現微秒級查詢的 Python 腳本。
假設已將 QQWry.dat 導出為文本 ipdata.txt,每行格式:
1.0.0.0 1.0.0.255 內蒙古 電信
1.0.1.0 1.0.3.255 福建 電信
...
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from bisect import bisect
_LIST1, _LIST2 = [], []
_INIT = False
# IP 字符串轉 32 位整數
ip2int = lambda ip_str: reduce(lambda a, b: (a << 8) + b, [int(i) for i in ip_str.split('.')])
def _init():
global _LIST1, _LIST2, _INIT
if not _INIT:
with open('ipdata.txt', 'rb') as f:
for line in f:
parts = line.split()
ip1, ip2 = parts[0], parts[1]
addr = b' '.join(parts[2:]).decode('utf-8', 'ignore')
_LIST1.append(ip2int(ip1))
_LIST2.append((ip2int(ip1), ip2int(ip2), addr))
_INIT = True
def ip_from(ip):
_init()
i = ip2int(ip)
idx = bisect(_LIST1, i) # 找到第一個大於 i 的起始 IP 索引
if idx == 0:
return u'unknown ip address %s' % ip
# 因為 bisect 返回插入位置,實際區間在 idx-1
start, end, addr = _LIST2[idx - 1]
if start <= i <= end:
return addr
return u'unknown ip address %s' % ip
if __name__ == '__main__':
print(ip_from('115.238.54.106'))
print(ip_from('220.181.29.160'))
print(ip_from('115.238.54.107'))
print(ip_from('8.8.8.8'))
| 步驟 | 說明 |
|---|---|
| IP 轉整數 | a.b.c.d → ((a<<24)|(b<<16)|(c<<8)|d),便於數值比較與二分 |
| 索引構建 | _LIST1 存所有段起始 IP(已排序),_LIST2 存 (start, end, addr) 元組 |
| 二分查找 | bisect(_LIST1, ip_int) 返回插入位置,目標區間在 idx-1 |
| 邊界檢查 | 確認 start <= ip <= end,防止落在間隙段 |
| 懶加載 | 首次查詢時才讀取文件構建索引,避免模塊導入開銷 |