Python: 正则表达式

正则表达式(Regular Expression,简称 regex)是一种描述文本模式的特殊语言。用一行正则表达式,可以完成几十行手写代码才能做到的文本匹配和提取工作。它在数据清洗、日志分析、表单验证中无处不在。


1. 什么是正则表达式

正则表达式是用特殊符号描述"我想找什么样的字符串"。

PYTHON
import re

# 最简单的正则:直接匹配字符串
pattern = r"hello"
text = "hello world"

result = re.search(pattern, text)
if result:
    print("找到了!")               # 找到了!
    print(result.group())           # hello
    print(result.start())           # 0(起始位置)
    print(result.end())             # 5(结束位置)
⚠️ 写正则时在字符串前面加 r(原始字符串)。 r"\n" 是反斜杠+字母 n,不是换行符。正则里大量用到反斜杠,不加 r 会很痛苦。



2. 常用方法

PYTHON
import re

text = "我的邮箱是 zhangsan@example.com,请发邮件。还有 lisi@test.com"

# search() —— 查找第一个匹配
result = re.search(r"\w+@\w+\.\w+", text)
print(result.group())               # zhangsan@example.com

# findall() —— 查找所有匹配
emails = re.findall(r"\w+@\w+\.\w+", text)
print(emails)                       # ['zhangsan@example.com', 'lisi@test.com']

# match() —— 从开头匹配
print(re.match(r"我的", text))       # 匹配(在开头)
print(re.match(r"邮箱", text))       # None(不在开头)

# sub() —— 替换
masked = re.sub(r"\w+@", "***@", text)
print(masked)                       # 我的邮箱是 ***@example.com,请发邮件。还有 ***@test.com


3. 元字符速查表

元字符 含义 示例 匹配
. 任意单个字符(除换行) h.t hat, hot, hit
\d 数字 \d{3} 123, 456
\w 字母/数字/下划线 \w+ hello, abc123
\s 空白字符 \s 空格、Tab、换行
* 前一个字符 0 次或多次 ab*c ac, abc, abbc
+ 前一个字符 1 次或多次 ab+c abc, abbc(不含 ac)
? 前一个字符 0 次或 1 次 colou?r color, colour
{n} 重复 n 次 \d{4} 2026, 1990
{n,m} 重复 n 到 m 次 \d{2,4} 23, 456, 2026
^ 字符串开头 ^Hello Hello...
$ 字符串结尾 end$ ...end
[] 字符集合 [aeiou] 任意元音字母
` ` `cat

▶ 示例:电话号验证(难度⭐⭐)

PYTHON
import re

def is_valid_phone(phone):
    """验证中国大陆手机号(11位,1开头)"""
    pattern = r"^1[3-9]\d{9}$"
    return bool(re.match(pattern, phone))

phones = ["13800138000", "12345678901", "010-12345678", "1380013800a"]
for p in phones:
    print(f"{p}: {'✅' if is_valid_phone(p) else '❌'}")

# 提取文本中的所有手机号
text = "联系电话:13800138000,备用:13912345678,座机:010-12345678"
phones = re.findall(r"1[3-9]\d{9}", text)
print(f"找到手机号:{phones}")
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功


4. 分组捕获

用括号 () 可以把匹配到的内容分成多个部分提取:

PYTHON
import re

# 提取邮箱的用户名和域名
email = "zhangsan@example.com"
pattern = r"(\w+)@(\w+\.\w+)"
result = re.search(pattern, email)

if result:
    print(f"完整匹配:{result.group(0)}")    # zhangsan@example.com
    print(f"用户名:{result.group(1)}")      # zhangsan
    print(f"域名:{result.group(2)}")        # example.com

# 从日志中提取信息
log = "2026-06-23 15:30:45 ERROR 数据库连接超时"
pattern = r"(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+) (.+)"
result = re.search(pattern, log)
print(f"日期:{result.group(1)}")           # 2026-06-23
print(f"时间:{result.group(2)}")           # 15:30:45
print(f"级别:{result.group(3)}")           # ERROR
print(f"消息:{result.group(4)}")           # 数据库连接超时

▶ 示例:URL 解析器(难度⭐⭐⭐)

PYTHON
import re

def parse_url(url):
    """解析 URL,提取协议、域名、路径、查询参数"""
    pattern = r"(https?)://([^/]+)(/[^?]*)?(?:\?([^#]*))?"
    result = re.search(pattern, url)
    if not result:
        return None
    
    return {
        "protocol": result.group(1),
        "domain": result.group(2),
        "path": result.group(3) or "/",
        "query": result.group(4) or ""
    }

urls = [
    "https://www.example.com/path/page.html?id=123&name=test",
    "http://localhost:8080/api/users",
    "https://google.com"
]

for url in urls:
    info = parse_url(url)
    if info:
        print(f"\nURL: {url}")
        for key, value in info.items():
            print(f"  {key}: {value}")
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功

▶ 示例:文本替换与脱敏(难度⭐⭐)

PYTHON
import re

text = "用户张三的手机号是13800138000,邮箱是zhangsan@example.com,身份证号110101199001011234"

phone_masked = re.sub(r"(1[3-9]\d)\d{4}(\d{4})", r"\1****\2", text)
print(f"手机脱敏:{phone_masked}")

email_masked = re.sub(r"(\w{2})\w+(@\w+\.\w+)", r"\1***\2", text)
print(f"邮箱脱敏:{email_masked}")

id_masked = re.sub(r"(\d{4})\d{10}(\d{4})", r"\1**********\2", text)
print(f"身份证脱敏:{id_masked}")

template = "你好,{name}!订单{order_id}已发货。"
data = {"name": "Alice", "order_id": "ORD2024001"}
result = re.sub(r"\{(\w+)\}", lambda m: data.get(m.group(1), m.group(0)), template)
print(f"模板替换:{result}")
▶ 试一试

输出:

TEXT 📖 仅展示
手机脱敏:用户张三的手机号是138****8000,邮箱是zhangsan@example.com,身份证号110101199001011234
邮箱脱敏:用户张三的手机号是13800138000,邮箱是zh***@example.com,身份证号110101199001011234
身份证脱敏:用户张三的手机号是13800138000,邮箱是zhangsan@example.com,身份证号1101**********1234
模板替换:你好,Alice!订单ORD2024001已发货。


5. 贪婪与懒惰

默认情况下,*+贪婪的——尽可能多匹配。加 ? 变成懒惰——尽可能少匹配:

PYTHON
import re

text = "<h1>标题</h1><p>段落</p>"

# 贪婪模式——匹配尽可能多的内容
greedy = re.search(r"<.+>", text)
print(greedy.group())               # <h1>标题</h1><p>段落</p>

# 懒惰模式——匹配尽可能少的内容(加 ?)
lazy = re.search(r"<.+?>", text)
print(lazy.group())                 # <h1>
💡 .*?(懒惰)vs .*(贪婪): 处理 HTML、JSON 等有嵌套结构的内容时,懒惰模式更常用。贪婪模式经常"越界"匹配到不该匹配的内容。如果不确定用什么,先用懒惰。



6. 常见应用场景


❓ 常见问题

Q 正则表达式中的 r 前缀是什么意思?
A r 表示原始字符串(raw string)。在普通字符串中 \n 是换行,在原始字符串中 \n 就是反斜杠+字母n。正则表达式大量使用反斜杠(如 \d\w),不加 r 就要写 \\d,非常痛苦。
Q 正则表达式性能怎么样?
A 大多数情况足够快。但如果写得很差(如 (.*)* 这种嵌套量词),可能导致"灾难性回溯"——处理一段简单文本需要几十分钟。解决方案:避免嵌套量词、用懒惰模式、用 re.compile() 预编译重复使用的正则。
Q 复杂的正则表达式很难读懂,有什么好办法?
A ① 加注释——re.VERBOSE 模式允许正则有空格和注释。② 拆分成多个简单的正则。③ 用在线工具调试(如 regex101.com)。正则表达式是"写起来爽,读起来哭"的典型——不要追求"一行正则解决所有问题"。

📖 小节


📝 作业

  1. 基础题(难度⭐):写一个函数 is_valid_email(email),用正则验证邮箱格式(含 @@ 前后非空,有域名后缀)。

  2. 进阶题(难度⭐⭐):写一个函数 extract_numbers(text),从字符串中提取所有数字(包括整数和小数),返回它们的和。例如 "价格是 19.99 元,运费 5 元,优惠券 -10 元" → 返回 14.99。

  3. 挑战题(难度⭐⭐⭐):写一个"简单模板引擎"。给定模板 "你好,{name}!你的订单 {order_id} 已发货,预计 {days} 天内到达。" 和一个字典 {"name": "Alice", "order_id": "2024001", "days": 3},用正则替换所有 {变量名} 为对应的值。提示:用 re.sub() 结合回调函数。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏