代码之家  ›  专栏  ›  技术社区  ›  Simon

pandas检查哪个子网ip地址属于

  •  4
  • Simon  · 技术社区  · 6 年前

    我有一个用户及其IP地址的Pandas数据框:

    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
    

    以及包含网络范围和对应的GeNAMID ID的单独数据帧:

    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个例子。事实上, 用户名 有4000行,并且 geonames_df 超过300万

    我正在使用这个:

    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
    

    这非常慢,因为dataframe/list上有嵌套循环。有没有一种更快的方法来利用numpy/pandas?或者至少有比上面的方法更快的方法?

    有一个类似的问题( How can I check if an ip is in a network in python 2.x? ),但1)不涉及熊猫/小熊猫,2)我想检查多个IP 多个网络 ,和3)最高投票率的答案无法避免嵌套循环,这就是我缓慢的性能来源

    1 回复  |  直到 6 年前
        1
  •  0
  •   Ajay Srivastava    6 年前

    我不认为嵌套循环是可以避免的,但我已经将评论中提到的以前的解决方案与pandas结合起来。你可以检查一下速度是否更快。

    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)