使用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个回答

5

这个方法适用于大多数的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()

5

这个答案是我个人尝试解决获取局域网IP的问题的方法,因为socket.gethostbyname(socket.gethostname())也返回了127.0.0.1。这种方法不需要互联网,只需要局域网连接。代码适用于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())

1
如果您在同一网络上的两台计算机上同时运行此程序,会发生什么?当您在网络上广播消息时,所有计算机都将收到“我的局域网IP地址是什么”的信息。您的udp_listening_server可以回复消息“您的IP地址是xxx”。 - Nicolas Defranoux

5

如果你需要一个与本地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]

即使在/etc/hostname中输入 localhost ,代码仍将给出您的本地IP地址。

4

这是一个使用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)]

4
为了获取 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)可以显示。 - Charles Duffy

4

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命令。


3
他指的是他外部可见的IP地址。127...*范围通常是指本地主机或内部网络,显然不是他想要的。 - Cerin

4

使用新引入的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的优秀回答


我点了+1,因为我喜欢(并使用)这个想法,但语法并没有错。 - LogicDaemon
1
@LogicDaemon,谢谢。顺便说一下,我刚刚修复了语法错误。 - Frederik Aalund

4

netifaces可以通过pip和easy_install获取。(我知道,它不是内置的,但安装它可能很值得)

在不同平台上,netifaces存在一些奇怪的问题:

  • 本地主机/环回接口可能不会始终包含(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]

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

下面的代码片段会将所有内容都打印出来:

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真的在处理这个问题时大大地简化了生活。 - Drake Guan

4
您可以在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'])

4
import netifaces as ni 

ni.ifaddresses('eth0')
ip = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr']
print(ip)

这将返回Ubuntu系统和MacOS中的IP地址。输出将是系统IP地址,例如我的IP:192.168.1.10。


2
netifaces不是标准库的一部分,而且已经发布了非常相似的答案。 - Maximilian Peters

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