Панды проверяют, к какому IP-адресу подсети принадлежит - PullRequest
0 голосов
/ 04 сентября 2018

У меня есть пандас с данными о пользователях и их IP-адресах:

users_df = pd.DataFrame({'id': [1,2,3],
                         'ip': ['96.255.18.236','105.49.228.135','104.236.210.234']})

   id               ip
0   1    96.255.18.236
1   2   105.49.228.135
2   3  104.236.210.234

И отдельный фрейм данных, содержащий диапазоны сети и соответствующие идентификаторы геонамов:

geonames_df = pd.DataFrame({'network': ['96.255.18.0/24','105.49.224.0/19','104.236.128.0/17'],
                            'geoname': ['4360369.0','192950.0','5391959.0']})

     geoname           network
0  4360369.0    96.255.18.0/24
1   192950.0   105.49.224.0/19
2  5391959.0  104.236.128.0/17

Для каждого пользователя мне нужно проверить его ip для всех сетей, вытащить соответствующее географическое имя и добавить его к users_df. Я хочу это как вывод:

   id               ip   geonames
0   1    96.255.18.236  4360369.0
1   2   105.49.228.135   192950.0
2   3  104.236.210.234  5391959.0

В этом примере это просто, потому что они правильно упорядочены и всего 3 примера. В действительности users_df имеет 4000 строк, а geonames_df имеет более 3 миллионов

Я сейчас использую это:

import ipaddress

networks = []
for n in geonames_df['network']:
    networks.append(ipaddress.ip_network(n))

geonames = []

for idx, row in users_df.iterrows():
    ip_address = ipaddress.IPv4Address(row['ip'])

    for block in networks:
        if ip_address in block:
            geonames.append(str(geonames_df.loc[geonames_df['network'] == str(block), 'geoname'].item()))
            break

users_df['geonames'] = geonames

Это очень медленно из-за вложенного цикла над фреймом данных / списком. Есть ли более быстрый способ использования numpy / pandas? Или, по крайней мере, каким-то образом, который быстрее, чем вышеописанный метод?

Есть похожий вопрос по этому поводу ( Как я могу проверить, есть ли ip в сети в python 2.x? ), но 1) он не включает панд / numpy, 2) я хочу сравнить несколько IP-адресов с несколькими сетями и 3) ответ с наибольшим количеством голосов не может избежать вложенного цикла, из-за которого моя низкая производительность исходит из

1 Ответ

0 голосов
/ 04 сентября 2018

Я не думаю, что вложенного цикла можно избежать, но я объединил предыдущее решение, упомянутое в комментарии, с пандами. Вы можете проверить, быстрее ли это.

import socket,struct

def makeMask(n):
    "return a mask of n bits as a long integer"
    return (2<<n-1) - 1

def dottedQuadToNum(ip):
    "convert decimal dotted quad string to long integer"
    return struct.unpack('L',socket.inet_aton(ip))[0]

def networkMask(network):
    "Convert a network address to a long integer" 
    return dottedQuadToNum(network.split('/')[0]) & makeMask(int(network.split('/')[1]))

def whichNetwork(ip):
    "return the network to which the ip belongs"
    numIp = dottedQuadToNum(ip)
    for index,aRow in geonames_df.iterrows():
        if (numIp & aRow["Net"] == aRow["Net"]):
            return aRow["geoname"]
    return "Not Found"

geonames_df["Net"] = geonames_df["network"].map(networkMask)
users_df["geonames"] = users_df["ip"].map(whichNetwork)
...