从零到接单 07:正则表达式入门——文本处理的瑞士军刀
系列目录:本文是「从零到接单:Python 自动化与爬虫实战」系列的第 7 篇。正则表达式是文本处理的终极利器——爬虫从 HTML 中提取信息、清洗脏数据、验证用户输入、解析日志……都离不开它。虽然语法看起来有点"天书",但掌握核心规则后,你会发现它无比强大。
一、正则表达式是什么?
正则表达式(Regular Expression,简称 regex)是一套模式匹配语言,用来在一段文本中查找、提取、替换符合特定规则的字符串。
举个例子:
- 从一堆文字中找所有手机号 →
1[3-9]\d{9} - 从 HTML 中提取所有链接 →
href="(.*?)" - 验证邮箱格式 →
[\w.-]+@[\w.-]+\.\w+
看起来像乱码?一步步来。
二、Python re 模块基础
import re
Python 中正则表达式通过 re 模块使用,四种最常用的方法:
| 方法 | 用途 | 返回值 |
|------|------|--------|
| re.search() | 搜索第一个匹配 | Match 对象或 None |
| re.findall() | 查找所有匹配 | 列表 |
| re.sub() | 替换所有匹配 | 新字符串 |
| re.match() | 从开头匹配 | Match 对象或 None |
import re
text = "我的手机号是 13812345678,备用号是 15987654321"
# search:找第一个
result = re.search(r"1[3-9]\d{9}", text)
if result:
print(result.group()) # 13812345678
# findall:找所有
phones = re.findall(r"1[3-9]\d{9}", text)
print(phones) # ['13812345678', '15987654321']
# sub:替换
masked = re.sub(r"\d{4}$", "****", "13812345678")
print(masked) # 1381234****
# match:从开头匹配
print(re.match(r"你好", "你好,世界!")) # <re.Match object>
print(re.match(r"世界", "你好,世界!")) # None(不在开头)
💡 建议始终用 原始字符串
r"...",避免\d被 Python 当成转义字符。
三、正则核心语法
1. 基本字符匹配
# 普通字符直接匹配
re.findall(r"cat", "cat dog cat bird") # ['cat', 'cat']
# 点号 . 匹配任意单个字符(除换行)
re.findall(r"c.t", "cat cot c@t cut") # ['cat', 'cot', 'c@t', 'cut']
# 转义特殊字符
# 特殊字符:. ^ $ * + ? { } [ ] \ | ( )
re.findall(r"\$", "价格:$99.99") # ['$']
re.findall(r"\d+", "价格:99.99") # ['99', '99']
2. 字符类 [ ]
# [abc] 匹配 a 或 b 或 c
re.findall(r"[aeiou]", "hello world") # ['e', 'o', 'o']
# [a-z] 匹配 a 到 z 的小写字母
re.findall(r"[a-z]", "Hello World") # ['e', 'l', 'l', 'o', 'o', 'r', 'l', 'd']
# [0-9] 匹配数字
re.findall(r"[0-9]", "电话:010-12345678") # ['0', '1', '0', '1', '2', ...]
# [^abc] 匹配不是 a、b、c 的字符(取反)
re.findall(r"[^aeiou]", "hello") # ['h', 'l', 'l']
3. 预定义字符类(最常用)
| 符号 | 含义 | 等价于 |
|------|------|--------|
| \d | 数字 | [0-9] |
| \D | 非数字 | [^0-9] |
| \w | 字母/数字/下划线 | [a-zA-Z0-9_] |
| \W | 非字母数字下划线 | [^a-zA-Z0-9_] |
| \s | 空白字符(空格、制表、换行) | [ \t\n\r\f] |
| \S | 非空白 | [^ \t\n\r\f] |
text = "价格:¥99.99,数量:5件"
# 提取所有数字
print(re.findall(r"\d+", text)) # ['99', '99', '5']
# 提取所有中文字符
print(re.findall(r"[\u4e00-\u9fff]+", text)) # ['价格', '数量', '件']
4. 量词:指定重复次数
| 符号 | 含义 |
|------|------|
| * | 0 次或多次 |
| + | 1 次或多次 |
| ? | 0 次或 1 次 |
| {n} | 恰好 n 次 |
| {n,} | 至少 n 次 |
| {n,m} | n 到 m 次 |
# + 一次或多次
re.findall(r"\d+", "abc 123 4567") # ['123', '4567']
# * 零次或多次
re.findall(r"go*", "go g goo g") # ['go', 'g', 'goo', 'g']
# ? 零次或一次(可选)
re.findall(r"colou?r", "color colour") # ['color', 'colour']
# {n} 精确 n 次
re.findall(r"\d{3}", "12 345 6789") # ['345', '678']
# {n,m} n到m次
re.findall(r"\d{2,4}", "1 12 123 1234 12345") # ['12', '123', '1234', '1234']
5. 贪婪 vs 非贪婪(爬虫重中之重!)
html = "<div>第一个</div><div>第二个</div>"
# 贪婪匹配(默认):尽可能多
print(re.findall(r"<div>.*</div>", html))
# ['<div>第一个</div><div>第二个</div>'] ← 一次匹配了整个!
# 非贪婪匹配(加 ?):尽可能少
print(re.findall(r"<div>.*?</div>", html))
# ['<div>第一个</div>', '<div>第二个</div>'] ← 正确!
🔥 这是爬虫最重要的正则技巧! 在 HTML 中提取内容,几乎永远用
.*?(非贪婪)。
6. 锚点:匹配位置
| 符号 | 含义 |
|------|------|
| ^ | 字符串开头 |
| $ | 字符串结尾 |
| \b | 单词边界 |
# ^ 开头
print(re.findall(r"^\d+", "123abc456")) # ['123']
# $ 结尾
print(re.findall(r"\d+$", "123abc456")) # ['456']
# \b 单词边界
print(re.findall(r"\bcat\b", "cat category concat"))
# ['cat'] ← 只匹配独立的 cat
7. 分组与捕获
text = "姓名:张三,年龄:25,城市:北京"
# 用分组提取
result = re.search(r"姓名:(.+?),年龄:(\d+),城市:(.+)", text)
if result:
print(result.group(0)) # 完整匹配:姓名:张三,年龄:25,城市:北京
print(result.group(1)) # 第一个分组:张三
print(result.group(2)) # 第二个分组:25
print(result.group(3)) # 第三个分组:北京
print(result.groups()) # 所有分组:('张三', '25', '北京')
# 命名分组(更清晰)
result = re.search(r"姓名:(?P<name>.+?),年龄:(?P<age>\d+),城市:(?P<city>.+)", text)
if result:
print(result.group("name")) # 张三
print(result.group("age")) # 25
print(result.groupdict()) # {'name': '张三', 'age': '25', 'city': '北京'}
四、常用正则表达式速查
# 手机号
phone_pattern = r"1[3-9]\d{9}"
# 邮箱
email_pattern = r"[\w.-]+@[\w.-]+\.\w+"
# 网址(URL)
url_pattern = r"https?://[^\s]+"
# 身份证号(18位)
id_pattern = r"\d{17}[\dXx]"
# IP 地址
ip_pattern = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"
# 中文
chinese_pattern = r"[\u4e00-\u9fff]+"
# 日期 YYYY-MM-DD
date_pattern = r"\d{4}-\d{2}-\d{2}"
# HTML 标签中的内容
html_content_pattern = r">([^<]+)<"
# HTML 属性值
attr_pattern = r'(\w+)="([^"]*)"'
五、实战练习
练习 1:提取网页中所有链接
import re
html = '''
<html>
<body>
<a href="https://example.com/page1">链接1</a>
<a href="https://example.com/page2">链接2</a>
<a href="/about">关于我们</a>
<img src="/images/logo.png" alt="Logo">
</body>
</html>
'''
# 提取所有 a 标签的 href 和文本
links = re.findall(r'<a\s+href="([^"]*)"[^>]*>(.*?)</a>', html)
for href, text in links:
print(f"链接:{href},文字:{text.strip()}")
练习 2:清洗文本中的特殊字符
import re
def clean_text(text):
"""清洗文本:去掉HTML标签、多余空白、特殊字符"""
# 去掉 HTML 标签
text = re.sub(r"<[^>]+>", "", text)
# 去掉多余空白
text = re.sub(r"\s+", " ", text)
# 去掉首尾空格
text = text.strip()
return text
dirty = "<p> 这是一段 <b>脏</b> 数据 </p>"
print(clean_text(dirty)) # 这是一段 脏 数据
练习 3:解析日志文件
import re
log = """
[2026-07-12 10:15:30] [INFO] 爬虫启动,目标URL:https://example.com
[2026-07-12 10:15:32] [INFO] 正在抓取第1页...
[2026-07-12 10:15:35] [WARNING] 响应速度慢:2.5s
[2026-07-12 10:15:40] [ERROR] 第3页抓取失败:Connection timeout
[2026-07-12 10:15:45] [INFO] 爬虫结束,共抓取200条数据
"""
pattern = r"\[([\d\- :]+)\] \[(\w+)\] (.+)"
matches = re.findall(pattern, log)
# 统计各级别日志数量
from collections import Counter
levels = Counter(m[1] for m in matches)
print(levels) # Counter({'INFO': 3, 'WARNING': 1, 'ERROR': 1})
# 找出所有 ERROR
for time, level, msg in matches:
if level == "ERROR":
print(f"[{time}] {msg}")
六、正则调试技巧
正则写错了怎么调?两种方法:
- 在线工具:regex101.com,粘贴文本和正则,实时看匹配结果
- Python 内调试:
import re
def debug_regex(pattern, text):
"""辅助调试正则"""
matches = re.finditer(pattern, text)
found = False
for m in matches:
found = True
print(f"匹配: '{m.group()}' | 位置: {m.start()}-{m.end()}")
for i, g in enumerate(m.groups(), 1):
print(f" 分组{i}: '{g}'")
if not found:
print("没有找到匹配!")
debug_regex(r"\d{3,4}", "电话:010-12345678")
总结
| 模式 | 含义 | 示例 |
|------|------|------|
| . | 任意字符 | c.t → cat, cot |
| \d | 数字 | \d+ → 123, 4567 |
| \w | 字母数字下划线 | \w+ → hello, name_1 |
| \s | 空白 | \s+ → 空格, 换行 |
| * | 0次或多次 | ab* → a, ab, abb |
| + | 1次或多次 | ab+ → ab, abb |
| ? | 非贪婪 | .*? 爬虫必用 |
| [] | 字符类 | [aeiou] → 元音 |
| () | 分组 | (\d+) 捕获数字 |
| ^$ | 开头/结尾 | ^Hello 以 Hello 开头 |
| (?P<name>) | 命名分组 | (?P<year>\d{4}) |
正则表达式是爬虫的"眼睛"——帮你看清网页里有什么,并把有用的信息摘出来。下一篇,我们将正式进入爬虫世界,学习 HTTP 协议与 requests 库!
练习:
- 写一个函数
extract_emails(text),从一段文本中提取所有邮箱地址- 写一个函数
extract_phone_numbers(text),提取所有中国大陆手机号- 用这两个函数处理一段包含联系方式的模拟文本
Comments
Sign in to leave a comment.