如何找到本地IP地址(即192.168.x。x或10.0.x.x)在Python平台独立,只使用标准库?


当前回答

此方法返回本地盒子上的“主”IP(具有默认路由的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.254.254.254', 1))
            IP = s.getsockname()[0]
        except Exception:
            IP = '127.0.0.1'
        finally:
            s.close()
        return IP
    print(get_ip())

这将返回一个主IP(具有默认路由的IP)。如果您需要将所有IP附加到所有接口(包括localhost等),请参见类似这样的回答。

如果你在一个NAT防火墙后面,比如你家里的wifi路由器,那么这将不会显示你的公共NAT IP,而是显示你在本地网络上的私有IP,它有一个默认路由到你的本地wifi路由器。如果你需要外部IP:

在那个外部设备(wifi路由器)上运行这个功能,或者 连接到外部服务(如https://www.ipify.org/),可以反射从外部世界看到的IP

... 但这些想法与最初的问题完全不同。:)

其他回答

作为别名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。3. Python。x,现代和旧的Linux发行版,OSX/macOS和Windows来查找当前的IPv4地址。 对于有多个IP地址、IPv6、没有配置IP地址或没有互联网访问的机器,将不会返回正确的结果。 据报道,这在最新版本的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地址,将抛出异常。


也可以在没有互联网连接的局域网上工作的版本:

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])

这适用于Python 2和3,在Linux和Windows上,但不能处理多个网络设备或IPv6。然而,它在最近的Linux发行版上停止工作,所以我尝试了这种替代技术。它尝试在53端口8.8.8.8连接谷歌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代码片段。

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

对于linux env,读取/proc/net/tcp,第二个(localaddress)和第三个(remoteaddress)将以六进制格式给出ip。

提示:如果第二列是零(00000000:000),那么它是一个监听端口:)

https://github.com/romol0s/python/blob/master/general/functions/getTcpListenIpsByPort.py

https://www.kernel.org/doc/Documentation/networking/proc_net_tcp.txt

这是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

使用新引入的asyncio包的Python 3.4版本。

async def get_local_ip():
    loop = asyncio.get_event_loop()
    transport, protocol = await loop.create_datagram_endpoint(
        asyncio.DatagramProtocol,
        remote_addr=('8.8.8.8', 80))
    result = transport.get_extra_info('sockname')[0]
    transport.close()
    return result

这是基于UnkwnTech的精彩回答。

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

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中,这就是为什么我在命令中给出完整的路径。我希望这能有所帮助。