【问题标题】:Pandas check which subnetwork IP address belongs to熊猫检查IP地址属于哪个子网
【发布时间】:2019-02-09 01:13:38
【问题描述】:

我有一个用户及其 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

还有一个单独的数据框,其中包含网络范围和相应的地理名称 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 个示例。实际上,users_df 有 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

由于数据帧/列表上的嵌套循环,这非常慢。有没有更快的方法来利用 numpy/pandas?或者至少某种方式比上面的方法更快?

对此有类似的问题 (How can I check if an ip is in a network in python 2.x?),但 1) 它不涉及 pandas/numpy,2) 我想针对 多个网络 检查多个 IP,以及 3) 投票最高答案无法避免嵌套循环,这就是我性能缓慢的原因

【问题讨论】:

  • 我认为您将在这里遇到的问题是在内部 for 循环中使用 pandas 函数。如果您可以将其转换为更底层的形式,那么您可以使用 Numba、Cython 等。不过,我看不到加速外部 for 循环的方法,因为您正在调用第三方库。
  • 105.49.228.135 如何映射到105.49.224.0/19 范围?

标签: python python-3.x pandas ip


【解决方案1】:

我不认为可以避免嵌套循环,但我已将评论中提到的先前解决方案与 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)

【讨论】:

    【解决方案2】:

    如果你愿意用 R 代替 Python,我写了一个 ipaddress 包可以解决这个问题。仍然有一个底层循环,但它是用 C++ 实现的(快得多!)

    library(tibble)
    library(ipaddress)
    library(fuzzyjoin)
    
    addr <- tibble(
      id = 1:3,
      address = ip_address(c("96.255.18.236", "105.49.228.135", "104.236.210.234"))
    )
    nets <- tibble(
      network = ip_network(c("96.255.18.0/24", "105.49.224.0/19", "104.236.128.0/17")),
      geoname = c("4360369.0", "192950.0", "5391959.0")
    )
    
    fuzzy_left_join(addr, nets, c("address" = "network"), is_within)
    #> # A tibble: 3 x 4
    #>      id         address          network geoname  
    #>   <int>       <ip_addr>       <ip_netwk> <chr>    
    #> 1     1   96.255.18.236   96.255.18.0/24 4360369.0
    #> 2     2  105.49.228.135  105.49.224.0/19 192950.0 
    #> 3     3 104.236.210.234 104.236.128.0/17 5391959.0
    

    reprex package (v0.3.0) 于 2020 年 9 月 2 日创建

    【讨论】:

      猜你喜欢
      • 2011-07-26
      • 2021-12-12
      • 2017-10-30
      • 2012-09-21
      • 2016-09-09
      • 2017-07-18
      • 2010-11-05
      • 1970-01-01
      • 2016-12-24
      相关资源
      最近更新 更多