使用Python标准库查找本地IP地址

719

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


11
本地IP?还是公共IP?你要如何处理有多个IP的系统? - Sargun Dhillon
使用 ifconfig -a 命令并使用其输出... - Fredrik Pihl
18
@Fredrik,那是个糟糕的想法。首先,你没有必要分叉一个新进程,这可能会防止你的程序在严格锁定的配置中工作(或者你将不得不授予你的程序不需要的权限)。其次,你会为不同语言环境的用户引入错误。第三,如果您决定启动一个新程序,您不应该启动一个已经弃用的程序 - ip addr 更加适合(而且更容易解析)。 - phihag
14
@phihag,你说得完全正确,感谢你指出我的错误。 - Fredrik Pihl
1
这里更根本的问题是,在一个正确编写的现代网络程序中,正确的(一组)本地IP地址取决于对等方或潜在对等方的集合。如果需要将本地IP地址用于将套接字“绑定”到特定接口,则这是一个策略问题。如果需要将本地IP地址交给对等方,以便对等方可以“回调”,即打开到本地机器的连接,则情况取决于是否存在任何NAT(网络地址转换)盒子。如果没有NAT,则getsockname是一个不错的选择。 - Pekka Nikander
如果存在网络地址转换(NAT),而我仍然想使用回调来连接套接字,那该怎么办? - Parth Lathiya
50个回答

24

对 ninjagecko 答案的改进版本。此方法适用于允许 UDP 广播且不需要访问局域网或互联网上的地址的任何 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())

等等,<broadcast> 怎么会是一个有效的主机名?!!这种口头主机名中有多少是有效的? - Dev Aggarwal
1
这个在我的Ubuntu 20.04上有效 - 获取192.168.0.24而不是127.0.0.1。 - Lewis Morris
1
在我测试过的每个系统上都可以运行。Windows,Android,Linux,Mac。 - dlm
在Ubuntu 21.04上运行得非常好,谢谢! - Yannick Mauray
2
在 Mac OS 11.1 上无法工作。看到了这个错误:socket.error: [Errno 49] Can't assign requested address。 - user1766438
显示剩余2条评论

23

我在我的Ubuntu机器上使用这个:

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

这不起作用。


9
自2.6版本起已经被废弃,请使用subprocess模块来运行命令。 - Colin Dunklau
6
ifconfig也已经被弃用了。请使用iproute2。 - Helmut Grohne
获取所有IP地址:导入sh; [ip.split()[1][5:] for ip in filter(lambda x: 'inet addr' in x, sh.ifconfig().split("\n"))] - Gabriel Littman
这让我在Raspbian上获得了本地主机127.0.0.1的IP地址。 - nwgat
我们如何知道ipconfig的输出格式永远不会改变? - Thomas Weller
显示剩余4条评论

18

我相信这个版本还没有发布过。 我在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'

3
适用于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'
- cessor

18

在 Debian(已测试)以及我怀疑的大多数 Linux 系统上...

import commands

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

在 MS Windows 上(已测试)

import socket

socket.gethostbyname(socket.gethostname())

2
在 macOS 上无法工作:hostname: illegal option -- I\nusage: hostname [-fs] [name-of-host] - Derek 朕會功夫
在Python 3中,您需要将“commands”替换为“subprocess”,其余部分保持不变。 - Miguel El Merendero

11

对于Linux系统,您只需像这样使用hostname -I系统命令的check_output

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

对于谷歌员工,我知道这个问题是关于跨平台解决方案的。 - Kasper Skytte Andersen
获取精确的IP地址:check_output(['hostname', '-I']).decode().strip() - iku

10

这是UnkwnTech回答的一个变体 -- 它提供了一个get_local_addr()函数,该函数返回主机的主要局域网IP地址。我发表这篇文章是因为它添加了许多内容:IPv6支持、错误处理、忽略本地主机/链路本地地址,并使用测试网络地址(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”。 - Jamie Lindsey
@JamieLindsey 您是否有关于您的操作系统、网络配置的详细信息?此外,类似于“get_local_addr(remove=”www.google.com“)”这样的东西会返回什么?记录由_get_local_addr()抛出的 socket.error 可能有助于诊断。 - Eli Collins

8

我可以告诉您,这个方法是可行的:

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]未提供节点名或服务名,或者未知 - user1766438

8

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

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

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

1
它不会显示所有的IPv4地址,因为ifconfig只会告诉你关于主要的地址。你需要使用iproute2中的"ip"命令来查看所有的地址。 - Helmut Grohne
这是一个关于标准库的问题,却用了大量的shell。而且,解析ifconfig既不可移植,也不能在一台机器上可靠地工作。 - Dominik George

8
很抱歉,除了连接到另一台计算机并让其发送您的IP地址之外,我恐怕没有其他好的跨平台方法。例如:findmyipaddress。请注意,如果您需要一个在NAT后面的IP地址,这种方法将不起作用,除非您连接的计算机也在NAT后面。

这里有一个适用于Linux的解决方案:获取与网络接口相关联的IP地址


6
import socket
[i[4][0] for i in socket.getaddrinfo(socket.gethostname(), None)]

2
在具有两个网卡的服务器上,这将给出分配的一个IP地址,但重复三次。 在我的笔记本电脑上,它会给出“127.0.1.1”(重复三次…)… - bryn
在Windows桌面上,它给我返回['fe80::34e8:fe19:1459:2cde%22','fe80::d528:99fb:d572:e289%12', '192.168.56.1', '192.168.1.2'] - Nakilon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接