【问题标题】:Finding local IP addresses using Python's stdlib使用 Python 的 stdlib 查找本地 IP 地址
【发布时间】:2010-09-15 01:19:48
【问题描述】:

如何在 Python 平台中仅使用标准库独立查找本地 IP 地址(即 192.168.x.x 或 10.0.x.x)?

【问题讨论】:

  • 本地IP?还是公网IP?您将如何处理具有多个 IP 的系统?
  • 使用ifconfig -a 并使用那里的输出...
  • @Fredrik 这是个坏主意。首先,您不必要地分叉了一个新进程,这可能会阻止您的程序在紧密锁定的配置中工作(或者,您必须允许您的程序不需要的权限)。其次,您将为不同语言环境的用户介绍错误。第三,如果您决定启动一个新程序,您不应该启动一个已弃用的程序 - ip addr 更合适(并且更易于解析、启动)。
  • @phihag 你是绝对正确的,谢谢你纠正我的愚蠢
  • 这里一个更根本的问题是,在正确编写的现代网络程序中,正确的(一组)本地 IP 地址取决于对等方或潜在对等方的集合。如果需要本地 IP 地址来bind 到特定接口的套接字,那么这是一个策略问题。如果需要将本地IP地址交给一个peer,让peer可以“回调”,即打开一个回本机的连接,那么情况就看有没有NAT(Network Address Translation)了之间的盒子。如果没有 NAT,getsockname 是一个不错的选择。

标签: python


【解决方案1】:

我刚刚发现了这个,但它看起来有点老套,但是他们说在 *nix 上尝试过,而我在 windows 上尝试过,并且成功了。

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
print(s.getsockname()[0])
s.close()

这假设您可以访问互联网,并且没有本地代理。

【讨论】:

  • 如果您的机器上有多个接口,并且需要一个路由到例如的接口,那就太好了。 gmail.com
  • 捕获可能由 s.connect() 引发的 socket.error 异常可能是个好主意!
  • 最好使用 IP 地址而不是域名——它必须更快并且独立于 DNS 可用性。例如。我们可以使用 8.8.8.8 IP -- Google 的公共 DNS 服务器。
  • 非常聪明,完美运行。除了 gmail 或 8.8.8.8,您还可以使用您想要查看的服务器的 IP 或地址(如果适用)。
  • 此示例具有能够实际解析 gmail.com 的外部依赖性。如果您将其设置为不在本地局域网上的 IP 地址(无论它是启动还是关闭),它都可以在没有依赖关系和网络流量的情况下工作。
【解决方案2】:
import socket
socket.gethostbyname(socket.gethostname())

这不会总是有效(在主机名在/etc/hosts 的机器上返回127.0.0.1127.0.0.1),gimel 显示的是缓和,请改用socket.getfqdn()。当然,您的机器需要一个可解析的主机名。

【讨论】:

  • 应该注意这不是一个独立于平台的解决方案。很多 Linux 会使用这种方法返回 127.0.0.1 作为你的 IP 地址。
  • 变体:socket.gethostbyname(socket.getfqdn())
  • 这似乎只返回一个 IP 地址。如果机器有多个地址怎么办?
  • 在 Ubuntu 上由于某种原因返回 127.0.1.1。
  • @Jason R. Coombs,使用以下代码检索属于主机的 IPv4 地址列表:socket.gethostbyname_ex(socket.gethostname())[-1]
【解决方案3】:

此方法返回本地机器上的“主要”IP(具有默认路由的机器)

  • 根本不需要可路由的网络访问或任何连接。
  • 即使所有接口都从网络中拔出,也能正常工作。
  • 不需要甚至尝试去其他任何地方
  • 适用于 NAT、公共、私有、外部和内部 IP
  • 没有外部依赖的纯 Python 2(或 3)。
  • 适用于 Linux、Windows 和 OSX。

Python 3 或 2:

import socket
def get_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.settimeout(0)
    try:
        # doesn't even have to be reachable
        s.connect(('10.255.255.255', 1))
        IP = s.getsockname()[0]
    except Exception:
        IP = '127.0.0.1'
    finally:
        s.close()
    return IP

这将返回一个作为主 IP 的 IP(具有默认路由的 IP)。如果您需要将所有 IP 附加到所有接口(包括 localhost 等),请参阅 this answer 之类的内容。

如果您在家里的 wifi 盒子等 NAT 防火墙后面,那么这将不会显示您的公共 NAT IP,而是显示您在本地网络上的私有 IP,该 IP 具有到本地 WIFI 路由器的默认路由;获取您的wifi路由器的外部IP要么需要在那个盒子上运行它,要么连接到可以反映IP的外部服务,例如whatismyip.com/whatismyipaddress.com......但这与原始问题完全不同。 :)

【讨论】:

  • 在 Raspbian 中使用 Python 2 和 3 工作!
  • 太棒了。适用于 Win7、8、8.1 + Linux Mint 和 Arch,包括 VM。
  • 适用于 Windows 10 专业版!谢谢你,杰米森·贝克尔!
  • 由于某种原因,这在 Mac OS X El Capitan 10.11.6 上不起作用(它会生成异常 OS 错误:[Errno 49] Can't assign requested address)。将端口从 '0' 更改为 '1' : s.connect(('10.255.255.255', 1)) 在 Mac OS X 和 Linux Ubuntu 17.04 上都对我有用
  • 这应该是公认的答案。 socket.gethostbyname(socket.gethostname()) 给出了可怕的结果。
【解决方案4】:

作为别名myip

alias myip="python -c 'import socket; print([l for l in ([ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith(\"127.\")][:1], [[(s.connect((\"8.8.8.8\", 53)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]]) if l][0][0])'"
  • 可与 Python 2.x、Python 3.x、现代和旧的 Linux 发行版、OSX/macOS 和 Windows 一起正常工作,以查找当前的 IPv4 地址。
  • 对于具有多个 IP 地址、IPv6、未配置 IP 地址或无法访问 Internet 的计算机,将不会返回正确的结果。
  • 据报道,这不适用于最新版本的 macOS。

注意:如果您打算在 Python 程序中使用类似的东西,正确的方法是使用支持 IPv6 的 Python 模块。


同上,只是 Python 代码:

import socket
print([l for l in ([ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")][:1], [[(s.connect(('8.8.8.8', 53)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]]) if l][0][0])
  • 如果未配置 IP 地址,这将引发异常。

也可以在没有互联网连接的 LAN 上运行的版本:

import socket
print((([ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")] or [[(s.connect(("8.8.8.8", 53)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]]) + ["no IP found"])[0])

(感谢@ccpizza


背景

在这里使用socket.gethostbyname(socket.gethostname()) 不起作用,因为我使用的其中一台计算机有一个/etc/hosts,其中包含重复的条目和对其自身的引用。 socket.gethostbyname() 只返回/etc/hosts 中的最后一个条目。

这是我最初的尝试,它清除了所有以"127."开头的地址:

import socket
print([ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")][:1])

这适用于 Linux 和 Windows 上的 Python 2 和 3,但不能处理多个网络设备或 IPv6。但是,它在最近的 Linux 发行版上停止工作,所以我尝试了这种替代技术。它尝试通过端口53 连接到位于8.8.8.8 的Google DNS 服务器:

import socket
print([(s.connect(('8.8.8.8', 53)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1])

然后我将上述两种技术组合成一个适用于任何地方的单行代码,并在此答案的顶部创建了 myip 别名和 Python sn-p。

随着 IPv6 的日益普及,对于具有多个网络接口的服务器,使用第三方 Python 模块来查找 IP 地址可能比此处列出的任何方法都更加健壮和可靠。

【讨论】:

  • @Alexander:只是说这个答案没有以前那么有用了(而且过滤掉重复项并不重要;)。根据文档socket.getaddrinfo() 应该可以跨平台一致地工作 - 但我只在 Linux 上检查过,没有打扰任何其他操作系统。
  • @Alexander、/etc/resolve.conf: No such file or directory 和我的本地 IPv4 地址由 ifconfig 显示。
  • 我可以确认更新的版本适用于 Ubuntu 14.04 以及 Python2 和 Py3k。
  • “更新”显示了一个在 UDP 套接字上使用 connect() 的好技巧。它不发送流量,但可以让您找到将数据包发送到指定收件人的发件人地址。该端口可能无关紧要(即使是 0 也应该可以)。在多宿主主机上,选择正确子网中的地址很重要。
  • 组合别名代码会启动到 8.8.8.8 的不必要的外部连接,即使 gethostbyname_ex 返回了有效 IP。这将打破没有互联网的“围墙花园”类型的局域网。外部调用可以使用or进行有条件的调用,例如:ips = [ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")] or [[(s.connect(("8.8.8.8", 53)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]]
【解决方案5】:

您可以使用netifaces 模块。只需输入:

pip install netifaces

在您的命令 shell 中,它将在默认 Python 安装中自行安装。

那么你可以这样使用它:

from netifaces import interfaces, ifaddresses, AF_INET
for ifaceName in interfaces():
    addresses = [i['addr'] for i in ifaddresses(ifaceName).setdefault(AF_INET, [{'addr':'No IP addr'}] )]
    print '%s: %s' % (ifaceName, ', '.join(addresses))

在我的电脑上打印:

{45639BDC-1050-46E0-9BE9-075C30DE1FBC}:192.168.0.100
{D43A468B-F3AE-4BF9-9391-4863A4500583}:10.5.9.207

该模块的作者声称它应该可以在 Windows、UNIX 和 Mac OS X 上运行。

【讨论】:

  • 正如问题中所述,我希望从默认安装中获得一些东西,因为不需要额外的安装。
  • @MattJoiner 这两种情况都不再正确(最新版本在 PyPI 上有 Windows 二进制文件并且确实支持 Py3K)。
  • @Jean-PaulCalderone FWIW,最新版本的 netifaces确实在 Windows 上支持 IPv6。
  • 这个模块必须是标准库的一部分,因为 python 声称“包含电池”的理念
  • @MattJoiner - 请注意,在 Ubuntu 上,最新版本不需要用于 python 或 Py3K 的 C 编译器。该模块也有包。
【解决方案6】:

如果计算机有一条通往 Internet 的路由,即使 /etc/hosts 设置不正确,它总是可以获取首选的本地 IP 地址。

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 1))  # connect() for UDP doesn't send packets
local_ip_address = s.getsockname()[0]

【讨论】:

  • 这是如何工作的? , 8.8.8.8 是 google dns 服务器,我们可以使用本地 dns 服务器吗?
  • @Ciastopiekarz 地址不必是有效的。见:stackoverflow.com/a/28950776/14280715
【解决方案7】:

套接字 API 方法

https://stackoverflow.com/a/28950776/711085

缺点:

  • 不是跨平台的。
  • 需要更多后备代码,与 Internet 上特定地址的存在相关
  • 如果您在 NAT 后面,这也不起作用
  • 可能会创建一个 UDP 连接,而不是独立于(通常是 ISP 的)DNS 可用性(有关使用 8.8.8.8 等想法的其他答案:Google 的(巧合的是 DNS)服务器)
  • 确保将目标地址设置为 UNREACHABLE,例如指定保证不使用的数字 IP 地址。不要使用类似 fakesubdomain.google.com 或 somefakewebsite.com 的域;您仍然会向该方发送垃圾邮件(现在或将来),并且在此过程中也会向您自己的网络盒发送垃圾邮件。

反射器法

(请注意,这并不能回答 OP 关于本地 IP 地址的问题,例如 192.168...;它会为您提供您的公共 IP 地址,根据用例可能更理想。)

您可以查询一些网站,例如 whatismyip.com(但使用 API),例如:

from urllib.request import urlopen
import re
def getPublicIp():
    data = str(urlopen('http://checkip.dyndns.com/').read())
    # data = '<html><head><title>Current IP Check</title></head><body>Current IP Address: 65.96.168.198</body></html>\r\n'

    return re.compile(r'Address: (\d+\.\d+\.\d+\.\d+)').search(data).group(1)

或者如果使用python2:

from urllib import urlopen
import re
def getPublicIp():
    data = str(urlopen('http://checkip.dyndns.com/').read())
    # data = '<html><head><title>Current IP Check</title></head><body>Current IP Address: 65.96.168.198</body></html>\r\n'

    return re.compile(r'Address: (\d+\.\d+\.\d+\.\d+)').search(data).group(1)

优点:

  • 这种方法的一个优点是它是跨平台的
  • 它在丑陋的 NAT 后面工作(例如您的家庭路由器)。

缺点(和解决方法):

  • 要求此网站正常运行,格式不变(几乎肯定不会),并且您的 DNS 服务器正常工作。如果出现故障,还可以通过查询其他第三方 IP 地址反射器来缓解此问题。
  • 如果您不查询多个反射器(以防止受损的反射器告诉您您的地址不是),或者如果您不使用 HTTPS(以防止人在-中间攻击冒充服务器)

edit:虽然一开始我认为这些方法真的很糟糕(除非你使用许多备用方法,否则多年后代码可能会变得无关紧要),但它确实提出了一个问题“什么是互联网? ”。一台计算机可能有许多接口指向许多不同的网络。有关该主题的更详尽描述,请在 google 搜索 gateways and routes。计算机可能能够通过内部网关访问内部网络,或者通过例如路由器上的网关访问万维网(通常是这种情况)。 OP 询问的本地 IP 地址仅针对单个链路层进行了明确定义,因此您必须指定(“是网卡,还是我们正在谈论的以太网电缆?”) .提出的这个问题可能有多个非唯一的答案。然而,万维网上的全球 IP 地址可能是明确定义的(在没有大量网络碎片的情况下):可能是通过可以访问 TLD 的网关的返回路径。

【讨论】:

  • 如果您在 NAT 之后,这将返回您的 LAN 范围的地址。如果您要连接到 Internet,则可以连接到返回您的公共 IP 地址之一的 Web 服务。
  • 它不会创建 TCP 连接,因为它会创建 UDP 连接。
  • 作为套接字 API 版本中的替代方案,将 s.connect(('INSERT SOME TARGET WEBSITE.com', 0)) 替换为 s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) ;s.connect(('', 0)) 以避免 DNS 查找。 (我猜如果有防火墙,广播可能有问题)
【解决方案8】:

在 Linux 上:

>>> import socket, struct, fcntl
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> sockfd = sock.fileno()
>>> SIOCGIFADDR = 0x8915
>>>
>>> def get_ip(iface = 'eth0'):
...     ifreq = struct.pack('16sH14s', iface, socket.AF_INET, '\x00'*14)
...     try:
...         res = fcntl.ioctl(sockfd, SIOCGIFADDR, ifreq)
...     except:
...         return None
...     ip = struct.unpack('16sH2x4s8x', res)[2]
...     return socket.inet_ntoa(ip)
... 
>>> get_ip('eth0')
'10.80.40.234'
>>> 

【讨论】:

  • 所以这有效地打开了一个它什么都不做的套接字,然后您检查有关该套接字的原始数据以获取本地 IP?
  • 打开套接字以获取 fd 与内核通信(通过ioctl)。套接字没有绑定您想要添加信息的接口 - 它只是用户空间和内核之间的通信机制。 en.wikipedia.org/wiki/Ioctllxr.free-electrons.com/source/net/socket.c
  • 在 Python3 上进行一项修改即可工作:struct.pack('16sH14s', iface, socket.AF_INET, '\x00'*14) 应替换为 struct.pack('16sH14s', iface.encode('utf-8'), socket.AF_INET, b'\x00'*14)
  • @ChristianFischer ioctl 是一个遗留接口,我不相信它支持 IPv6,而且可能永远不会。我认为“正确”的方法是通过 Netlink,这在 Python 中不是很简单。我认为 libc 应该有函数 getifaddrs 可以通过 pythons 访问 ctypes 模块可能工作 - man7.org/linux/man-pages/man3/getifaddrs.3.html
  • @Maddy ioctl 是一个遗留接口,我不相信它支持 IPv6,而且可能永远不会。我认为“正确”的方法是通过 Netlink,这在 Python 中不是很简单。我认为 libc 应该具有可以通过 pythons ctypes 模块访问的函数 getifaddrs 可以工作 - man7.org/linux/man-pages/man3/getifaddrs.3.html
【解决方案9】:

我正在使用以下模块:

#!/usr/bin/python
# module for getting the lan ip address of the computer

import os
import socket

if os.name != "nt":
    import fcntl
    import struct
    def get_interface_ip(ifname):
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        return socket.inet_ntoa(fcntl.ioctl(
                s.fileno(),
                0x8915,  # SIOCGIFADDR
                struct.pack('256s', bytes(ifname[:15], 'utf-8'))
                # Python 2.7: remove the second argument for the bytes call
            )[20:24])

def get_lan_ip():
    ip = socket.gethostbyname(socket.gethostname())
    if ip.startswith("127.") and os.name != "nt":
        interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"]
        for ifname in interfaces:
            try:
                ip = get_interface_ip(ifname)
                break;
            except IOError:
                pass
    return ip

在 windows 和 linux 上测试过(并且不需要额外的模块) 旨在用于在单个基于 IPv4 的 LAN 中的系统。

接口名称的固定列表不适用于最近的 linux 版本,如Alexander 所指出的,这些版本采用了关于可预测接口名称的 systemd v197 更改。 在这种情况下,您需要手动将列表替换为系统上的接口名称,或者使用其他解决方案,例如netifaces

【讨论】:

【解决方案10】:

[仅限 Windows] 如果您不想使用外部软件包并且不想依赖外部 Internet 服务器,这可能会有所帮助。这是我在Google Code Search 上找到并修改为返回所需信息的代码示例:

def getIPAddresses():
    from ctypes import Structure, windll, sizeof
    from ctypes import POINTER, byref
    from ctypes import c_ulong, c_uint, c_ubyte, c_char
    MAX_ADAPTER_DESCRIPTION_LENGTH = 128
    MAX_ADAPTER_NAME_LENGTH = 256
    MAX_ADAPTER_ADDRESS_LENGTH = 8
    class IP_ADDR_STRING(Structure):
        pass
    LP_IP_ADDR_STRING = POINTER(IP_ADDR_STRING)
    IP_ADDR_STRING._fields_ = [
        ("next", LP_IP_ADDR_STRING),
        ("ipAddress", c_char * 16),
        ("ipMask", c_char * 16),
        ("context", c_ulong)]
    class IP_ADAPTER_INFO (Structure):
        pass
    LP_IP_ADAPTER_INFO = POINTER(IP_ADAPTER_INFO)
    IP_ADAPTER_INFO._fields_ = [
        ("next", LP_IP_ADAPTER_INFO),
        ("comboIndex", c_ulong),
        ("adapterName", c_char * (MAX_ADAPTER_NAME_LENGTH + 4)),
        ("description", c_char * (MAX_ADAPTER_DESCRIPTION_LENGTH + 4)),
        ("addressLength", c_uint),
        ("address", c_ubyte * MAX_ADAPTER_ADDRESS_LENGTH),
        ("index", c_ulong),
        ("type", c_uint),
        ("dhcpEnabled", c_uint),
        ("currentIpAddress", LP_IP_ADDR_STRING),
        ("ipAddressList", IP_ADDR_STRING),
        ("gatewayList", IP_ADDR_STRING),
        ("dhcpServer", IP_ADDR_STRING),
        ("haveWins", c_uint),
        ("primaryWinsServer", IP_ADDR_STRING),
        ("secondaryWinsServer", IP_ADDR_STRING),
        ("leaseObtained", c_ulong),
        ("leaseExpires", c_ulong)]
    GetAdaptersInfo = windll.iphlpapi.GetAdaptersInfo
    GetAdaptersInfo.restype = c_ulong
    GetAdaptersInfo.argtypes = [LP_IP_ADAPTER_INFO, POINTER(c_ulong)]
    adapterList = (IP_ADAPTER_INFO * 10)()
    buflen = c_ulong(sizeof(adapterList))
    rc = GetAdaptersInfo(byref(adapterList[0]), byref(buflen))
    if rc == 0:
        for a in adapterList:
            adNode = a.ipAddressList
            while True:
                ipAddr = adNode.ipAddress
                if ipAddr:
                    yield ipAddr
                adNode = adNode.next
                if not adNode:
                    break

用法:

>>> for addr in getIPAddresses():
>>>    print addr
192.168.0.100
10.5.9.207

由于它依赖于windll,因此只能在 Windows 上运行。

【讨论】:

  • 上面的单线解决方案一般适用于windows。问题出在 Linux 上。
  • +1 这种技术至少会尝试返回机器上的所有地址。
  • 返回第一个地址后,此脚本在我的机器上失败。错误是“AttributeError:'LP_IP_ADDR_STRING'对象没有属性'ipAddress'”我怀疑它与IPv6地址有关。
  • 事实证明,除了第一个 IP 地址之外,adNode 没有被取消引用。在 while 循环中的示例中再添加一行,它适用于我:adNode = adNode.contents
【解决方案11】:

我在我的 ubuntu 机器上使用它:

import commands
commands.getoutput("/sbin/ifconfig").split("\n")[1].split()[1][5:]

这不起作用。

【讨论】:

  • 漂亮而简单。也适用于亚马逊的 Linux AMI,但前提是我是 root。否则我会得到一个错误:'sh: ifconfig: command not found'
  • 所以你应该像 gavaletz 说的那样使用“/sbin/ifconfig”。它也适用于 Red Hat 4.1.2-48。
  • 自 2.6 起已弃用。使用subprocess module 运行命令。
  • 并且 ifconfig 也被弃用了。使用 iproute2。
  • 获取所有ip:import sh; [ip.split()[1][5:] for ip in filter(lambda x: 'inet addr' in x, sh.ifconfig().split("\n"))]
【解决方案12】:

ninjagecko 的答案有所不同。这应该适用于任何允许 UDP 广播并且不需要访问 LAN 或 Internet 上的地址的 LAN。

import socket
def getNetworkIp():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
    s.connect(('<broadcast>', 0))
    return s.getsockname()[0]

print (getNetworkIp())

【讨论】:

  • 等等,&lt;broadcast&gt; 是一个有效的主机名吗?!!这些口头主机名中有多少是有效的?
  • 这适用于我在 Ubuntu 20.04 上 - 得到 192.168.0.24 而不是 127.0.0.1
  • 适用于我测试过的每个系统。 Windows、Android、Linux、Mac。
  • 当我在 Windows 10 上使用 &lt;broadcast&gt; 时,我得到了无线网卡 IP 地址。当我使用10.255.255.255 时,我得到了以太网 TAP 适配器的 IP 地址。两者都是有效的。但是结果不一样,哈哈。
  • 在 Ubuntu 21.04 上像魅力一样工作,谢谢!
【解决方案13】:

我认为尚未发布的版本。 我在 Ubuntu 12.04 上使用 python 2.7 进行了测试。

http://code.activestate.com/recipes/439094-get-the-ip-address-associated-with-a-network-inter/找到此解决方案

import socket
import fcntl
import struct

def get_ip_address(ifname):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa(fcntl.ioctl(
        s.fileno(),
        0x8915,  # SIOCGIFADDR
        struct.pack('256s', ifname[:15])
    )[20:24])

示例结果:

>>> get_ip_address('eth0')
'38.113.228.130'

【讨论】:

  • 适用于 Python3、Ubuntu 18.04;字符串需要是字节: >>> socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', 'enp0s31f6'[:15].encode('utf-8') ))[20:24]) '192.168.1.1'
【解决方案14】:

在 Debian(经过测试)上,我怀疑大多数 Linux 的..

import commands

RetMyIP = commands.getoutput("hostname -I")

在 MS Windows 上(已测试)

import socket

socket.gethostbyname(socket.gethostname())

【讨论】:

  • 不适用于 macOS:hostname: illegal option -- I\nusage: hostname [-fs] [name-of-host]
  • 在python 3中需要将“commands”替换为“subprocess”,其余相同
【解决方案15】:

这是 UnkwnTech 答案的变体——它提供了一个 get_local_addr() 函数,它返回主机的主 LAN ip 地址。我发布它是因为这增加了很多东西:ipv6 支持、错误处理、忽略 localhost/linklocal 地址,并使用 TESTNET 地址 (rfc5737) 连接。

# imports
import errno
import socket
import logging

# localhost prefixes
_local_networks = ("127.", "0:0:0:0:0:0:0:1")

# ignore these prefixes -- localhost, unspecified, and link-local
_ignored_networks = _local_networks + ("0.", "0:0:0:0:0:0:0:0", "169.254.", "fe80:")

def detect_family(addr):
    if "." in addr:
        assert ":" not in addr
        return socket.AF_INET
    elif ":" in addr:
        return socket.AF_INET6
    else:
        raise ValueError("invalid ipv4/6 address: %r" % addr)

def expand_addr(addr):
    """convert address into canonical expanded form --
    no leading zeroes in groups, and for ipv6: lowercase hex, no collapsed groups.
    """
    family = detect_family(addr)
    addr = socket.inet_ntop(family, socket.inet_pton(family, addr))
    if "::" in addr:
        count = 8-addr.count(":")
        addr = addr.replace("::", (":0" * count) + ":")
        if addr.startswith(":"):
            addr = "0" + addr
    return addr

def _get_local_addr(family, remote):
    try:
        s = socket.socket(family, socket.SOCK_DGRAM)
        try:
            s.connect((remote, 9))
            return s.getsockname()[0]
        finally:
            s.close()
    except socket.error:
        # log.info("trapped error connecting to %r via %r", remote, family, exc_info=True)
        return None

def get_local_addr(remote=None, ipv6=True):
    """get LAN address of host

    :param remote:
        return  LAN address that host would use to access that specific remote address.
        by default, returns address it would use to access the public internet.

    :param ipv6:
        by default, attempts to find an ipv6 address first.
        if set to False, only checks ipv4.

    :returns:
        primary LAN address for host, or ``None`` if couldn't be determined.
    """
    if remote:
        family = detect_family(remote)
        local = _get_local_addr(family, remote)
        if not local:
            return None
        if family == socket.AF_INET6:
            # expand zero groups so the startswith() test works.
            local = expand_addr(local)
        if local.startswith(_local_networks):
            # border case where remote addr belongs to host
            return local
    else:
        # NOTE: the two addresses used here are TESTNET addresses,
        #       which should never exist in the real world.
        if ipv6:
            local = _get_local_addr(socket.AF_INET6, "2001:db8::1234")
            # expand zero groups so the startswith() test works.
            if local:
                local = expand_addr(local)
        else:
            local = None
        if not local:
            local = _get_local_addr(socket.AF_INET, "192.0.2.123")
            if not local:
                return None
    if local.startswith(_ignored_networks):
        return None
    return local

【讨论】:

  • 我认为这可能是一个非常好的答案.. 但它总是返回 None
  • @JamieLindsey 你有关于你的操作系统、网络配置的一些细节吗?另外,get_local_addr(remove="www.google.com") 之类的返回值是什么?记录 _get_local_addr() 抛出的 socket.error 可能有助于诊断。
【解决方案16】:

恐怕除了连接到另一台计算机并让它向您发送您的 IP 地址之外,没有任何独立于平台的好的方法可以做到这一点。例如:findmyipaddress。请注意,如果您需要一个位于 NAT 之后的 IP 地址,除非您要连接的计算机也在 NAT 之后,否则这将不起作用。

这是适用于 Linux 的一种解决方案:get the IP address associated with a network interface

【讨论】:

    【解决方案17】:

    仅供参考,我可以验证该方法:

    import socket
    addr = socket.gethostbyname(socket.gethostname())
    

    在 OS X (10.6,10.5)、Windows XP 和管理良好的 RHEL 部门服务器上工作。它不适用于一个非常小的 CentOS 虚拟机,我只是对它进行了一些内核黑客攻击。因此,对于这种情况,您只需检查 127.0.0.1 地址,在这种情况下执行以下操作:

    if addr == "127.0.0.1":
         import commands
         output = commands.getoutput("/sbin/ifconfig")
         addr = parseaddress(output)
    

    然后从输出中解析ip地址。应该注意的是 ifconfig 默认不在普通用户的 PATH 中,这就是我在命令中给出完整路径的原因。我希望这会有所帮助。

    【讨论】:

    • 这是我在 Mac OS 11.1 上看到的:socket.gaierror: [Errno 8] nodename nor servname provided, or not known
    【解决方案18】:

    一种通过命令行工具产生“干净”输出的简单方法:

    import commands
    ips = commands.getoutput("/sbin/ifconfig | grep -i \"inet\" | grep -iv \"inet6\" | " +
                             "awk {'print $2'} | sed -ne 's/addr\:/ /p'")
    print ips
    

    它将显示系统上的所有 IPv4 地址。

    【讨论】:

    • 它不会显示所有的 IPv4 地址,因为 ifconfig 只告诉你主要的。您需要使用 iproute2 中的“ip”来查看所有地址。
    • 对于一个要求标准库的问题来说,这是一个地狱般的外壳......而且,解析 ifconfig 既不便携,甚至不能在一台机器上可靠地工作。
    【解决方案19】:

    对于 linux,您可以像这样使用hostname -I 系统命令中的check_output

    from subprocess import check_output
    check_output(['hostname', '-I'])
    

    【讨论】:

    • 对于谷歌员工,我知道问题是针对跨平台解决方案
    【解决方案20】:

    这适用于大多数 linux 机器:

    import socket, subprocess, re
    def get_ipv4_address():
        """
        Returns IP address(es) of current machine.
        :return:
        """
        p = subprocess.Popen(["ifconfig"], stdout=subprocess.PIPE)
        ifc_resp = p.communicate()
        patt = re.compile(r'inet\s*\w*\S*:\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
        resp = patt.findall(ifc_resp[0])
        print resp
    
    get_ipv4_address()
    

    【讨论】:

      【解决方案21】:

      这个答案是我个人尝试解决获取局域网IP的问题,因为socket.gethostbyname(socket.gethostname())也返回了127.0.0.1。此方法不需要 Internet,只需 LAN 连接。代码适用于 Python 3.x,但可以轻松转换为 2.x。使用 UDP 广播:

      import select
      import socket
      import threading
      from queue import Queue, Empty
      
      def get_local_ip():
              def udp_listening_server():
                  s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                  s.bind(('<broadcast>', 8888))
                  s.setblocking(0)
                  while True:
                      result = select.select([s],[],[])
                      msg, address = result[0][0].recvfrom(1024)
                      msg = str(msg, 'UTF-8')
                      if msg == 'What is my LAN IP address?':
                          break
                  queue.put(address)
      
              queue = Queue()
              thread = threading.Thread(target=udp_listening_server)
              thread.queue = queue
              thread.start()
              s2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
              s2.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
              waiting = True
              while waiting:
                  s2.sendto(bytes('What is my LAN IP address?', 'UTF-8'), ('<broadcast>', 8888))
                  try:
                      address = queue.get(False)
                  except Empty:
                      pass
                  else:
                      waiting = False
              return address[0]
      
      if __name__ == '__main__':
          print(get_local_ip())
      

      【讨论】:

      • 如果你在同一网络的两台机器上同时运行它会发生什么?当您在网络上广播您的消息时,所有机器都会收到“我的 LAN IP 地址是什么”。您的 udp_listening_server 可以对消息回复“您的 IP 地址是 xxx”。
      【解决方案22】:
      import socket
      [i[4][0] for i in socket.getaddrinfo(socket.gethostname(), None)]
      

      【讨论】:

      • 嗯...在具有两个 NIC 的服务器上,这提供了 一个 分配的 IP 地址,但重复了 3 次。在我的笔记本电脑上它给出'127.0.1.1'(重复三遍......)......
      • 在 Windows 桌面上给我['fe80::34e8:fe19:1459:2cde%22','fe80::d528:99fb:d572:e289%12', '192.168.56.1', '192.168.1.2']
      【解决方案23】:

      如果您正在寻找与您的本地主机 IP 地址 127.0.0.1 不同的 IPV4 地址,这里有一段简洁的 Python 代码:

      import subprocess
      address = subprocess.check_output(['hostname', '-s', '-I'])
      address = address.decode('utf-8') 
      address=address[:-1]
      

      也可以写成一行:

      address = subprocess.check_output(['hostname', '-s', '-I']).decode('utf-8')[:-1]
      

      即使您将localhost 放入/etc/hostname,代码仍然会给出您的本地IP 地址。

      【讨论】:

        【解决方案24】:

        127.0.1.1 您的真实 IP 地址。更一般地说,一台计算机可以有任意数量的 IP 地址。您可以为专用网络过滤它们 - 127.0.0.0/8、10.0.0.0/8、172.16.0.0/12 和 192.168.0.0/16。

        但是,没有跨平台的方式来获取所有 IP 地址。在 Linux 上,您可以使用 SIOCGIFCONF ioctl。

        【讨论】:

        • 他的意思是他的外部可见IP。 127.*.*.*这个范围一般是指localhost或者内网,这显然不是他想要的。
        【解决方案25】:

        使用 IP 命令并返回 IPv4 和 IPv6 地址的命令版本的轻微改进:

        import commands,re,socket
        
        #A generator that returns stripped lines of output from "ip address show"
        iplines=(line.strip() for line in commands.getoutput("ip address show").split('\n'))
        
        #Turn that into a list of IPv4 and IPv6 address/mask strings
        addresses1=reduce(lambda a,v:a+v,(re.findall(r"inet ([\d.]+/\d+)",line)+re.findall(r"inet6 ([\:\da-f]+/\d+)",line) for line in iplines))
        #addresses1 now looks like ['127.0.0.1/8', '::1/128', '10.160.114.60/23', 'fe80::1031:3fff:fe00:6dce/64']
        
        #Get a list of IPv4 addresses as (IPstring,subnetsize) tuples
        ipv4s=[(ip,int(subnet)) for ip,subnet in (addr.split('/') for addr in addresses1 if '.' in addr)]
        #ipv4s now looks like [('127.0.0.1', 8), ('10.160.114.60', 23)]
        
        #Get IPv6 addresses
        ipv6s=[(ip,int(subnet)) for ip,subnet in (addr.split('/') for addr in addresses1 if ':' in addr)]
        

        【讨论】:

          【解决方案26】:

          你可以在 GNU/Linux 上使用命令“ip route”来知道你当前的 IP 地址。

          这显示了路由器/调制解调器上运行的 DHCP 服务器为接口分配的 IP。通常“192.168.1.1/24”是本地网络的IP,其中“24”表示DHCP服务器在掩码范围内给出的可能IP地址范围。

          这是一个示例:请注意,PyNotify 只是为了说明我的观点而添加的,根本不需要

          #! /usr/bin/env python
          
          import sys , pynotify
          
          if sys.version_info[1] != 7:
             raise RuntimeError('Python 2.7 And Above Only')       
          
          from subprocess import check_output # Available on Python 2.7+ | N/A 
          
          IP = check_output(['ip', 'route'])
          Split_Result = IP.split()
          
          # print Split_Result[2] # Remove "#" to enable
          
          pynotify.init("image")
          notify = pynotify.Notification("Ip", "Server Running At:" + Split_Result[2] , "/home/User/wireless.png")    
          notify.show()    
          

          这样做的好处是您不需要指定网络接口。这在运行套接字服务器时非常有用

          您可以使用 easy_install 甚至 Pip 安装 PyNotify:

          easy_install py-notify
          

          pip install py-notify
          

          或在 python 脚本/解释器中

          from pip import main
          
          main(['install', 'py-notify'])
          

          【讨论】:

            【解决方案27】:

            要获取 IP 地址,您可以直接在 python 中使用 shell 命令

            import socket, subprocess
            
            def get_ip_and_hostname():
                hostname =  socket.gethostname()
            
                shell_cmd = "ifconfig | awk '/inet addr/{print substr($2,6)}'"
                proc = subprocess.Popen([shell_cmd], stdout=subprocess.PIPE, shell=True)
                (out, err) = proc.communicate()
            
                ip_list = out.split('\n')
                ip = ip_list[0]
            
                for _ip in ip_list:
                    try:
                        if _ip != "127.0.0.1" and _ip.split(".")[3] != "1":
                            ip = _ip
                    except:
                        pass
                return ip, hostname
            
            ip_addr, hostname = get_ip_and_hostname()
            

            【讨论】:

            • 请注意,ifconfig 已被弃用——即使早在 2016 年编写此答案时,它也不支持所有可用的内核套接字和地址类型,并且可以默默地隐藏一些东西(比如不绑定到命名别名),较新的 iproute2 工具(例如 ip addr list)可以显示。
            【解决方案28】:

            对于 *nix 系统上的 IP 地址列表,

            import subprocess
            co = subprocess.Popen(['ifconfig'], stdout = subprocess.PIPE)
            ifconfig = co.stdout.read()
            ip_regex = re.compile('((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-4]|2[0-5][0-9]|[01]?[0-9][0-9]?))')
            [match[0] for match in ip_regex.findall(ifconfig, re.MULTILINE)]
            

            虽然这个答案有点晚了,但我认为其他人可能会觉得它很有用:-)

            PS : 它还会返回广播地址和网络掩码。

            【讨论】:

            • FWIW,我发现hostname -ihostname -I(注意大写i)是ifconfig 的更简单替代方案。大写版本返回所有地址,而小写返回“默认”,可能是127.0.1.1(即无用)
            • hostname -I(大写字母 I)在各种操作系统的旧版本中不可用。例如,CentOS 5.2。所以,我想为了安全起见,应该首选上面的脚本。 PS:感谢您的评论。该命令对最新的操作系统版本很有帮助。
            • 值得注意的是,Rob 建议的主机名使用是 Linux 特定的。例如,如果您调用以 root 身份给出的命令,Solaris 会很乐意将您的主机名更改为“-I”。
            • 感谢@EliHeady 的留言,它拯救了数百万人的生命:D
            【解决方案29】:

            注意:这不是使用标准库,而是很简单。

            $ pip install pif

            from pif import get_public_ip
            get_public_ip()
            

            【讨论】:

            • 问题是关于使用 stdlib 查找 IP
            【解决方案30】:

            netifaces 可通过 pip 和 easy_install 获得。 (我知道,它不在基础版本中,但值得安装。)

            netifaces 确实有一些跨平台的奇怪之处:

            • 可能并不总是包含 localhost/loop-back 接口 (Cygwin)。
            • 地址按协议(例如 IPv4、IPv6)列出,协议按接口列出。在某些系统 (Linux) 上,每个协议-接口对都有自己的关联接口(使用 interface_name:n 表示法),而在其他系统 (Windows) 上,单个接口将具有每个协议的地址列表。在这两种情况下都有一个协议列表,但它可能只包含一个元素。

            这里有一些 netifaces 代码可以玩:

            import netifaces
            
            PROTO = netifaces.AF_INET   # We want only IPv4, for now at least
            
            # Get list of network interfaces
            # Note: Can't filter for 'lo' here because Windows lacks it.
            ifaces = netifaces.interfaces()
            
            # Get all addresses (of all kinds) for each interface
            if_addrs = [netifaces.ifaddresses(iface) for iface in ifaces]
            
            # Filter for the desired address type
            if_inet_addrs = [addr[PROTO] for addr in if_addrs if PROTO in addr]
            
            iface_addrs = [s['addr'] for a in if_inet_addrs for s in a if 'addr' in s]
            # Can filter for '127.0.0.1' here.
            

            上面的代码没有将地址映射回它的接口名称(对于动态生成 ebtables/iptables 规则很有用)。所以这里有一个版本,将上述信息与接口名称保存在一个元组中:

            import netifaces
            
            PROTO = netifaces.AF_INET   # We want only IPv4, for now at least
            
            # Get list of network interfaces
            ifaces = netifaces.interfaces()
            
            # Get addresses for each interface
            if_addrs = [(netifaces.ifaddresses(iface), iface) for iface in ifaces]
            
            # Filter for only IPv4 addresses
            if_inet_addrs = [(tup[0][PROTO], tup[1]) for tup in if_addrs if PROTO in tup[0]]
            
            iface_addrs = [(s['addr'], tup[1]) for tup in if_inet_addrs for s in tup[0] if 'addr' in s]
            

            而且,不,我不喜欢列表推导。这就是我现在大脑的工作方式。

            下面的sn-p会全部打印出来:

            from __future__ import print_function  # For 2.x folks
            from pprint import pprint as pp
            
            print('\nifaces = ', end='')
            pp(ifaces)
            
            print('\nif_addrs = ', end='')
            pp(if_addrs)
            
            print('\nif_inet_addrs = ', end='')
            pp(if_inet_addrs)
            
            print('\niface_addrs = ', end='')
            pp(iface_addrs)
            

            享受吧!

            【讨论】:

            • netifaces 在处理这个问题时确实让生活轻松了很多。
            猜你喜欢
            • 1970-01-01
            • 2011-07-02
            • 2013-04-26
            • 1970-01-01
            • 2021-10-06
            相关资源
            最近更新 更多