杰瑞科技汇

python 正则表达式 ip地址

IPv4 地址正则表达式

IPv4 地址由四个十进制数字组成,每个数字的范围是 0 到 255,数字之间用点()分隔,168.1.1

python 正则表达式 ip地址-图1
(图片来源网络,侵删)

简单但不精确的版本

一个最简单的正则表达式来匹配 "点分十进制" 格式是:

import re
pattern = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"

问题所在:这个表达式会匹配 999.999.999 这样的非法 IP 地址,因为它只检查了数字和点的格式,没有验证每个数字的范围(0-255)。

精确的版本(推荐)

为了精确匹配合法的 IPv4 地址,我们需要确保每个 8 位组(octet)都在 0 到 255 之间,这需要一个更复杂的正则表达式。

核心思路

python 正则表达式 ip地址-图2
(图片来源网络,侵删)
  1. 匹配 0-1991\d{2} (匹配 100-199), [1-9]\d (匹配 10-99), [0-9] (匹配 0-9),可以合并为 (?:1\d{2}|[1-9]\d|\d)
  2. 匹配 200-2492[0-4]\d
  3. 匹配 250-25525[0-5]
  4. 将以上三种情况合并,得到匹配 0-255 的表达式:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)
  5. \. 匹配点,并将这个整体重复四次,用 \. 连接起来。

最终的正则表达式

import re
# 使用非捕获组 (?:...) 来提高效率,避免捕获不必要的子组
ipv4_pattern = r"""
    (?:25[0-5]|       # 匹配 250-255
       2[0-4][0-9]|   # 匹配 200-249
       1[0-9]{2}|     # 匹配 100-199
       [1-9][0-9]|    # 匹配 10-99
       [0-9])         # 匹配 0-9
    \.                # 匹配一个点
    {3}               # 重复前面的模式3次(因为有3个点)
    (?:25[0-5]|       # 匹配最后一个 0-255 的数字
       2[0-4][0-9]|
       1[0-9]{2}|
       [1-9][0-9]|
       [0-9])
"""

使用示例

import re
# 编译正则表达式,re.VERBOSE 允许我们写注释和换行,使表达式更易读
ipv4_regex = re.compile(ipv4_pattern, re.VERBOSE)
# 测试用例
valid_ips = [
    "192.168.1.1",
    "10.0.0.1",
    "127.0.0.1",
    "0.0.0.0",
    "255.255.255.255",
    "8.8.8.8"
]
invalid_ips = [
    "256.1.1.1",   # 数字超出范围
    "192.168.01.1",# 前导零(虽然有些系统允许,但标准正则通常不匹配)
    "192.168.1",   # 缺少一位
    "192.168.1.1.1",# 多了一位
    "a.b.c.d",     # 非数字字符
    " 192.168.1.1 " # 包含空格
]
print("--- 验证有效的 IPv4 地址 ---")
for ip in valid_ips:
    if ipv4_regex.fullmatch(ip):
        print(f"'{ip}' 是一个有效的 IPv4 地址。")
    else:
        print(f"'{ip}' 不是一个有效的 IPv4 地址。 (意外!)")
print("\n--- 验证无效的 IPv4 地址 ---")
for ip in invalid_ips:
    if not ipv4_regex.fullmatch(ip):
        print(f"'{ip}' 不是一个有效的 IPv4 地址。")
    else:
        print(f"'{ip}' 是一个有效的 IPv4 地址。 (意外!)")

关键点

  • fullmatch():确保整个字符串都匹配 IP 地址格式,避免从长字符串中提取出 IP 地址,用 fullmatch("192.168.1.1") 会成功,而 fullmatch("my ip is 192.168.1.1") 会失败。
  • re.VERBOSE:这是一个强大的标志,允许你在正则表达式中添加空格和注释,极大地提高了复杂正则的可读性。

IPv6 地址正则表达式

IPv6 地址比 IPv4 复杂得多,它由 8 组 4 位的十六进制数表示,组之间用冒号()分隔。2001:0db8:85a3:0000:0000:8a2e:0370:7334

python 正则表达式 ip地址-图3
(图片来源网络,侵删)

核心规则

  1. 每组是 1 到 4 个十六进制字符(不区分大小写)。
  2. 可以有连续的零组,可以用双冒号 进行压缩( 在一个地址中只能出现一次)。
  3. 为了方便,IPv4 地址可以嵌入在 IPv6 地址的最后 32 位,形成 IPv4 映射地址,:ffff:192.168.1.1

精确的版本(非常复杂)

匹配所有合法的 IPv6 地址(包括压缩格式和 IPv4 映射)是一个巨大的挑战,下面是一个公认的比较全面的正则表达式。

正则表达式

import re
# 这个正则表达式非常复杂,但能处理大多数标准 IPv6 格式
ipv6_pattern = r"""
    ^
    # 处理 :: 开头的情况,::1
    (?:(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|
    # 处理 :: 结尾的情况,2001::
    (?:(?:[0-9A-Fa-f]{1,4}:){1,7}:)|
    # 处理 :: 在中间的情况,2001::1
    (?:(?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4})|
    # 处理 :: 在中间,且前面有多个组的情况
    (?:(?:[0-9A-Fa-f]{1,4}:){1,5}(?::[0-9A-Fa-f]{1,4}){1,2})|
    (?:(?:[0-9A-Fa-f]{1,4}:){1,4}(?::[0-9A-Fa-f]{1,4}){1,3})|
    (?:(?:[0-9A-Fa-f]{1,4}:){1,3}(?::[0-9A-Fa-f]{1,4}){1,4})|
    (?:(?:[0-9A-Fa-f]{1,4}:){1,2}(?::[0-9A-Fa-f]{1,4}){1,5})|
    # 处理 :: 在最前面,后面有多个组的情况
    [0-9A-Fa-f]{1,4}:(?::[0-9A-Fa-f]{1,4}){1,6}|
    # 处理单独的 ::,表示全0
    :(?:(?::[0-9A-Fa-f]{1,4}){1,7}|:)|
    # 处理 IPv4 映射地址
    (?:(?:[0-9A-Fa-f]{1,4}:){1,7}:)|                                     # ::ffff:192.168.1.1
    (?:IPv4的精确正则表达式)                                            # 这个部分比较复杂,下面会简化
)
    $
"""

简化版的 IPv6 正则(推荐用于多数场景)

由于上述表达式过于复杂,在实际应用中,一个简化但能覆盖大部分情况的版本可能更实用,这个版本不处理 的所有变体,但能匹配标准格式和 IPv4 映射。

import re
# 简化的 IPv6 正则表达式
# 它匹配标准的 8 组格式,以及 :: 开头/中间的情况,但不穷举所有组合
# 它也匹配 IPv4 映射地址
simplified_ipv6_pattern = r"""
    ^
    # 标准格式 8组
    (?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|
    # 包含 :: 的格式
    (?:(?:[0-9A-Fa-f]{1,4}:){1,7}:)|
    (?:(?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4})|
    (?:(?:[0-9A-Fa-f]{1,4}:){1,5}(?::[0-9A-Fa-f]{1,4}){1,2})|
    (?:(?:[0-9A-Fa-f]{1,4}:){1,4}(?::[0-9A-Fa-f]{1,4}){1,3})|
    (?:(?:[0-9A-Fa-f]{1,4}:){1,3}(?::[0-9A-Fa-f]{1,4}){1,4})|
    (?:(?:[0-9A-Fa-f]{1,4}:){1,2}(?::[0-9A-Fa-f]{1,4}){1,5})|
    [0-9A-Fa-f]{1,4}:(?::[0-9A-Fa-f]{1,4}){1,6}|
    :(?:(?::[0-9A-Fa-f]{1,4}){1,7}|:)|
    # IPv4 映射地址
    (?:[0-9A-Fa-f]{1,4}:){1,7}:IPv4精确正则表达式
    $
"""

更推荐的实践:使用 ipaddress 模块

由于 IPv6 正则表达式极其复杂且容易出错,Python 官方推荐使用 ipaddress 模块来处理 IP 地址,因为它更可靠、更安全,并且能处理所有边缘情况。

import ipaddress
def is_valid_ip(ip_str):
    try:
        # 尝试创建一个 IP 地址对象
        # ip_str 是有效的 IPv4 或 IPv6 地址,它会成功
        ipaddress.ip_address(ip_str)
        return True
    except ValueError:
        # 如果不是有效的 IP 地址,会抛出 ValueError
        return False
# 测试
print(is_valid_ip("192.168.1.1"))      # True
print(is_valid_ip("2001:0db8::1"))     # True
print(is_valid_ip("::ffff:192.168.1.1")) # True
print(is_valid_ip("256.1.1.1"))       # False
print(is_valid_ip("not.an.ip"))       # False

同时匹配 IPv4 和 IPv6

如果你需要一个正则表达式来同时匹配 IPv4 和 IPv6 地址,你可以将两者的正则表达式用 (或) 连接起来。

import re
# 使用前面定义的精确 ipv4_pattern 和简化 ipv6_pattern
# 注意:这里为了清晰,重新写了一遍
ipv4_pattern = r"(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)"
# 这是一个非常基础的 IPv6 匹配,不处理 :: 压缩
# 实际项目中,强烈建议使用 ipaddress 模块
ipv6_pattern = r"(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}"
# 组合起来
ip_pattern = f"({ipv4_pattern}|{ipv6_pattern})"
ip_regex = re.compile(ip_pattern)
text = "我的服务器地址是 192.168.1.1,另一个是 2001:0db8:85a3:0000:0000:8a2e:0370:7334。"
# findall 会返回所有匹配到的 IP 地址
found_ips = ip_regex.findall(text)
print("在文本中找到的 IP 地址是:")
for ip in found_ips:
    print(ip)

总结与最佳实践

方法 优点 缺点 推荐场景
正则表达式 (IPv4) 速度快,适合简单的格式验证。 容易出错,难以覆盖所有规则(如前导零)。 需要在文本中快速查找或提取 IPv4 地址时。
正则表达式 (IPv6) 理论上可以匹配所有格式。 极其复杂,难以编写和维护,容易遗漏边缘情况。 不推荐,除非有特殊需求,否则应避免。
ipaddress 模块 强烈推荐,官方标准,准确、可靠、安全,能处理所有合法/非法 IP 格式。 相比正则,代码量稍多一点,但可读性更好。 所有涉及 IP 地址验证、解析、比较、转换的场景。

最终建议

  • 如果你只是想验证一个字符串是否是合法的 IPv4 地址,使用精确的 IPv4 正则表达式和 fullmatch() 是一个不错的选择。
  • 如果你需要处理 IPv6 地址请立即放弃正则表达式,直接使用 Python 内置的 ipaddress 模块,它能为你省去无数的麻烦和潜在的 bug。
  • 如果你需要从一个大的文本块中同时提取 IPv4 和 IPv6 地址,可以组合使用简化的正则表达式,但请务必清楚其局限性,并对结果进行二次验证(用 ipaddress 模块)。
分享:
扫描分享到社交APP
上一篇
下一篇