当前位置: 代码网 > it编程>前端脚本>Python > 99天精通Python正则表达式进阶之分组与断言

99天精通Python正则表达式进阶之分组与断言

2026年09月11日 Python 我要评论
前言在上一节课中,我们学会了用正则表达式匹配"一类"字符串(比如所有手机号、所有邮箱)。但有时候,我们的需求会更精细:我想匹配邮箱,但我只想要 @ 前面的用户名,不要后面的域名。我

前言

在上一节课中,我们学会了用正则表达式匹配"一类"字符串(比如所有手机号、所有邮箱)。但有时候,我们的需求会更精细:

  • 我想匹配邮箱,但我只想要 @ 前面的用户名,不要后面的域名。
  • 我想匹配 html 标签 <h1>标题</h1>,但我必须确保前后的标签名是一致的(不能是 <h1>标题</h2>)。
  • 我想匹配 “windows 10” 中的 “windows”,但前提是它后面必须跟着 “10”(也就是不匹配 “windows xp”)。

这就需要用到正则的进阶大招:分组 (groups)断言 (assertions)。掌握了它们,你就能像外科手术刀一样精准地切割和处理文本。

本节内容:

  • 分组 ()group() 提取
  • 后向引用 \1:匹配重复出现的文本
  • re.sub 的高级替换技巧
  • 零宽断言:(?=...)(?<=...)
  • 实战练习:html 解析与数据清洗

一、分组 (grouping):精准提取

1.1 什么是分组?

在正则中,用圆括号 () 包起来的部分就是一个分组
它的作用主要有两个:

  1. 整体操作:比如 (ab)+ 表示匹配 “ab” 重复多次(abab…),而不加括号 ab+ 表示 a 后面跟着多个 b(abbb…)。
  2. 独立提取:匹配成功后,可以单独获取括号内匹配到的内容。

1.2 使用 group() 获取内容

import re

text = "我的邮箱是 alice@google.com,请联系我。"
# 需求:提取用户名和域名

# 使用两个括号创建两个分组
pattern = r"(\w+)@(\w+\.\w+)"
match = re.search(pattern, text)

if match:
    print(match.group())   # alice@google.com (完整匹配)
    print(match.group(0))  # 同上
    
    print(match.group(1))  # alice (第一个括号的内容)
    print(match.group(2))  # google.com (第二个括号的内容)
    
    print(match.groups())  # ('alice', 'google.com') (所有分组组成的元组)

二、后向引用 (backreference):前后一致

假设我们要匹配 html 标题标签,如 <h1>hello</h1><h3>hi</h3>
如果我们写 <h\d>.*</h\d>,它可能会错误地匹配到 <h1>hello</h2>(头尾不一致)。

为了确保结束标签和开始标签匹配,我们需要用到后向引用\1 代表引用第一个分组匹配到的实际内容

import re

htmls = ["<h1>valid</h1>", "<h2>wrong</h3>", "<h3>also valid</h3>"]

# <(h\d)> : 第一个分组,匹配 h1-h9
# .*?     : 中间内容(非贪婪)
# </\1>   : 引用第一个分组匹配到的内容(如果前面是h1,这里必须也是h1)
pattern = r"<(h\d)>.*?</\1>"

for html in htmls:
    if re.search(pattern, html):
        print(f"匹配成功: {html}")
    else:
        print(f"匹配失败: {html}")

# 输出:
# 匹配成功: <h1>valid</h1>
# 匹配失败: <h2>wrong</h3>
# 匹配成功: <h3>also valid</h3>

三、re.sub 的高级替换:调整顺序

re.sub(pattern, repl, string) 不仅可以把匹配到的内容替换成固定字符串,还可以调整内容顺序

repl 参数中,使用 \1, \2 来引用分组。

# 需求:将日期格式从 "2026-01-20" 转换为 "01/20/2026" (月/日/年)

text = "today is 2026-01-20."
# 分组1: 年, 分组2: 月, 分组3: 日
pattern = r"(\d{4})-(\d{2})-(\d{2})"

# 替换为:分组2/分组3/分组1
new_text = re.sub(pattern, r"\2/\3/\1", text)
print(new_text)
# today is 01/20/2026.

四、零宽断言 (lookaround):只看不吃

断言是一种特殊的正则,它只进行判断,不消耗字符(即匹配结果中不包含它)。

4.1 正向先行断言(?=...)

含义:匹配 xxx,但前提是 xxx 后面必须跟着 yyy。
语法xxx(?=yyy)

场景:匹配 “windows 10” 中的 “windows”,但不匹配 “windows xp”。

text = "windows 10 is better than windows xp."

# windows(?= 10) -> 查找 windows,要求后面紧跟 " 10"
# 注意:结果只返回 "windows",不包含 " 10"
matches = re.findall(r"windows(?= 10)", text)
print(matches) 
# ['windows'] (只匹配了第一个)

4.2 正向后行断言(?<=...)

含义:匹配 yyy,但前提是 yyy 前面必须是 xxx。
语法(?<=xxx)yyy

场景:匹配价格数字,但只匹配带 $ 符号的。如 $99 中的 99,不匹配 item 99

text = "price: $99, quantity: 99"

# (?<=\$)\d+ -> 查找数字,要求前面紧跟 "$"
matches = re.findall(r"(?<=\$)\d+", text)
print(matches)
# ['99'] (只匹配了第一个99)

记忆口诀

  • 先行 (=):往后看。
  • 后行 (<=):往前看(箭头向左)。

五、实战练习

练习1:提取超链接 url

从 html 字符串中提取 href="..." 里面的链接地址。

html = '<a href="https://www.google.com" rel="external nofollow"  class="link">google</a>'

# 方法1:使用分组提取
# 匹配 href="任意内容" rel="external nofollow" 
pattern1 = r'href="(.*?)" rel="external nofollow" '
print(re.findall(pattern1, html))
# ['https://www.google.com']

# 方法2:使用断言 (逼格更高,且不需要处理分组)
# 前面是 href=",后面是 " rel="external nofollow" 
pattern2 = r'(?<=href=").*?(?=" rel="external nofollow" )'
print(re.findall(pattern2, html))
# ['https://www.google.com']

练习2:密码强度验证

要求:

  1. 至少 6 位。
  2. 必须包含至少一个数字。
  3. 必须包含至少一个字母。

这是一个经典的断言应用场景(同时满足多个条件)。

def check_password(pwd):
    # ^             : 开头
    # (?=.*\d)      : 往后看,必须包含至少一个数字
    # (?=.*[a-za-z]): 往后看,必须包含至少一个字母
    # .{6,}         : 任意字符至少6位
    # $             : 结尾
    pattern = r"^(?=.*\d)(?=.*[a-za-z]).{6,}$"
    
    return re.match(pattern, pwd) is not none

print(check_password("123456"))   # false (没字母)
print(check_password("abcdef"))   # false (没数字)
print(check_password("abc12"))    # false (太短)
print(check_password("abc1234"))  # true

六、常见问题

q1:非捕获分组(?:...)是什么?

有时候我们需要用括号来整体重复 (ab)+,但又不想让它占一个分组编号(不想被 group(1) 取到)。这时可以用 (?:ab)+
它参与匹配,但不参与提取,效率稍微高一点点。

q2:断言能匹配不定长的内容吗?

python 的 re 模块中,后行断言 (?<=...) 里的内容必须是固定长度的。你不能写 (?<=a*)。但是第三方库 regex 支持不定长。

七、小结

关键要点

  1. 想要提取某部分内容?用 ()
  2. 想要确保前后内容一致?用 \1
  3. 想要匹配"位置"而不是"字符"(比如只要数字不要单位)?用 断言

八、课后作业

  1. markdown 图片链接提取:markdown 图片格式为 ![alt](url)。编写正则,提取出所有的 alt 文字和 url 链接。
  2. 数字千分位:将 “1234567890” 转换为 “1,234,567,890”。提示:使用 re.sub 和零宽断言(匹配"空位置",该位置后面紧跟的数字个数是3的倍数)。
  3. 日志提取:日志格式 [info] 2026-01-20 user login。请提取出:日志级别(info)、日期(2026-01-20) 和 消息(user login)。

总结

到此这篇关于python正则表达式进阶之分组与断言的文章就介绍到这了,更多相关python正则表达式分组与断言内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com