如何在Python中验证IP地址?

185

如何验证用户输入的IP地址是否有效?输入的IP地址是以字符串形式提供的。


我想指出的是,如果广播地址不被视为有效地址,那么迄今为止提出的每个解决方案都会失败。您必须根据子网掩码进行测试,以确定它是否为广播地址。 - Bitt
导入ipaddress模块,使用ipaddress.ip_address(your_input_text)函数,并捕获ValueError异常。该函数在标准库中。 - user3064538
11个回答

195

不要解析它,只需询问。

import socket

try:
    socket.inet_aton(addr)
    # legal
except socket.error:
    # Not legal

24
嗯,似乎接受像“4”和“192.168”这样的东西,并默默地用零填充其余部分。从技术上讲是有效的,我相信,但不完全是我所期望的。 - krupan
6
不,不能使用所有合法的IP地址:>>> socket.inet_aton("2001:660::1") Traceback (most recent call last): File "", line 1, in socket.error: illegal IP address string passed to inet_aton - bortzmeyer
26
如果你想支持IPv6,那么socket.inet_pton(socket_family, address)就是你需要的。你仍然需要指定地址族。inet_aton仅支持IPv4,不支持其他任何东西。 - richo
9
看起来 Richo 的回复解决了 krupan 和 bortzmeyer 的问题。使用 socket.inet_pton,家族参数为 socket.AF_INET 或 socket.AF_INET6,可以验证 IPv4 和 IPv6 地址,同时不接受不完整的地址。 - freb
9
inet_aton() 函数不会接受“无效”的 IP 地址,但它会接受 "4"、"192.168" 和 "127.1"。它只是简单地使用底层 C 语言的行为——可以参考文档。对于 127.1,该函数将 127 放在最高位,并将 1 解析为一个 24 位数字,然后将其分割成剩余三个八位字节。这样做的目的是支持增量 IP 的 /16 范围,因此您可以从 172.16.1...172.16.255 转移到 172.16.256,而不需要更改您的计算方式以转到 172.16.1.0。 - IBBoard
显示剩余9条评论

106
从Python 3.4开始,检查IPv6或IPv4地址是否正确的最佳方法是使用Python标准库模块ipaddress - IPv4 / IPv6操作库,详细文档请查看https://docs.python.org/3/library/ipaddress.html
示例:
#!/usr/bin/env python

import ipaddress
import sys

try:
    ip = ipaddress.ip_address(sys.argv[1])
    print('%s is a correct IP%s address.' % (ip, ip.version))
except ValueError:
    print('address/netmask is invalid: %s' % sys.argv[1])
except:
    print('Usage : %s  ip' % sys.argv[0])

其他版本:Github,phihag / Philipp Hagemeister,“Python 3.3的ipaddress适用于旧版Python版本”

可以通过Anaconda Python 2.7等方式获取来自phihag的后备版本,并已包含在安装程序中。请参见https://docs.continuum.io/anaconda/pkg-docs

使用pip进行安装:

pip install ipaddress

s.a.: ipaddress 1.0.17,是一款“IPv4/IPv6操作库”,是3.3+版本的ipaddress模块的移植版,https://pypi.python.org/pypi/ipaddress/1.0.17


我遇到了这个错误`C:\Python\Codes>check_ip.py File "C:\Python\Codes\check_ip.py", line 8 print '%s is a correct IP%s address.' % (ip, ip.version) ^ SyntaxError: invalid syntaxC:\Python\Codes>` - user9013730
2
感谢@Yohann。对于Python 3.5,print语句需要加上括号,否则代码将会产生错误。由于空间有限,我将在下面的答案部分更新代码。希望这能帮助其他人。 - user9013730
这将返回test.example.com的错误响应。我得到了IPv6Address(u'7465:7374:2e65:7861:6d70:6c65:2e63:6f6d') - SharkIng
抱歉,我是新手。在这个例子中,sys.argv[0]/[1] 扮演的角色是什么?当运行脚本时,这些只是你传递的测试参数(例如IP地址),还是在实现时需要它们?从这里的阅读(https://www.geeksforgeeks.org/how-to-use-sys-argv-in-python/)来看,它们似乎只是测试参数。 - ericOnline

81
import socket

def is_valid_ipv4_address(address):
    try:
        socket.inet_pton(socket.AF_INET, address)
    except AttributeError:  # no inet_pton here, sorry
        try:
            socket.inet_aton(address)
        except socket.error:
            return False
        return address.count('.') == 3
    except socket.error:  # not a valid address
        return False

    return True

def is_valid_ipv6_address(address):
    try:
        socket.inet_pton(socket.AF_INET6, address)
    except socket.error:  # not a valid address
        return False
    return True

1
为什么这行代码是 "return address.count('.') == 3"?这是你调试时留下的吗? - quux
21
@quux: 不行。这是一个长时间的讨论,人们不喜欢在Linux和Windows上缩短地址被认为是可以接受的事实。例如,socket.inet_aton('127.1')的结果与'127.0.0.1'完全相同,都是'\x7f\x00\x00\x01'。我曾在其他地方(SO)进行了这个烦人而冗长的讨论,但我记不得具体在哪里了。 - tzot
2
在Windows上怎么样? - towry
请注意,这是仅适用于Unix的答案。 - cowlinator
2
@cowlinator inet_pton 只存在于 Unix 中,而 inet_aton 存在于所有平台中,因此这是一个“Unix为主”的答案。 - tzot

68

针对IP地址处理设计的模块IPy,会对无效地址抛出ValueError异常。

>>> from IPy import IP
>>> IP('127.0.0.1')
IP('127.0.0.1')
>>> IP('277.0.0.1')
Traceback (most recent call last):
 ...
ValueError: '277.0.0.1': single byte must be 0 <= byte < 256
>>> IP('foobar')
Traceback (most recent call last):
 ...
ValueError: invalid literal for long() with base 10: 'foobar'

然而,与Dustin的回答类似,它将接受像“4”和“192.168”这样的内容,因为如前所述,这些是IP地址的有效表示。

如果您使用的是Python 3.3或更高版本,则现在已包含ipaddress模块

>>> import ipaddress
>>> ipaddress.ip_address('127.0.0.1')
IPv4Address('127.0.0.1')
>>> ipaddress.ip_address('277.0.0.1')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.3/ipaddress.py", line 54, in ip_address
    address)
ValueError: '277.0.0.1' does not appear to be an IPv4 or IPv6 address
>>> ipaddress.ip_address('foobar')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.3/ipaddress.py", line 54, in ip_address
    address)
ValueError: 'foobar' does not appear to be an IPv4 or IPv6 address

对于Python 2,如果您安装了python-ipaddress,您可以使用ipaddress获取相同的功能:

pip install ipaddress

这个模块与Python 2兼容,并提供了一个非常类似于Python标准库中自Python 3.3起包含的ipaddress模块的API。更多细节在这里。在Python 2中,您需要显式地将IP地址字符串转换为unicode:ipaddress.ip_address(u'127.0.0.1')


很棒的想法。到目前为止,唯一能够适用于所有IP地址的解决方案。
from IPy import IP IP("2001:660::1") IP('2001:660::1')
- bortzmeyer
对于Python 2,使用pip install ipaddress即可获得几乎相同的API :) - radtek
关于 import ipaddress,当我传递一个IPv4地址时,我得到的输出是 IPv4Address('127.0.0.1')。但是当我尝试将其转换为string以检查它是否包含IPv4IPv6时,我只得到了IP地址。怎样才能在代码中知道它是IPv4还是IPv6呢?使用if "IPv4" in str(type(val)): 是一个好主意吗? - Aakash Goyal
使用完全相同的步骤,出现了“ipaddress.AddressValueError: '127.0.0.1' 似乎不是IPv4或IPv6地址。你是否传入了一个bytes(在Python 2中为str)而不是一个unicode对象?”的错误。错误消息提供的帮助帮助我解决了这个问题。在Python 2中需要使用u'...' - rbaleksandar
1
解决方案就是这么简单:try: return bool(ipaddress.ip_address(ip)) except ValueError: return False - Hett

46
def is_valid_ip(ip):
    """Validates IP addresses.
    """
    return is_valid_ipv4(ip) or is_valid_ipv6(ip)

IPv4:

def is_valid_ipv4(ip):
    """Validates IPv4 addresses.
    """
    pattern = re.compile(r"""
        ^
        (?:
          # Dotted variants:
          (?:
            # Decimal 1-255 (no leading 0's)
            [3-9]\d?|2(?:5[0-5]|[0-4]?\d)?|1\d{0,2}
          |
            0x0*[0-9a-f]{1,2}  # Hexadecimal 0x0 - 0xFF (possible leading 0's)
          |
            0+[1-3]?[0-7]{0,2} # Octal 0 - 0377 (possible leading 0's)
          )
          (?:                  # Repeat 0-3 times, separated by a dot
            \.
            (?:
              [3-9]\d?|2(?:5[0-5]|[0-4]?\d)?|1\d{0,2}
            |
              0x0*[0-9a-f]{1,2}
            |
              0+[1-3]?[0-7]{0,2}
            )
          ){0,3}
        |
          0x0*[0-9a-f]{1,8}    # Hexadecimal notation, 0x0 - 0xffffffff
        |
          0+[0-3]?[0-7]{0,10}  # Octal notation, 0 - 037777777777
        |
          # Decimal notation, 1-4294967295:
          429496729[0-5]|42949672[0-8]\d|4294967[01]\d\d|429496[0-6]\d{3}|
          42949[0-5]\d{4}|4294[0-8]\d{5}|429[0-3]\d{6}|42[0-8]\d{7}|
          4[01]\d{8}|[1-3]\d{0,9}|[4-9]\d{0,8}
        )
        $
    """, re.VERBOSE | re.IGNORECASE)
    return pattern.match(ip) is not None

IPv6:

def is_valid_ipv6(ip):
    """Validates IPv6 addresses.
    """
    pattern = re.compile(r"""
        ^
        \s*                         # Leading whitespace
        (?!.*::.*::)                # Only a single whildcard allowed
        (?:(?!:)|:(?=:))            # Colon iff it would be part of a wildcard
        (?:                         # Repeat 6 times:
            [0-9a-f]{0,4}           #   A group of at most four hexadecimal digits
            (?:(?<=::)|(?<!::):)    #   Colon unless preceeded by wildcard
        ){6}                        #
        (?:                         # Either
            [0-9a-f]{0,4}           #   Another group
            (?:(?<=::)|(?<!::):)    #   Colon unless preceeded by wildcard
            [0-9a-f]{0,4}           #   Last group
            (?: (?<=::)             #   Colon iff preceeded by exacly one colon
             |  (?<!:)              #
             |  (?<=:) (?<!::) :    #
             )                      # OR
         |                          #   A v4 address with NO leading zeros 
            (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)
            (?: \.
                (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)
            ){3}
        )
        \s*                         # Trailing whitespace
        $
    """, re.VERBOSE | re.IGNORECASE | re.DOTALL)
    return pattern.match(ip) is not None

IPv6版本使用“(?:(?<=::)|(?<!::):)”,在支持带有环视条件的正则表达式引擎上可以替换为“(?(?<!::):)”(即PCRE、.NET)。
编辑:
- 删除了本地变量。 - 扩展了正则表达式以符合RFC。 - 添加了另一个用于IPv6地址的正则表达式。
编辑2:
我找到了一些讨论如何使用正则表达式解析IPv6地址的链接:
- A Regular Expression for IPv6 Addresses - InterMapper论坛 - Working IPv6 regular expression - Patrick's playground博客 - test-ipv6-regex.pl - 带有大量测试用例的Perl脚本。看起来我的正则表达式在其中一些测试中失败了。

编辑3:

终于成功编写了一个通过所有测试的模式,并且我也很满意。


1
不可以,只支持IPv4地址。 - bortzmeyer
test-ipv6-regex.pl 是黄金+1。 - Marcin
似乎无法处理使用方括号的IPv6地址(例如,用于指定端口号):[2001:4860:4860::8888]:80 - winklerrr
1
我认为这种方式高度不符合Python风格,而且过于复杂。使用来自ipaddress.ip_address的简单调用应该足够了。 - Hett

18

我希望它足够简单和符合Python的编程方式:

def is_valid_ip(ip):
    m = re.match(r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$", ip)
    return bool(m) and all(map(lambda n: 0 <= int(n) <= 255, m.groups()))

你正在重复相同的模式三次... - warfares
8
这是一个正则表达式,用于匹配IP地址。它可以匹配由四个数字组成的IP地址,每个数字之间由句点分隔开。如果您在此输入一个IP地址,它将返回正则表达式是否成功匹配该地址。 - warfares
2
@warfaresthat是有原因的,它需要将组分开以便检查值是否在0和255范围内,这就是返回值的第二部分所做的事情。如果你想使用你的正则表达式,只需使用return bool(m)即可。 - alsotoes

11

我想这样做会行...

def validIP(address):
    parts = address.split(".")
    if len(parts) != 4:
        return False
    for item in parts:
        if not 0 <= int(item) <= 255:
            return False
    return True

2
你可能需要捕获 int() 中的 ValueError 异常,以防用户输入的是 "a.b.c.d" 而不是整数。 - Greg Hewgill
9
错误的代码,只能用于IPv4地址。 - bortzmeyer
3
Python的int()函数在这里太宽松了,例如会去除空格。 - Daira Hopwood
2
192.168.178.0030 是有效的。 - OrangeTux
1
不支持IPv6。 - tim-oleksii

8
将IPv4地址视为"ip"。
if re.match(r'^((\d{1,2}|1\d{2}|2[0-4]\d|25[0-5])\.){3}(\d{1,2}|1\d{2}|2[0-4]\d|25[0-5])$', ip):  
    print("Valid IP")  
else:
    print("Invalid IP")

6

我必须要感谢Markus Jarderot的文章——我的大部分文章都是从他那里得到启发的。

我发现Markus的答案仍然无法通过他的答案所引用的Perl脚本中的一些IPv6示例。

以下是我的正则表达式,可以通过那个Perl脚本中所有的示例:

r"""^
     \s* # Leading whitespace
     # Zero-width lookaheads to reject too many quartets
     (?:
        # 6 quartets, ending IPv4 address; no wildcards
        (?:[0-9a-f]{1,4}(?::(?!:))){6}
             (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d|\d)
        (?:\.(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}
      |
        # 0-5 quartets, wildcard, ending IPv4 address
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,4}[0-9a-f]{1,4})?
        (?:::(?!:))
             (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d|\d)
        (?:\.(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}
      |
        # 0-4 quartets, wildcard, 0-1 quartets, ending IPv4 address
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,3}[0-9a-f]{1,4})?
        (?:::(?!:))
        (?:[0-9a-f]{1,4}(?::(?!:)))?
             (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d|\d)
        (?:\.(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}
      |
        # 0-3 quartets, wildcard, 0-2 quartets, ending IPv4 address
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,2}[0-9a-f]{1,4})?
        (?:::(?!:))
        (?:[0-9a-f]{1,4}(?::(?!:))){0,2}
             (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d|\d)
        (?:\.(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}
      |
        # 0-2 quartets, wildcard, 0-3 quartets, ending IPv4 address
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,1}[0-9a-f]{1,4})?
        (?:::(?!:))
        (?:[0-9a-f]{1,4}(?::(?!:))){0,3}
             (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d|\d)
        (?:\.(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}
      |
        # 0-1 quartets, wildcard, 0-4 quartets, ending IPv4 address
        (?:[0-9a-f]{1,4}){0,1}
        (?:::(?!:))
        (?:[0-9a-f]{1,4}(?::(?!:))){0,4}
             (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d|\d)
        (?:\.(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}
      |
        # wildcard, 0-5 quartets, ending IPv4 address
        (?:::(?!:))
        (?:[0-9a-f]{1,4}(?::(?!:))){0,5}
             (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d|\d)
        (?:\.(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}
      |
        # 8 quartets; no wildcards
        (?:[0-9a-f]{1,4}(?::(?!:))){7}[0-9a-f]{1,4}
      |
        # 0-7 quartets, wildcard
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,6}[0-9a-f]{1,4})?
        (?:::(?!:))
      |
        # 0-6 quartets, wildcard, 0-1 quartets
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,5}[0-9a-f]{1,4})?
        (?:::(?!:))
        (?:[0-9a-f]{1,4})?
      |
        # 0-5 quartets, wildcard, 0-2 quartets
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,4}[0-9a-f]{1,4})?
        (?:::(?!:))
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,1}[0-9a-f]{1,4})?
      |
        # 0-4 quartets, wildcard, 0-3 quartets
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,3}[0-9a-f]{1,4})?
        (?:::(?!:))
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,2}[0-9a-f]{1,4})?
      |
        # 0-3 quartets, wildcard, 0-4 quartets
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,2}[0-9a-f]{1,4})?
        (?:::(?!:))
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,3}[0-9a-f]{1,4})?
      |
        # 0-2 quartets, wildcard, 0-5 quartets
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,1}[0-9a-f]{1,4})?
        (?:::(?!:))
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,4}[0-9a-f]{1,4})?
      |
        # 0-1 quartets, wildcard, 0-6 quartets
        (?:[0-9a-f]{1,4})?
        (?:::(?!:))
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,5}[0-9a-f]{1,4})?
      |
        # wildcard, 0-7 quartets
        (?:::(?!:))
        (?:(?:[0-9a-f]{1,4}(?::(?!:))){0,6}[0-9a-f]{1,4})?
     )
     (?:/(?:1(?:2[0-7]|[01]\d)|\d\d?))? # With an optional CIDR routing prefix (0-128)
     \s* # Trailing whitespace
    $"""

我也编写了一个Python脚本来测试所有这些IPv6示例;它在Pastebin上,链接为这里,因为它太大了无法在此处发布。
您可以使用以“[结果]=[示例]”形式的测试结果和示例参数运行该脚本,例如:
python script.py Fail=::1.2.3.4: pass=::127.0.0.1 false=::: True=::1

或者您可以不指定任何参数,直接运行所有测试,像这样:

python script.py

无论如何,我希望这可以帮助其他人!

4
虽然我非常欣赏你的努力,但我认为你的构造存在一个严重的设计缺陷:它太大了!如果没有成千上万的人多年使用过这样大小的正则表达式,我是不会信任它的。 - erikbstack
1
@erikb85:看一下我在Pastebin上发布的脚本。它包含了1154个不同IPv6格式的测试,并且全部通过了。如果你认为需要更多的测试,可以随意修改我的脚本,添加测试,并发布结果。 - blag
我发现只需使用 ipaddress.IPv6Address(my_addr) 就可以匹配您发布的每个测试用例结果,因此如果您使用的是Python 3.3+,则可以使用该函数...请注意相应的IPv4函数不太好,不能通过一些(不同的)测试用例,特别是涉及前导零的情况。 - user9645

3
我提出了这个简单版本
def ip_checkv4(ip):
        parts=ip.split(".")
        if len(parts)<4 or len(parts)>4:
            return "invalid IP length should be 4 not greater or less than 4"
        else:
            while len(parts)== 4:
                a=int(parts[0])
                b=int(parts[1])
                c=int(parts[2])
                d=int(parts[3])
                if a<= 0 or a == 127 :
                    return "invalid IP address"
                elif d == 0:
                    return "host id  should not be 0 or less than zero " 
                elif a>=255:
                    return "should not be 255 or greater than 255 or less than 0 A"
                elif b>=255 or b<0: 
                    return "should not be 255 or greater than 255 or less than 0 B"
                elif c>=255 or c<0:
                    return "should not be 255 or greater than 255 or less than 0 C"
                elif d>=255 or c<0:
                    return "should not be 255 or greater than 255 or less than 0 D"
                else:
                    return "Valid IP address ", ip
        
    p=raw_input("Enter IP address")
    print ip_checkv4(p)

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