[英]How to efficiently check if a given IP Address belong to an IP subnetwork in Python?
[英]Pandas check which subnetwork IP address belongs to
我有一個包含用戶及其 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,並提取相應的 geoname 並將其添加到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? 或者至少是某種比上述方法更快的方法?
有一個類似的問題( 如何在 python 2.x 中檢查 ip 是否在網絡中? ),但是 1)它不涉及 pandas/numpy,2)我想針對多個網絡檢查多個 IP,以及 3 ) 得票最高的答案無法避免嵌套循環,這就是我性能緩慢的原因
我認為無法避免嵌套循環,但我已將評論中提到的先前解決方案與熊貓結合使用。 您可以檢查它是否更快。
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)
如果你願意使用 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 包(v0.3.0) 於 2020 年 9 月 2 日創建
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.