系列文章

Python 从入门到精通

第 20 / 36 篇

从环境安装和基础语法出发,逐步学习工程实践、自动化、数据处理与 Web 开发。

  1. 01
    Python 怎么装、怎么用
  2. 02
    变量、数字和字符串
  3. 03
    列表:把一组数据放在一起
  4. 04
    元组、集合和字典
  5. 05
    条件判断:让程序做选择
  6. 06
    循环:重复的事交给程序
  7. 07
    函数:把代码整理成可复用的块
  8. 08
    模块与包:拆分你的程序
  9. 09
    输入、输出与字符串格式化
  10. 10
    文件读写:保存程序的数据
  11. 11
    异常处理:程序出错时怎么办
  12. 12
    基础阶段练习:命令行记账本
  13. 13
    类和对象:面向对象入门
  14. 14
    继承、组合与特殊方法
  15. 15
    迭代器与生成器
  16. 16
    列表推导式与生成器表达式
  17. 17
    装饰器:给函数增加能力
  18. 18
    上下文管理器与 with
  19. 19
    类型标注与 dataclass
  20. 20
    正则表达式:从文本中找规律正在阅读
  21. 21
    日期、时间与时区
  22. 22
    日志与调试
  23. 23
    虚拟环境与依赖管理
  24. 24
    测试:让修改不再提心吊胆
  25. 25
    网络请求:用 Python 调用 API
  26. 26
    网页解析与合规采集
  27. 27
    操作 Excel、CSV 与批量文件
  28. 28
    SQLite:给程序加一个数据库
  29. 29
    数据分析入门:NumPy 与 Pandas
  30. 30
    画图:把数据变得直观
  31. 31
    Flask 入门:做一个小网站
  32. 32
    异步编程:同时处理多项任务
  33. 33
    线程、进程与并发选择
  34. 34
    性能分析与优化
  35. 35
    项目结构、配置与发布
  36. 36
    综合项目:从需求到上线

查看整个系列 →

处理文本时,最头疼的需求往往是”找规律”:从一堆日志里找出所有 IP、从网页源码里提取链接、验证手机号格式对不对。用字符串方法硬抠会写到怀疑人生。正则表达式(regex)就是干这个的——用一套”模式”语言描述你要找的规律。

第一个正则

Python 的正则在 re 模块里。最简单的用法:re.findall 找出所有匹配:

import re

text = "我的电话是 138-1234-5678,办公室是 010-8888-6666"
phones = re.findall(r"d{3}-d{4}-d{4}", text)
print(phones)   # ['138-1234-5678', '010-8888-6666']

注意字符串前面的 r:原始字符串(raw string),告诉 Python”反斜杠别转义”,写正则必须加。

核心语法速查

记住这张表,覆盖 80% 场景:

# 字符类
d     # 数字,等价 [0-9]
w     # 单词字符:字母、数字、下划线
s     # 空白:空格、tab、换行
.      # 任意字符(换行除外)
[abc]  # a、b、c 中任意一个
[0-9]  # 数字区间
[^0-9] # 非数字

# 量词
*      # 0 次或多次
+      # 1 次或多次
?      # 0 次或 1 次
{3}    # 恰好 3 次
{2,4}  # 2 到 4 次

# 位置和分组
^      # 字符串开头
$      # 字符串结尾
|      # 或(如 cat|dog)
()     # 分组,同时能捕获内容
b     # 单词边界
import re

text = "南山小站 2026 年开张,有 12 篇文章,3 个系列"

# d+:连续数字
print(re.findall(r"d+", text))        # ['2026', '12', '3']

# d{4}:恰好 4 位数字
print(re.findall(r"d{4}", text))      # ['2026']

# [南小]:任意一个字符
print(re.findall(r"[南小]", text))     # ['南', '小']

# w+:连续单词
print(re.findall(r"w+", text))        # ['南山小站', '2026', ...]

search vs match vs findall

三个最常用的函数,别搞混:

text = "我的邮箱是 hello@example.com,备用是 hi@test.org"

# findall:找所有,返回列表
emails = re.findall(r"[w.]+@[w.]+.w+", text)
print(emails)   # ['hello@example.com', 'hi@test.org']

# search:找第一个,返回 Match 对象(找不到返回 None)
m = re.search(r"[w.]+@[w.]+.w+", text)
print(m.group())      # hello@example.com(匹配到的内容)

# match:必须从开头匹配(注意区别!)
m2 = re.match(r"d+", "2026年是南山小站开张年")
print(m2.group())     # 2026(开头就有数字)
m3 = re.match(r"d+", "南山小站2026年开张")
print(m3)             # None(开头不是数字,匹配失败)

记法:findall 找全部,search 找第一个(任意位置),match 从开头找

分组:提取关键部分

用括号分组,能只取需要的部分:

text = "张三 138-1234-5678,李四 139-9999-8888"
pattern = r"(w+) (d{3}-d{4}-d{4})"   # 两个分组:名字、电话

for m in re.finditer(pattern, text):     # finditer:逐个 Match
    name, phone = m.groups()             # 取所有分组
    print(f"{name}: {phone}")
# 张三: 138-1234-5678
# 李四: 139-9999-8888
# 分组加名字(?P<名字>),大项目可读性好
pattern = r"(?P<name>w+) (?P<phone>d{3}-d{4}-d{4})"
m = re.search(pattern, text)
print(m.group("name"))    # 张三
print(m.group("phone"))   # 138-1234-5678

sub:替换

text = "联系我:138-1234-5678"

# 手机号打码
masked = re.sub(r"d{3}-d{4}-d{4}", "***-****-****", text)
print(masked)   # 联系我:***-****-****

# 去掉所有非数字字符
clean = re.sub(r"D", "", text)     # D 非数字
print(clean)    # 13812345678

贪婪 vs 非贪婪

默认量词是贪婪的:尽可能多匹配。加 ? 变非贪婪:

text = "<b>加粗</b>和<b>更粗</b>"

# 贪婪:从第一个 
print(re.findall(r"<b>.*</b>", text))
# ['<b>加粗</b>和<b>更粗</b>']  —— 一次吃到底!

# 非贪婪:.*? 尽量少匹配
print(re.findall(r"<b>.*?</b>", text))
# ['<b>加粗</b>', '<b>更粗</b>']   —— 这才是想要的

.*? 是最常用的非贪婪组合,提取成对标签时几乎必用。

实战:验证输入 + 提取 URL

import re

# 验证手机号(简单版:1 开头 11 位)
def is_phone(s):
    return bool(re.fullmatch(r"1d{10}", s))   # fullmatch:整个字符串都要匹配

print(is_phone("13812345678"))   # True
print(is_phone("23812345678"))   # False(2 开头)
print(is_phone("1381234567"))    # False(10 位)

# 提取所有 URL
text = "官网 https://www.nsn0704y.cn 和文档 http://docs.example.com"
urls = re.findall(r"https?://[w./-]+", text)
print(urls)   # ['https://www.nsn0704y.cn', 'http://docs.example.com']

新手坑

坑 1:忘加 r 前缀。"d"d 会被 Python 当转义符处理(d 不是合法转义,旧版本直接报错或行为诡异)。写正则一律 r"..."

坑 2:用正则解析 HTML/JSON。HTML 不是正则语言,嵌套标签正则搞不定。解析 HTML 用 BeautifulSoup(后面网页解析那篇),解析 JSON 用 json 模块。

坑 3:贪婪匹配吃多了。该用 .*? 用了 .*,结果全匹配成一坨。匹配成对结构时想想非贪婪。

小结

  • 正则 = 描述文本规律的模式语言,用 re 模块,字符串加 r 前缀
  • 核心语法:d w s . [] 字符、* + ? {} 量词、^ $ | () 位置和分组
  • findall 找全部、search 找第一个、match 从头找、fullmatch 整串匹配
  • 分组提取用 () + m.groups();替换用 re.sub
  • 默认贪婪,.*? 非贪婪;别用正则解析 HTML/JSON

练习

  1. 写正则从 “南山小站成立于2026年8月,位于吉林省敦化市” 里提取所有数字
  2. 写一个函数 extract_emails(text),返回文本里所有邮箱地址
  3. 思考:re.findall(r"<.*>", "<a>b</a>") 会返回什么?如果换成 <.*?> 呢?为什么?