Python正则表达式

7.1 为什么要学习正则表达式

7.1.1 现实中的问题

想象一下这些场景:

  • 从一段文字中找出所有的电话号码
  • 验证用户输入的邮箱格式是否正确
  • 从日志文件中提取错误信息
  • 清理文本中的多余空格和标点

这些任务如果用普通的字符串方法会很麻烦,但用正则表达式就很简单!

7.1.2 正则表达式能做什么

  • 查找:在文本中搜索特定模式的字符串
  • 验证:检查字符串是否符合特定格式
  • 提取:从复杂文本中抽取出需要的信息
  • 替换:将匹配的文本替换为其他内容
  • 分割:按特定模式分割字符串

7.2 初识正则表达式

7.2.1 最简单的匹配:字面匹配

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import re

# 示例1:查找包含"python"的文本
text = "我喜欢学习python编程,python很有趣"
result = re.findall("python", text)
print(result)  # 输出: ['python', 'python']

# 示例2:查找精确匹配
text = "apple banana apple orange"
result = re.findall("apple", text)
print(result)  # 输出: ['apple', 'apple']
小提示
re.findall() 会找到所有匹配的内容,返回一个列表

7.2.2 使用re.search()查找第一个匹配

1
2
3
4
5
6
7
8
import re

# 示例:查找第一个数字
text = "我的电话是123456,你的电话是789012"
match = re.search(r"\d+", text)  # \d代表数字,+代表一个或多个
if match:
    print("找到数字:", match.group())  # 输出: 找到数字: 123456
    print("位置:", match.start(), "到", match.end())  # 输出: 位置: 5 到 11
【课堂练习】7.2.1 基础练习1:查找关键词

任务: 在下面的文本中查找所有"数据"这个词

1
2
text = "数据分析很重要,数据采集是第一步,数据处理需要技巧"
# 你的代码写在这里

要求:

  1. 使用 re.findall() 方法
  2. 输出找到的所有"数据"

预期输出:

1
['数据', '数据', '数据']

7.3 基础元字符

7.3.1 字符类:匹配一组字符

元字符 说明 示例 匹配结果
[abc] 匹配a、b或c [aeiou] 匹配任意元音字母
[0-9] 匹配0到9的数字 [0-9] 匹配单个数字
[a-z] 匹配小写字母 [a-z] 匹配单个小写字母
[A-Z] 匹配大写字母 [A-Z] 匹配单个大写字母
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import re

# 示例1:匹配元音字母
text = "Hello World"
vowels = re.findall(r"[aeiou]", text, re.IGNORECASE)  # 忽略大小写
print("元音字母:", vowels)  # 输出: ['e', 'o', 'o']

# 示例2:匹配数字
text = "房间号是305,价格是299元"
numbers = re.findall(r"[0-9]", text)
print("单个数字:", numbers)  # 输出: ['3', '0', '5', '2', '9', '9']

# 示例3:使用 [0-9]+ 匹配连续数字
numbers = re.findall(r'[0-9]+', text)
print("连续数字:", numbers)  # 输出: ['305', '299']
【课堂练习】字符类匹配练习1:从文本中提取不同类型的信息

任务: 从文本中提取不同类型的信息

1
text = "产品型号:iPhone15 Pro,价格:$999,库存:256台,评分:A+级"

要求:

  1. 提取所有大写字母(使用 [A-Z]
  2. 提取所有小写字母(使用 [a-z]
  3. 提取所有数字(使用 [0-9]
  4. 提取产品名称中的字母(连续的字母字符)

预期输出:

1
2
3
4
5
大写字母: ['I', 'P', 'P', 'A']
小写字母: ['h', 'o', 'n', 'e', 'r', 'o']
单个数字: ['1', '5', '9', '9', '9', '2', '5', '6']
价格数字: ['999']
产品名称字母: ['iPhone', 'Pro']

请编写代码完成以上任务。

7.3.2 预定义字符类

字符类 等价于 说明
\d [0-9] 数字
\D [^0-9] 非数字
\w [a-zA-Z0-9_] 单词字符
\W [^a-zA-Z0-9_] 非单词字符
\s [ \t\n\r\f\v] 空白字符
\S [^ \t\n\r\f\v] 非空白字符
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import re

# 示例1:提取所有数字
text = "苹果5个,香蕉3根,橙子8个"
numbers = re.findall(r"\d", text)
print("数字:", numbers)  # 输出: ['5', '3', '8']

# 示例2:提取所有单词
text = "Hello, 世界! How are you?"
words = re.findall(r"\w+", text)
print("单词:", words)  # 输出: ['Hello', '世界', 'How', 'are', 'you']

# 示例3:找到所有空白位置
text = "Hello   World\nPython"
spaces = re.findall(r"\s", text)
print("空白字符:", [repr(s) for s in spaces])  # 输出: [' ', ' ', ' ', '\n']
【课堂练习】7.3.1 基础练习2:提取信息

任务: 从文本中提取不同类型的信息

1
text = "订单号:A1001,价格:299元,数量:3件,总计:897元"

要求:

  1. 提取所有数字
  2. 提取所有字母
  3. 提取所有中文文字(提示:中文的Unicode范围是 [\u4e00-\u9fff]

预期输出:

1
2
3
数字: ['1', '0', '0', '1', '2', '9', '9', '3', '8', '9', '7']
字母: ['A']
中文: ['订单号', '价格', '元', '数量', '件', '总计', '元']

7.4 重复匹配

7.4.1 量词:控制重复次数

量词 说明 示例 匹配
* 0次或多次 a* “”, “a”, “aa”, “aaa”
+ 1次或多次 a+ “a”, “aa”, “aaa”
? 0次或1次 a? “”, “a”
{n} 恰好n次 a{3} “aaa”
{n,} 至少n次 a{2,} “aa”, “aaa”, “aaaa”
{n,m} n到m次 a{2,4} “aa”, “aaa”, “aaaa”
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import re

# 示例1:匹配连续的数字
text = "价格是299元,折扣后279元,原价320元"
prices = re.findall(r"\d+", text)  # + 表示1个或多个数字
print("价格:", prices)  # 输出: ['299', '279', '320']

# 示例2:匹配特定长度的数字
text = "邮编:100001,电话:13812345678"
# 匹配6位数字(邮编)
postcodes = re.findall(r"\d{6}", text)
print("邮编:", postcodes)  # 输出: ['100001']

# 匹配11位数字(手机号)
phones = re.findall(r"\d{11}", text)
print("手机号:", phones)  # 输出: ['13812345678']

# 示例3:可选匹配
text = "颜色:red,颜色:blue,颜色:"
colors = re.findall(r"颜色:(\w+)?", text)
print("颜色:", colors)  # 输出: ['red', 'blue', '']
【课堂练习】量词匹配练习1

任务: 从文本中提取不同类型的信息,练习使用各种量词

1
2
3
4
5
6
7
8
text = """
用户信息:
用户名:john_doe123,密码:***,年龄:25
手机号:13812345678,邮编:100001
订单:A1001, B202, C30005, D4
评分:★★★★☆ (4.5分)
备注:这个商品很好!!!
"""

要求:

  1. 提取所有用户名中的字母
  2. 提取所有精确3位的数字
  3. 提取所有3位及以上的数字
  4. 提取所有2-4位的数字
  5. 提取评分中的星星数量

预期输出:

1
2
3
4
5
用户名字母: ['john', 'doe']
精确3位数字: ['123', '138']
3位及以上数字: ['123', '13812345678', '100001', '005']
2-4位数字: ['25', '100', '01', '1001', '202', '300', '05', '4.5']
星星数量: ['★★★★']

请编写代码完成以上任务。

7.4.2 贪婪 vs 非贪婪匹配

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import re

# 示例:贪婪匹配 vs 非贪婪匹配
text = "<div>内容1</div><div>内容2</div>"

# 贪婪匹配(默认):尽可能匹配多的字符
greedy = re.findall(r"<div>.*</div>", text)
print("贪婪匹配:", greedy)  # 输出: ['<div>内容1</div><div>内容2</div>']

# 非贪婪匹配:尽可能匹配少的字符
non_greedy = re.findall(r"<div>.*?</div>", text)
print("非贪婪匹配:", non_greedy)  # 输出: ['<div>内容1</div>', '<div>内容2</div>']
【课堂练习】7.4.1 基础练习3:提取多种信息

任务: 从文本中提取不同类型的信息

1
2
3
4
5
6
7
text = """
商品信息:
名称:智能手机,价格:2999元
名称:耳机,价格:399元  
名称:笔记本电脑,价格:5999元
联系电话:138-1234-5678
"""

要求:

  1. 提取所有商品名称(提示:在"名称:“后面的文字)
  2. 提取所有价格数字
  3. 提取电话号码(格式:xxx-xxxx-xxxx)

预期输出:

1
2
3
商品名称: ['智能手机', '耳机', '笔记本电脑']
价格: ['2999', '399', '5999']
电话号码: ['138-1234-5678']

7.5 位置匹配

7.5.1 边界匹配

边界 说明 示例
^ 字符串开始 ^Hello
$ 字符串结束 world$
\b 单词边界 \bword\b
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import re

# 示例1:匹配以特定内容开头的行
text = """苹果很好吃
香蕉也很甜
苹果汁很好喝"""

starts_with_apple = re.findall(r"^苹果", text, re.MULTILINE)  # 多行模式
print("以'苹果'开头:", starts_with_apple)  # 输出: ['苹果']

# 示例2:匹配完整的单词
text = "cat category catfish catch"
# 只匹配完整的cat单词
whole_cat = re.findall(r"\bcat\b", text)
print("完整单词cat:", whole_cat)  # 输出: ['cat']

# 匹配所有包含cat的单词
all_cat = re.findall(r"\b\w*cat\w*\b", text)
print("包含cat的单词:", all_cat)  # 输出: ['cat', 'category', 'catfish', 'catch']

7.6 实际应用示例

7.6.1 提取邮箱地址

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import re

# 简单的邮箱匹配
text = """
联系我们:
客服邮箱:service@company.com
销售邮箱:sales@company.cn
技术支持:support@test.org
无效邮箱:user@, @company.com
"""

# 简单的邮箱匹配模式
email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
emails = re.findall(email_pattern, text, re.IGNORECASE)
print("找到的邮箱:", emails)
# 输出: ['service@company.com', 'sales@company.cn', 'support@test.org']

7.6.2 提取电话号码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import re

# 匹配多种电话号码格式
text = """
联系方式:
手机:138-1234-5678
固话:010-6234-5678
紧急:110
无效:123-456
"""

# 匹配手机号和固话
phone_pattern = r'\b\d{3}-\d{4}-\d{4}\b'
phones = re.findall(phone_pattern, text)
print("电话号码:", phones)  # 输出: ['138-1234-5678', '010-6234-5678']

# 匹配所有3位以上的数字
all_numbers = re.findall(r'\b\d{3,}\b', text)
print("所有长数字:", all_numbers)  # 输出: ['110']

7.6.3 提取URL链接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import re

# 提取网页中的URL
text = """
欢迎访问我们的网站:
主站:https://www.example.com
博客:http://blog.example.com/path/to/article
图片:https://example.com/image.jpg
无效:www.example, http://
"""

# 简单的URL匹配
url_pattern = r'https?://[^\s]+'
urls = re.findall(url_pattern, text)
print("找到的URL:")
for url in urls:
    print("-", url)
【课堂练习】7.6.1 综合练习1:清理数据

任务: 清理和格式化文本数据

1
2
3
4
5
6
text = """
用户输入的数据:
姓名: 张三   ,年龄:25岁,邮箱:zhangsan@email.com
姓名:李四,年龄:30,邮箱:lisi@test.cn
姓名: 王五 ,年龄:abc,邮箱:invalid-email
"""

要求:

  1. 提取所有正确的姓名(去掉前后空格)
  2. 提取所有有效的年龄(只能是数字)
  3. 提取所有有效的邮箱地址
  4. 清理数据,输出结构化的信息

预期输出:

1
2
3
有效的用户数据:
姓名: 张三, 年龄: 25, 邮箱: zhangsan@email.com
姓名: 李四, 年龄: 30, 邮箱: lisi@test.cn

7.7 替换和分割

7.7.1 使用re.sub()进行替换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import re

# 示例1:隐藏敏感信息
text = "我的电话是138-1234-5678,邮箱是user@example.com"

# 隐藏电话号码
hidden_phone = re.sub(r'\d{3}-\d{4}-\d{4}', '***-****-****', text)
print("隐藏电话:", hidden_phone)

# 隐藏邮箱
hidden_email = re.sub(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', '***@***.***', text)
print("隐藏邮箱:", hidden_email)

# 示例2:格式化文本
text = "价格是299元,折扣后279元,原价320元"
# 给所有数字添加人民币符号
formatted = re.sub(r'(\d+)', r'¥\1', text)
print("格式化后:", formatted)  # 输出: 价格是¥299元,折扣后¥279元,原价¥320元

7.7.2 使用re.split()进行分割

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import re

# 示例1:按多种分隔符分割
text = "苹果,香蕉;橙子 梨子,桃子"

# 按逗号、分号、空格分割
fruits = re.split(r'[,; ]+', text)
print("水果列表:", fruits)  # 输出: ['苹果', '香蕉', '橙子', '梨子', '桃子']

# 示例2:复杂的文本分割
text = "姓名:张三 年龄:25 城市:北京"
# 按空格分割,但保留键值对
items = re.split(r'\s+', text)
print("分割结果:", items)  # 输出: ['姓名:张三', '年龄:25', '城市:北京']

# 进一步提取键值
for item in items:
    key_value = re.split(r':', item)
    print(f"{key_value[0]}: {key_value[1]}")

7.8 分组提取

7.8.1 使用括号分组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import re

# 示例1:提取日期各部分
text = "今天是2024-01-15,明天是2024-01-16"

# 使用分组提取年、月、日
dates = re.findall(r'(\d{4})-(\d{2})-(\d{2})', text)
print("日期分组:", dates)  # 输出: [('2024', '01', '15'), ('2024', '01', '16')]

for year, month, day in dates:
    print(f"年: {year}, 月: {month}, 日: {day}")

# 示例2:提取URL的各个部分
text = "网站: https://www.example.com:8080/path/to/page"

# 提取协议、域名、端口、路径
pattern = r'(https?)://([^:/]+)(?::(\d+))?(/.*)?'
match = re.search(pattern, text)

if match:
    protocol = match.group(1)  # https
    domain = match.group(2)    # www.example.com
    port = match.group(3)      # 8080
    path = match.group(4)      # /path/to/page
    
    print(f"协议: {protocol}")
    print(f"域名: {domain}")
    print(f"端口: {port}")
    print(f"路径: {path}")

7.8.2 命名分组(更易读)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import re

# 使用命名分组
text = "订单号:ORD20240115001,金额:299.00元"

pattern = r'订单号:(?P<order_id>\w+),金额:(?P<amount>\d+\.?\d*)元'
match = re.search(pattern, text)

if match:
    order_id = match.group('order_id')
    amount = match.group('amount')
    print(f"订单ID: {order_id}")
    print(f"金额: {amount}")
【课堂练习】7.8.1 综合练习2:解析日志

任务: 从服务器日志中提取信息

1
2
3
4
5
log_lines = [
    '192.168.1.1 - - [15/Jan/2024:10:30:45] "GET /index.html HTTP/1.1" 200 1234',
    '10.0.0.2 - - [15/Jan/2024:10:31:22] "POST /api/login HTTP/1.1" 201 567',
    '172.16.0.5 - - [15/Jan/2024:10:32:15] "GET /static/css/style.css HTTP/1.1" 304 -'
]

要求:

  1. 提取IP地址
  2. 提取时间
  3. 提取请求方法(GET/POST等)
  4. 提取请求路径
  5. 提取状态码

预期输出:

1
2
3
IP: 192.168.1.1, 时间: 15/Jan/2024:10:30:45, 方法: GET, 路径: /index.html, 状态: 200
IP: 10.0.0.2, 时间: 15/Jan/2024:10:31:22, 方法: POST, 路径: /api/login, 状态: 201
IP: 172.16.0.5, 时间: 15/Jan/2024:10:32:15, 方法: GET, 路径: /static/css/style.css, 状态: 304

7.9 与BeautifulSoup结合使用

7.9.1 在BeautifulSoup中使用正则表达式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import re
import requests
from bs4 import BeautifulSoup

# 获取网页内容
url = "https://xuexiqu.cn/python_web_crawler/2.链接和多媒体标签.html"
response = requests.get(url)
response.encoding = "utf-8"
soup = BeautifulSoup(response.text, "lxml")

# 使用正则表达式查找特定链接
# 查找所有包含"jd.com"的链接
jd_links = soup.find_all('a', href=re.compile(r'jd\.com'))
print("京东链接:")
for link in jd_links:
    print(f"- {link.text}: {link['href']}")

# 查找所有图片
images = soup.find_all('img', src=re.compile(r'\.(jpg|png|gif)$', re.IGNORECASE))
print("\n图片:")
for img in images:
    print(f"- {img.get('alt', '无描述')}: {img['src']}")

7.10 本章总结

7.10.1 重点回顾

  1. 基础匹配:字面匹配是最简单的开始
  2. 字符类[abc], \d, \w, \s 等用于匹配特定类型的字符
  3. 量词*, +, ?, {n} 控制重复次数
  4. 位置匹配^, $, \b 匹配特定位置
  5. 分组() 用于提取和分组内容

7.10.2 实用技巧

  • 从简单模式开始,逐步复杂化
  • 使用 re.findall() 快速测试模式
  • re.search() 查找第一个匹配
  • re.sub() 进行替换操作
  • 用分组 () 提取需要的信息

7.10.3 学习建议

  1. 多练习:正则表达式需要大量练习才能掌握
  2. 从小处着手:从简单的匹配开始,逐步增加复杂度
  3. 使用工具:在线正则表达式测试工具可以帮助调试
  4. 结合实际:在实际项目中应用学到的知识

记住:正则表达式是一个强大的工具,虽然开始可能觉得复杂,但随着练习会变得越来越容易!

0%