'` | `//div` |
| 匹配 class="title" 的元素 | `r'class="title"'` | `//*[@class="title"]` |
| 匹配包含 /subject/ 的链接 | `r'href="/subject/[^"]+"'` | `//a[contains(@href,"/subject/")]` |
| 提取标签文本内容 | `r'>([^<]+)'` → group(1) | `//span/text()` |
| 匹配任意字符 | `.+?`(非贪婪) | `//text()[contains(.,'关键词')]` |
| 匹配数字 | `\d+` | `//span[number(text())]` |
| 匹配开头/结尾 | `^内容`、`内容$` | `//div[starts-with(@class,"title")]` |
---
# 9. 常见问题与解决方案
## 9.1 匹配不到内容
```python
import re
text = 'Hello\nWorld'
# 问题:. 不匹配换行符
print(re.findall(r'.+', text)) # ['Hello', 'World'] - 被拆成了两行?
# 解决1:用 re.DOTALL 模式(让 . 匹配换行)
print(re.findall(r'.+', text, re.DOTALL)) # ['Hello\nWorld']
# 解决2:用 [\s\S] 代替 .
print(re.findall(r'[\s\S]+', text)) # ['Hello\nWorld']
```
## 9.2 中文匹配
```python
import re
text = 'Python是一门很棒的语言,C语言也是'
# 匹配中文
chinese = re.findall(r'[\u4e00-\u9fff]+', text)
print(chinese) # ['很棒的语言', '也是']
# 或者直接用 unicode
pattern = r'[\u4e00-\u9fff]+'
print(re.findall(pattern, text))
```
## 9.3 大小写不敏感匹配
```python
import re
text = 'Python PYTHON pYtHoN'
# 默认区分大小写
print(re.findall(r'python', text)) # []
# 用 re.IGNORECASE(可简写为 re.I)
print(re.findall(r'python', text, re.I)) # ['Python', 'PYTHON', 'pYtHoN']
```
## 9.4 多行模式
```python
import re
text = '''第一行内容
第二行内容
第三行内容'''
# 问题:^ 只匹配字符串开头
print(re.findall(r'^第.+', text)) # ['第一行内容']
# 解决:用 re.MULTILINE(可简写为 re.M)
print(re.findall(r'^第.+', text, re.M)) # ['第一行内容', '第二行内容', '第三行内容']
```
## 9.5 编译正则表达式提高性能
如果一个正则表达式要匹配多次,预先编译可以提高性能:
```python
import re
import time
# 需要匹配 10000 次的文本
text = '订单号:20240315-001,总额:999.50元'
# 方法1:每次调用都编译
start = time.time()
for _ in range(10000):
re.findall(r'\d{4}-\d+', text)
print(f'未编译: {time.time() - start:.4f}秒')
# 方法2:预先编译
pattern = re.compile(r'\d{4}-\d+')
start = time.time()
for _ in range(10000):
pattern.findall(text)
print(f'已编译: {time.time() - start:.4f}秒')
```
---
# 10. 动手练习
## 练习 1:提取天气预报
**目标**:从以下文本中提取日期、天气和温度。
```python
text = '''
2024-03-15 天气:晴 温度:15-25°C
2024-03-16 天气:多云 温度:12-20°C
2024-03-17 天气:小雨 温度:10-18°C
'''
# 提示:使用分组捕获日期、天气、温度
# pattern = r'你的正则表达式'
import re
pattern = r'(\d{4}-\d{2}-\d{2})\s*天气:([^ ]+)\s*温度:(\d+)-(\d+)°C'
matches = re.findall(pattern, text)
for match in matches:
date, weather, low, high = match
print(f'{date}: {weather}, {low}°C-{high}°C')
```
**预期输出:**
```
2024-03-15: 晴, 15°C-25°C
2024-03-16: 多云, 12°C-20°C
2024-03-17: 小雨, 10°C-18°C
```
---
## 练习 2:爬取豆瓣电影信息
**目标**:编写正则表达式,从模拟的 HTML 中提取电影信息。
```python
import re
html = '''
《流浪地球》
(2024)
8.5
导演:郭帆
《你好,李焕英》
(2024)
7.9
导演:贾玲
'''
# 编写正则表达式,提取所有电影信息
# pattern = r'你的正则表达式'
# 提示:可以用多个正则分别提取,或者用一个复杂的正则提取所有
name_pattern = r'
《([^》]+)》
'
year_pattern = r'
\((\d{4})\)'
rating_pattern = r'
([^<]+)'
director_pattern = r'导演:([^<]+)'
names = re.findall(name_pattern, html)
years = re.findall(year_pattern, html)
ratings = re.findall(rating_pattern, html)
directors = re.findall(director_pattern, html)
for i in range(len(names)):
print(f"{names[i]} | {years[i]} | 评分:{ratings[i]} | {directors[i]}")
```
---
## 练习 3:日志分析
**目标**:从服务器日志中提取 IP 地址、请求时间和状态码。
```python
import re
log = '''
192.168.1.100 - - [15/Mar/2024:10:15:30 +0800] "GET /index.html HTTP/1.1" 200 1234
10.0.0.50 - - [15/Mar/2024:10:15:31 +0800] "POST /api/login HTTP/1.1" 200 256
192.168.1.101 - - [15/Mar/2024:10:15:32 +0800] "GET /notfound.html HTTP/1.1" 404 512
172.16.0.200 - - [15/Mar/2024:10:15:33 +0800] "GET /images/logo.png HTTP/1.1" 200 4096
'''
# 提取 IP、时间和状态码
pattern = r'(\d+\.\d+\.\d+\.\d+).*?\[([^\]]+)\].*?" (\d{3}) \d+'
for match in re.finditer(pattern, log):
ip, time, status = match.groups()
print(f'IP: {ip:15} | 时间: {time:25} | 状态: {status}')
```
---
## 练习 4:电话号码脱敏
**目标**:将手机号中间四位用 `*` 替换,保护隐私。
```python
import re
phone_book = '''
张三:138-1234-5678
李四:139-5678-1234
王五:138-0000-1111
'''
# 脱敏:将 138-****-5678 格式输出
# 提示:使用分组和 re.sub
pattern = r'(\d{3})-(\d{4})-(\d{4})'
def mask_phone(match):
return f'{match.group(1)}-****-{match.group(3)}'
masked = re.sub(pattern, mask_phone, phone_book)
print(masked)
```
---
## 练习 5:综合挑战(选做)
从以下课程表 HTML 中,用正则表达式提取所有课程信息:
```python
import re
html = '''
| 时间 | 课程 | 教室 |
| 周一 1-2节 | Python程序设计 | A101 |
| 周一 3-4节 | 数据结构 | B205 |
| 周二 1-2节 | 高等数学 | C301 |
| 周三 5-6节 | Python程序设计 | A102 |
'''
# 分别提取时间、课程、教室
time_pattern = r'
([^<]+) | ([^<]+) | ([^<]+) | '
courses = re.findall(time_pattern, html)
print('课程表:')
for time, course, room in courses:
print(f'{time} | {course} | {room}')
```
---
# 📋 课程小结
## 核心要点
1. **正则表达式是强大的文本匹配工具**,可以精确提取任意文本中的目标内容
2. **元字符是基础**:
- `.` 匹配任意字符
- `\d` 匹配数字,`\w` 匹配字母数字下划线
- `[]` 定义字符集,`[^...]` 排除字符集
3. **量词控制次数**:
- `+` 一次或多次,`*` 零次或多次,`?` 零次或一次
- `{n,m}` 指定范围
4. **贪婪 vs 非贪婪**:
- 贪婪(`.*`、`\d+`)尽可能多匹配
- 非贪婪(`.*?`、`\d+?`)尽可能少匹配
- `[^<]+` 是爬虫中常用的非贪婪技巧
5. **分组 `()`**:
- 捕获匹配内容
- 用 `\1`、`\2` 在替换中引用分组
- `(?P
...)` 命名分组更清晰
6. **BeautifulSoup + 正则 = 黄金组合**:
- BeautifulSoup 负责结构定位
- 正则负责精确提取
7. **re 模块常用函数**:
- `findall()` 返回所有匹配列表
- `search()` 找第一个匹配
- `match()` 只匹配字符串开头
- `finditer()` 返回迭代器,节省内存
- `sub()` 替换匹配内容
## 常见错误速查
| 错误 | 原因 | 解决方法 |
|------|------|----------|
| 匹配结果为空 | `.` 不匹配换行符 | 用 `re.DOTALL` 或 `[^\n]` |
| 匹配太多 | 贪婪匹配 | 改为非贪婪 `*?`、`+?` |
| 匹配错误 | 元字符未转义 | 用 `\.` 匹配点号本身 |
| 中文匹配失败 | 字符集不对 | 用 `[\u4e00-\u9fff]` |
## 下节课预告
- XPath 选择器详解
- Selenium 动态页面抓取
- 反反爬策略与实战
---
*本讲义由 AI 助教生成,如有问题请随时提问。*