上一章我们认识了正则表达式,知道它本质上是在描述一种文本规则。
但只懂规则,还不够。
真正到了写代码的时候,你要面对的是另一件事: 怎么让 Python 真正帮你把文本里的内容提取出来
这就轮到 re 模块出场了。
如果说正则表达式是一套匹配规则,那么 re 模块就是你手里的工具箱。 规则负责告诉程序要找什么。re 模块负责帮你去找、去提取、去替换、去判断。
这一章,我们不再停留在概念层面,而是直接进入最常见的三个实战场景:
提取手机号 提取邮箱 提取日期
这三个场景看起来简单,其实很典型。
因为它们都属于一类问题: 文本内容不固定,但格式有规律
这正是正则最擅长的地方。
一、为什么一定要学会用 re 模块
上一章你已经知道,正则表达式能描述规则。
但光会写规则,其实没法直接干活。 你还得知道,规则写出来之后,怎么交给 Python 去执行。
比如下面这段文本:
text = "用户A电话是13812345678,用户B电话是15600001111"
你现在想把里面所有手机号找出来。 思路当然是写一个匹配 11 位数字的规则。 可规则写完后,谁来帮你找
答案就是 re 模块。
它是 Python 标准库的一部分,不需要单独安装,导入就能用:
import re
有了它,你就可以做很多事:
在文本里查找第一个符合条件的内容 找出所有符合条件的内容 判断某个字符串是否符合某种格式 把符合规则的内容替换掉 把文本按规则拆分开
你会发现,re 模块并不是只给爬虫用的。 只要你碰到文本清洗、日志分析、表单校验、内容提取,它都很常见。
所以这一章学的,其实不是一个冷门技巧,而是非常高频的文本处理能力。
二、先认识四个最常用的方法
re 模块里方法不少,但新手最常用、最值得先掌握的,其实就这几个:
re.findall()re.search()re.match()re.sub()
这一章我们重点先抓前面两个,因为提取数据时它们最常用。
先看 findall()。
它的作用非常直接: 找出所有符合规则的内容,并返回一个列表。
例如:
import retext = "今天卖了12件,昨天卖了30件,上周卖了105件"result = re.findall(r"\d+", text)print(result)
输出结果:
['12', '30', '105']
这个方法特别适合做批量提取。
再看 search()。
它的作用是: 在整段文本里查找第一个符合规则的内容。
import retext = "订单号是A20250329,金额是99元"result = re.search(r"\d+", text)print(result)print(result.group())
输出大致是:
<re.Match object; span=(4, 12), match='20250329'>20250329
你会看到,search() 返回的不是列表,而是一个匹配对象。 你需要再用 .group() 才能真正拿到匹配到的内容。
这里先有个直观认识就够了:
想找全部,用 findall()只想找第一个,用 search()
后面我们做手机号、邮箱、日期提取时,绝大多数情况都离不开这两个。
三、写正则时,尽量养成原始字符串的习惯
在正式做案例之前,先提醒你一个非常重要的小习惯。
写正则时,最好这样写:
r"\d+"
而不是这样写:
"\d+"
为什么
因为反斜杠在 Python 字符串里本身就有特殊作用。 正则里又经常要用反斜杠。 这两套规则一叠加,就容易让人混乱。
加上前面的 r 之后,Python 会把它当成原始字符串处理。 这样你写的正则规则会更清楚,也更少出错。
所以从这一章开始,你看到的正则,大多数都会写成这种形式:
r"某种规则"
这个习惯越早养成越好。
四、第一个实战:提取手机号
我们先从手机号开始。
因为它最容易理解。 手机号虽然每个人都不同,但在很多场景里,它都有一个明显特点: 连续 11 位数字
先看一段文本:
text = "张三:13812345678,李四:15600001111,王五:电话暂未登记"
现在我们要把里面的手机号提取出来。
最基础的写法就是:
import retext = "张三:13812345678,李四:15600001111,王五:电话暂未登记"phones = re.findall(r"\d{11}", text)print(phones)
输出结果:
['13812345678', '15600001111']
这个例子非常有代表性。
你根本不需要知道具体号码是什么。 你只需要告诉程序,帮我找所有连续 11 位数字。 程序就会自动把符合条件的内容找出来。
这就是正则处理文本的基本思路。
五、为什么手机号提取不能只停留在 11 位数字
刚才那个写法能跑,也能拿到结果。 但真实场景里,你不能永远只满足于能跑。
因为连续 11 位数字,并不一定就真的是手机号。
比如文本里可能有这样的内容:
text = "订单编号12345678901,客户电话13812345678"
如果你还是用:
r"\d{11}"
那么订单编号也会被一起匹配出来。
这显然不够严谨。
所以进一步我们要思考: 手机号有没有更具体的规律
以常见的中国大陆手机号为例,通常是 1 开头,第二位一般是 3 到 9,总长度 11 位。
那么更常见的规则可以写成:
r"1[3-9]\d{9}"
来拆开看:
1 表示第一位必须是 1[3-9] 表示第二位是 3 到 9 之间任意一个数字\d{9} 表示后面再跟 9 位数字
加起来刚好 11 位。
代码示例:
import retext = "订单编号12345678901,客户电话13812345678,备用号码16688889999"phones = re.findall(r"1[3-9]\d{9}", text)print(phones)
输出:
['13812345678', '16688889999']
这样就比单纯匹配 11 位数字更像样了。
这也是你后面做正则时很重要的一种提升: 不要只想着匹配成功,还要考虑误匹配的问题。
六、如果手机号中间带了横杠或空格怎么办
现实数据往往没那么整齐。
比如你可能拿到这样的文本:
text = "联系电话:138-1234-5678,备用:156 0000 1111"
这时候如果你还用前面的规则,就匹配不到了。 因为中间已经不是纯数字连续排列了。
那怎么办
这时候就要根据实际格式去调整规则。
例如,匹配带横杠的手机号:
r"1[3-9]\d{2}-\d{4}-\d{4}"
匹配带空格的手机号:
r"1[3-9]\d{2}\s\d{4}\s\d{4}"
如果你想同时兼容横杠和空格,还可以稍微灵活一点:
r"1[3-9]\d{2}[- ]\d{4}[- ]\d{4}"
这里的 [- ] 表示中间那个位置,要么是横杠,要么是空格。
示例代码:
import retext = "联系电话:138-1234-5678,备用:156 0000 1111"result = re.findall(r"1[3-9]\d{2}[- ]\d{4}[- ]\d{4}", text)print(result)
输出:
['138-1234-5678', '156 0000 1111']
你会发现,正则的真正价值不在于死记一个规则。 而在于你能不能根据实际数据,及时改写规则。
这才是实战能力。
七、提取完手机号之后,往往还要做清洗
这一点很容易被忽略。
很多新手以为,匹配出来就结束了。 其实真实开发里,提取只是第一步。 后面通常还要进一步清洗。
比如刚才提取出来的是:
['138-1234-5678', '156 0000 1111']
如果你后面要保存到数据库,或者统一展示,就可能希望把它们变成纯数字格式。
这时可以继续处理:
import retext = "联系电话:138-1234-5678,备用:156 0000 1111"phones = re.findall(r"1[3-9]\d{2}[- ]\d{4}[- ]\d{4}", text)cleaned = []for phone in phones: phone = phone.replace("-", "").replace(" ", "") cleaned.append(phone)print(cleaned)
输出:
['13812345678', '15600001111']
所以你一定要建立一个意识:
正则负责找出来 字符串方法负责进一步整理
这两者不是对立关系,而是经常配合使用。
八、第二个实战:提取邮箱地址
接下来我们看邮箱。
邮箱比手机号稍微复杂一点,因为它不是纯数字,而是由几部分组成的。
最典型的邮箱,结构通常是:
用户名 + @ + 域名 + 后缀
例如:
abc123@qq.comtom_test@163.comhello.world@gmail.com
那邮箱怎么匹配
先从一个比较常见、适合入门的规则开始:
r"[\w.-]+@[\w.-]+\.\w+"
别急,我们把它拆开。
[\w.-]+ 表示用户名部分,可以由字母、数字、下划线、点、横杠组成,并且出现一次或多次@ 就是字面意义上的 @[\w.-]+ 表示域名主体\. 表示真正的点号\w+ 表示后缀部分,比如 com、cn、net
看代码:
import retext = "联系邮箱:tom123@qq.com,备用邮箱:admin-test@163.com"emails = re.findall(r"[\w.-]+@[\w.-]+\.\w+", text)print(emails)
输出:
['tom123@qq.com', 'admin-test@163.com']
这已经能处理很多常见场景了。
九、邮箱提取里,为什么点号一定要转义
这个细节一定要懂。
正则里的点号 . 默认表示任意单个字符。 可邮箱地址里的那个点,比如 .com 里的点,它本身就是一个真正的句号。
如果你直接写:
r"@[\w.-]+.\w+"
那个点就会被理解成任意字符。 结果会变得不够严格,甚至把一些不该匹配的内容也匹配进去。
所以要写成:
r"\."
这个反斜杠的作用就是转义,让点号失去特殊含义,回到普通字符本身。
以后你只要想匹配真正的点号、加号、括号、问号这类有特殊意义的符号,都要留意是否需要转义。
十、邮箱规则为什么看起来总是很复杂
因为邮箱本身的合法格式,其实比很多人想象得更复杂。
比如有的邮箱用户名里会有点。 有的会有横杠。 有的域名还不止一层。
像下面这些都很常见:
hello.world@qq.commy-test@company.cnservice_team@sub.mail.com
所以,邮箱匹配通常比手机号更难做到百分之百完美。
这时候你要知道一个很重要的实战原则:
大多数业务场景里,不必追求世界上最严苛的正则 只要你的规则能覆盖绝大多数正常情况,并且误判不太严重,就已经够用了
很多初学者一学正则,就总想写出一个无懈可击的规则。 结果越写越复杂,最后自己都看不懂。
这并不是好习惯。
教程阶段,我们先掌握适合常见场景的写法。 真正遇到严格校验需求,再按业务规则细化。
十一、邮箱提取后,也要注意去重和清洗
比如一段文本里,同一个邮箱可能出现很多次:
text = "主邮箱是abc@qq.com,备用还是abc@qq.com,另一个是test@163.com"
如果你直接提取:
import retext = "主邮箱是abc@qq.com,备用还是abc@qq.com,另一个是test@163.com"emails = re.findall(r"[\w.-]+@[\w.-]+\.\w+", text)print(emails)
结果是:
['abc@qq.com', 'abc@qq.com', 'test@163.com']
这时候如果你只想保留唯一值,就可以配合集合去重:
unique_emails = list(set(emails))print(unique_emails)
当然,集合去重后顺序可能会变。 这一点你要心里有数。
如果你既想去重又想保留顺序,可以后面再学更稳妥的写法。 但在当前阶段,你至少要有这个意识:
提取出来的结果,不一定就是最终结果 有时还需要清洗、去重、标准化
十二、第三个实战:提取日期
日期比手机号和邮箱更常见。
聊天记录里有日期 日志里有日期 订单里有日期 文件名里也经常带日期
最常见的一种日期格式是:
2026-03-29
这种格式其实很好匹配,因为规则很清楚:
4 位年份 1 个横杠 2 位月份 1 个横杠 2 位日期
所以最基础的写法可以是:
r"\d{4}-\d{2}-\d{2}"
来看示例:
import retext = "项目开始时间是2026-03-29,结束时间是2026-04-15"dates = re.findall(r"\d{4}-\d{2}-\d{2}", text)print(dates)
输出:
['2026-03-29', '2026-04-15']
这个写法已经能把很多标准日期提取出来了。
十三、日期匹配成功,不等于日期一定真实存在
这里是一个非常容易忽略的问题。
正则只能判断格式像不像。 它不负责判断这个日期在现实中是否真的成立。
比如下面这些内容:
2026-13-402025-99-992026-00-00
只要你用的是:
r"\d{4}-\d{2}-\d{2}"
它们照样都能匹配成功。
因为从正则角度看,它们确实符合 4 位数字-2 位数字-2 位数字 这种结构。
所以你要明白: 正则更擅长做格式筛选 真正的业务校验,往往还需要后续逻辑
比如后面你学时间处理模块时,就可以把匹配出的日期再交给 datetime 去验证,看它是否真的是合法日期。
这就是现实开发里很常见的一种组合:
先用正则粗筛 再用程序逻辑精确校验
十四、如果日期格式不统一,该怎么办
现实数据常常不统一。
有的写成:
2026-03-29
有的写成:
2026/03/29
有的甚至写成:
2026.03.29
那怎么办
你就要学会把规则写得灵活一点。
例如:
r"\d{4}[-/.]\d{2}[-/.]\d{2}"
这里的 [-/.] 表示这个位置可以是横杠、斜杠或点号中的任意一个。
代码示例:
import retext = "创建时间:2026-03-29,修改时间:2026/04/01,归档时间:2026.04.15"dates = re.findall(r"\d{4}[-/.]\d{2}[-/.]\d{2}", text)print(dates)
输出:
['2026-03-29', '2026/04/01', '2026.04.15']
这就是正则实战里非常有用的一个思路:
别只盯着一种输入格式 要学会观察数据的多样性,再把规则稍微放宽一点
当然,放宽不等于乱放。 你还是要围绕规律来写。
十五、如果想把年、月、日分别拿出来怎么办
这时候就轮到分组上场了。
比如你不只是想要整个日期,还想把年份、月份、日期分别提取出来,那么可以这样写:
r"(\d{4})-(\d{2})-(\d{2})"
来看代码:
import retext = "今天是2026-03-29"result = re.search(r"(\d{4})-(\d{2})-(\d{2})", text)print(result.group(0))print(result.group(1))print(result.group(2))print(result.group(3))
输出大致是:
2026-03-2920260329
这里要看懂:
group(0) 是整体匹配结果group(1) 是第一个括号里的内容,也就是年份group(2) 是第二个括号里的内容,也就是月份group(3) 是第三个括号里的内容,也就是日期
这个能力特别重要。
因为很多时候,我们不是只想把日期整段抠出来,而是还要继续加工。 比如:
只保留年份 按月份统计 把日期重新拼成其他格式
而分组,正好为这些后续操作打基础。
十六、把手机号、邮箱、日期放到一段混合文本里一起提取
前面我们是分开练的。 现在来一段更像真实工作的文本。
text = """用户信息如下:姓名:张三手机号:13812345678邮箱:zhangsan_test@qq.com注册日期:2026-03-29姓名:李四手机号:15600001111邮箱:lisi.work@163.com注册日期:2026/04/01"""
现在我们分别提取手机号、邮箱、日期。
import retext = """用户信息如下:姓名:张三手机号:13812345678邮箱:zhangsan_test@qq.com注册日期:2026-03-29姓名:李四手机号:15600001111邮箱:lisi.work@163.com注册日期:2026/04/01"""phones = re.findall(r"1[3-9]\d{9}", text)emails = re.findall(r"[\w.-]+@[\w.-]+\.\w+", text)dates = re.findall(r"\d{4}[-/]\d{2}[-/]\d{2}", text)print("手机号:", phones)print("邮箱:", emails)print("日期:", dates)
输出结果大致会是:
手机号: ['13812345678', '15600001111']邮箱: ['zhangsan_test@qq.com', 'lisi.work@163.com']日期: ['2026-03-29', '2026/04/01']
这个例子你一定要好好体会。
因为真实开发里,你面对的往往不是一条规整数据,而是一大段文本。 而你要做的,就是从里面把不同类型的信息一个个拎出来。
这就是 re 模块最直接的使用方式。
十七、search 和 findall 在实战里该怎么选
这个问题很多人会迷糊。
其实不难。
如果你面对的是一整段文本,并且里面可能有多个结果,通常先想到 findall()。
比如一篇文章里有多个手机号、多个邮箱、多个日期。 那你肯定想全部提取出来。 这时候就适合 findall()。
如果你只是想判断有没有,或者只关心第一个匹配结果,就可以用 search()。
比如:
我只想找到第一个邮箱 我只想看看这段文本里是否存在日期 我只想把第一个手机号先拿出来试试
就可以用 search()。
例如:
import retext = "请联系 support@test.com 或 admin@demo.com"result = re.search(r"[\w.-]+@[\w.-]+\.\w+", text)if result: print("找到邮箱:", result.group())else: print("没有找到邮箱")
输出:
找到邮箱: support@test.com
你会发现,search() 特别适合搭配 if 使用。 因为它找不到时会返回 None,找到时会返回匹配对象。
这种写法在校验类场景里很常见。
十八、re.match 为什么很多时候不太适合做提取
match() 也是初学者常见的方法,但你现在要先知道它的特点。
它只会从字符串开头开始匹配。
例如:
import retext = "邮箱是abc@qq.com"print(re.match(r"[\w.-]+@[\w.-]+\.\w+", text))print(re.search(r"[\w.-]+@[\w.-]+\.\w+", text))
这里 match() 很可能匹配不到。 因为字符串开头是“邮箱是”,不是邮箱地址本身。
而 search() 会在整段文本里往后找,所以能找到。
因此在做文本提取时,大多数情况下你更应该优先想到 search() 和 findall()。match() 更适合那些要求从开头就必须符合规则的场景,比如格式校验。
这一点区分清楚,会少踩很多坑。
十九、用 compile 让规则更清楚
当你的正则规则比较长,或者要重复使用很多次时,可以把它先编译成一个模式对象。
写法如下:
import rephone_pattern = re.compile(r"1[3-9]\d{9}")email_pattern = re.compile(r"[\w.-]+@[\w.-]+\.\w+")date_pattern = re.compile(r"\d{4}[-/]\d{2}[-/]\d{2}")text = "手机号13812345678,邮箱abc@qq.com,日期2026-03-29"print(phone_pattern.findall(text))print(email_pattern.findall(text))print(date_pattern.findall(text))
输出:
['13812345678']['abc@qq.com']['2026-03-29']
这样写的好处是什么
第一,规则更清楚,代码可读性更好。 第二,后面重复使用时更方便。 第三,复杂项目里可以把各种规则集中管理,不容易乱。
你现在不一定非要马上大量使用 compile()。 但至少要知道,到了代码稍微复杂一点的时候,这种写法会很舒服。
二十、提取失败时,最应该先检查什么
很多人一用正则就报错,或者什么都匹配不到,然后马上怀疑 Python 有问题。
其实大多数时候,是下面几个原因。
第一,规则写得太死。 文本格式稍微变一下,就匹配不到。
比如你只写了横杠分隔日期,那遇到斜杠就不行了。
第二,忘了转义。 比如想匹配点号,结果直接写了 .,导致规则变味。
第三,忘了写原始字符串。 某些带反斜杠的规则就可能出问题。
第四,混淆了 search() 和 match()。 文本中间明明有内容,偏偏用 match() 从头匹配,自然找不到。
第五,数据本身比你想象得更脏。 比如前后多了空格、换行、中文符号、隐藏字符。
所以当你匹配失败时,别急着乱改。 先回头问自己几个问题:
我现在处理的文本到底长什么样 我的规则是不是过于理想化了 我想找的是全部还是第一个 我是不是把某些特殊字符写错了
这种排查习惯,比死记一堆规则更重要。
二十一、真实工作里,正则提取通常只是流程中的一步
你一定不要把正则看成万能钥匙。
它很强,但它通常只是整个数据处理流程中的一环。
比如做用户信息清洗时,流程往往是:
先读入文本 再用正则提取手机号、邮箱、日期 然后去重 再清洗格式 最后保存成表格或写入数据库
也就是说,正则负责的是把有规律的信息从杂乱文本里抓出来。 但抓出来之后,你通常还要做进一步处理。
这一点很关键。
因为很多初学者一学正则,就容易陷进去,老觉得一个规则能解决全部问题。 其实真实开发里,更常见的是组合拳:
正则 + 字符串处理 正则 + 列表循环 正则 + 条件判断 正则 + 文件读写
你越早明白这一点,后面学数据清洗、日志处理时就越顺。
二十二、写一个完整一点的小案例
下面我们来做一个稍微完整一点的案例。
假设现在有一段客户登记文本,我们要把里面的手机号、邮箱、日期都提取出来,并整理成更清晰的结果。
import retext = """客户A手机号:138-1234-5678邮箱:a_user@qq.com登记日期:2026-03-29客户B手机号:156 0000 1111邮箱:b.test@163.com登记日期:2026/04/01客户C手机号:未填写邮箱:暂缺登记日期:2026-04-15"""phone_pattern = re.compile(r"1[3-9]\d{2}[- ]\d{4}[- ]\d{4}")email_pattern = re.compile(r"[\w.-]+@[\w.-]+\.\w+")date_pattern = re.compile(r"\d{4}[-/]\d{2}[-/]\d{2}")phones = phone_pattern.findall(text)emails = email_pattern.findall(text)dates = date_pattern.findall(text)cleaned_phones = []for phone in phones: cleaned_phone = phone.replace("-", "").replace(" ", "") cleaned_phones.append(cleaned_phone)print("清洗后的手机号:", cleaned_phones)print("邮箱:", emails)print("日期:", dates)
输出结果大致是:
清洗后的手机号: ['13812345678', '15600001111']邮箱: ['a_user@qq.com', 'b.test@163.com']日期: ['2026-03-29', '2026/04/01', '2026-04-15']
这个例子里,有几个地方特别值得你注意。
第一,手机号格式并不统一,所以规则要兼容横杠和空格。 第二,提取完手机号后,还做了一次标准化清洗。 第三,邮箱和日期分别用不同规则去提取。 第四,有些字段缺失也没关系,因为正则只会抓到符合条件的内容。
这就是很像真实业务的一种处理方式。
二十三、这一章你真正该学会的,不是三条规则,而是方法
很多人学到这里,容易把重点理解错。
他们会觉得,这一章学完,自己背住手机号正则、邮箱正则、日期正则就行了。
其实不是。
真正重要的,不是把三条规则背下来。 而是你有没有形成这样一套思维方式:
先看文本里有没有规律 再把规律写成正则 然后用 re 模块提取 提取后再做清洗和整理 最后把结果放进后续流程中
这套方法一旦会了,你以后遇到别的内容也能照着做。
比如:
提取订单号 提取身份证号 提取网址 提取日志中的 IP 地址 提取文件名中的日期 提取评论里的标签词
表面上内容不同,底层方法其实都差不多。
二十四、给初学者的一个实用建议
刚开始练 re 模块时,不要一上来就去挑战特别长、特别变态的正则。
那样很容易把自己绕晕。
更好的练法是:
先用一小段文本做实验 先让规则匹配成功 再慢慢增加复杂情况 再观察哪些地方会误匹配 最后一点点修正规则
比如手机号,你先从连续 11 位数字开始。 然后再考虑 1 开头。 再考虑第二位范围。 再考虑横杠和空格。 这样一层层加,思路会非常清楚。
不要想着一步到位写出最完美的规则。 正则真正厉害的人,也不是一次写对全部。 而是会边测试、边观察、边调整。
这种过程,本身就是编程能力的一部分。
本章小结
re 模块是 Python 处理正则表达式的核心工具。 在文本提取场景里,最常用的方法是:
findall(),提取所有匹配结果search(),查找第一个匹配结果match(),从开头开始匹配compile(),把规则先编译成模式对象,方便复用
这一章我们围绕三个典型场景做了实战:
手机号提取,重点是理解位数规则和格式变化。 邮箱提取,重点是理解用户名、域名、点号转义这些结构。 日期提取,重点是明白格式匹配和真实日期校验不是一回事。
更重要的是,你要开始建立一个真实的数据处理意识:
正则不是孤立存在的。 它通常只是提取的第一步。 后面还要配合字符串处理、列表操作、去重、清洗、格式统一,才能真正变成可用的数据结果。
当你开始这样看问题时,re 模块就不再只是一个语法点,而会变成你处理复杂文本时非常顺手的工具。
课后练习
你可以自己动手做下面几个练习,效果会非常明显。
第一,准备一段包含多个手机号的文本,既有纯数字格式,也有带横杠和空格的格式,尝试全部提取出来,并统一清洗成纯数字。
第二,准备一段包含多个邮箱的文本,提取出所有邮箱,再去重。
第三,准备一段包含多种日期格式的文本,比如横杠、斜杠、点号三种写法都放进去,尝试一次性匹配出来。
第四,把手机号、邮箱、日期混合放进一段长文本里,分别提取成三个列表。
第五,自己写一个更贴近实际的客户信息文本,然后模仿本章的小案例,做一遍完整清洗。