正则表达式入门:从模式匹配到文本提取的实战指南

发布时间:2026/10/23 16:05:17
正则表达式入门:从模式匹配到文本提取的实战指南

简介:这份资源是面向Python初学者与进阶学习者的正则表达式专题学习资料,以PDF文档形式呈现,适合正在系统学习Python文本处理、数据清洗或爬虫开发的读者,用于补齐正则匹配与提取这一关键技能点。压缩包内共1个PDF文件,大小约661KB,内容围绕正则表达式的基础语法与实战应用展开,涵盖元字符、字符集、量词、分组捕获、贪婪与非贪婪匹配等核心知识点,并配有Python中re模块常用函数的用法说明与示例。目前已有260人学习下载,说明该主题在Python学习路径中具有稳定的关注度。读者可通过这份文档快速建立正则表达式的知识框架,掌握在字符串查找、替换、校验等场景下的实际写法,为后续处理日志解析、表单验证、网页信息抽取等任务打下基础,也可作为日常查阅的速查参考。

1. 正则表达式到底在干嘛:一个“找东西”的万能公式

你有没有过这种经历:在一份几千行的日志里,想找出所有带“error”的行;或者在一堆用户填的手机号里,挑出格式不对的那些。手动翻?眼睛会瞎。用查找替换?又不够灵活。这时候,正则表达式就该出场了。

通俗地说,正则表达式就是一套用来描述“文字长什么样”的公式。它不是具体的某段文字,而是一个模式。比如你想找“三个数字后面跟一个横杠,再跟四个数字”,你不用把每个可能的号码都列出来,只需要写一个模式,让程序拿着这个模式去文本里“对号入座”。它存在的理由很简单:人找文字靠眼睛和直觉,计算机找文字靠规则,而正则就是人和计算机之间关于“文字规则”的通用语言。搞懂它,你就能让程序帮你干那些枯燥又容易看走眼的文本筛选活儿。

2. 为什么非得用正则:从“笨办法”到“聪明模式”

2.1 没有正则的时候,我们是怎么找文本的

假设你手里有一份系统日志,想找出所有包含“登录失败”的行。最直接的办法是写个循环,逐行判断if "登录失败" in line。这招在关键词固定的时候挺好用,代码也直白。但需求稍微一变,麻烦就来了:如果“登录失败”中间可能夹着空格,或者“登录”和“失败”之间还有别的字,比如“登录验证失败”“登录尝试失败”,这个简单的包含判断就歇菜了。你可能会想到用line.find("登录")和line.find("失败")分别找位置,再比较先后顺序,代码立刻变得又长又容易出错。这就是没有正则时的典型困境:每多一种变化,就得多写一堆判断逻辑,维护起来像在补一件到处是洞的衣服。

2.2 正则的“模式思维”:一次描述,到处匹配

正则换了个思路。它不关心具体是“登录失败”还是“登录验证失败”,它只关心“登录”和“失败”这两个词是否按顺序出现,中间隔了什么都行。用正则写出来就是登录.*失败。这里的.*就像一个占位符,意思是“这里可以有任意多个任意字符”。你看,原本需要好几行代码才能说清的事,一个模式就搞定了。这种“描述规则”的能力,让正则特别擅长处理那些“大概长这样,但细节有变化”的文本。比如从网页源码里提取所有链接、从配置文件里抓出所有 IP 地址、验证用户输入的邮箱格式对不对,这些都是正则的拿手好戏。

2.3 一个最小可用的例子:把手机号从文本里揪出来

光说概念有点飘,看个具体例子。假设有一段文字,里面混着手机号和其它数字,我们想把 11 位的手机号找出来。手机号的规则是:1 开头,第二位通常是 3 到 9,后面跟着 9 个数字。用正则描述就是1[3-9]\d{9}。拆开看:1就是字面的数字 1;[3-9]表示这一位可以是 3 到 9 中的任意一个数字;\d代表任意一个数字,{9}表示前面那个\d要重复 9 次。合起来就是“1 开头,第二位 3 到 9,后面再跟 9 个数字”,正好 11 位。

import re text = "联系我:13812345678,备用:12345678901,座机:010-88886666" # 定义手机号模式:1开头,第二位3-9,后面9个数字 pattern = r"1[3-9]\d{9}" phones = re.findall(pattern, text) print(phones) # 输出:['13812345678']

这段代码里,re.findall的作用是把文本里所有符合模式的部分都找出来,返回一个列表。pattern前面的r表示这是一个原始字符串,避免反斜杠被 Python 提前转义。运行结果是只找到了13812345678,因为12345678901虽然也是 11 位,但第二位是 2,不符合[3-9]的规则。你看,正则帮我们自动完成了“筛选”这个动作,我们只需要把规则说清楚就行。

3. 正则的常用“零件”:元字符、量词和分组

3.1 元字符:那些有特殊含义的符号

正则里有一批符号不表示它们本身,而是表示一类字符或一种位置,这些符号叫元字符。最常见的几个:.表示任意一个字符(默认不包括换行);\d表示任意一个数字,等价于[0-9];\w表示字母、数字或下划线;\s表示空白字符,包括空格、制表符。还有^和$,分别表示一行的开头和结尾。比如^\d+$就表示“整行必须全部是数字,不能有别的”。这些元字符是构成模式的基本积木,记住它们,你就能拼出大部分日常需要的规则。

3.2 量词:控制“重复几次”的开关

光有元字符还不够,我们经常需要表达“重复若干次”。量词就是干这个的。*表示前面的东西出现 0 次或多次;+表示 1 次或多次;?表示 0 次或 1 次;{n}表示恰好 n 次;{n,m}表示 n 到 m 次。举个例子,\d+表示“一个或多个数字”,可以匹配1、123、9999。而\d{3,5}表示“3 到 5 个数字”,能匹配123、1234、12345,但不会匹配12或123456。量词默认是“贪婪”的,也就是尽可能多匹配。比如对字符串aaa用a+,它会一口气匹配全部三个 a。如果在量词后面加个?,比如a+?,就变成“懒惰”模式,尽可能少匹配,只匹配一个 a 就收手。这个区别在提取 HTML 标签内容时特别有用,后面会提到。

3.3 分组与捕获:把匹配到的内容“拎出来”

有时候我们不只是想知道“有没有匹配”,还想把匹配到的某一部分单独取出来。这时候就用圆括号()把需要提取的部分括起来,这叫分组。分组匹配到的内容会被“捕获”,可以通过编号或名字来引用。比如从日期2025-03-21里分别提取年、月、日,可以写成(\d{4})-(\d{2})-(\d{2})。第一个括号捕获年份,第二个捕获月份,第三个捕获日期。在 Python 的re模块里,用group(1)、group(2)就能拿到这些部分。

import re date_str = "今天是2025-03-21,明天是2025-03-22" # 三个分组分别对应年、月、日 pattern = r"(\d{4})-(\d{2})-(\d{2})" matches = re.finditer(pattern, date_str) for m in matches: print(f"年:{m.group(1)},月:{m.group(2)},日:{m.group(3)}") # 输出: # 年:2025,月:03,日:21 # 年:2025,月:03,日:22

re.finditer返回一个迭代器,每次拿出一个匹配对象。group(1)到group(3)分别对应三个括号里的内容。如果不分组,整个匹配到的字符串可以用group(0)拿到。分组让正则从“只判断有没有”升级到“还能提取关键信息”,实用性大大增强。

4. 容易搞混的几个概念:别把正则当通配符

4.1 易混点一:正则的*和文件通配符的*

很多人第一次接触正则是在命令行里用ls *.txt,以为*就是“任意字符”。但在正则里,*是量词,表示“前面的东西重复 0 次或多次”,它不能单独使用。*.txt在正则里是错的,因为*前面没有东西可重复。正则里表示“任意字符”的是.,所以“任意字符重复多次再跟 .txt”应该写成.*\.txt。注意那个\.,因为.在正则里是元字符,要匹配真正的点号必须用反斜杠转义。这个区别坑过不少人,记住:通配符的*代表任意字符,正则的*代表重复次数。

4.2 易混点二:^和$在多行模式下的行为

^匹配字符串开头,$匹配字符串结尾,这是默认行为。但如果你处理的是多行文本,想让^和$匹配每一行的开头和结尾,就需要开启多行模式。在 Python 里,给re.compile或re.findall传一个re.MULTILINE标志就行。不开多行模式时,^只认整个文本的第一个字符位置,$只认最后一个字符位置。开了之后,每个换行符后面的位置都被视为行首,每个换行符前面的位置被视为行尾。这个细节在逐行分析日志时特别关键,忘了开多行模式,匹配结果会少得让你怀疑人生。

4.3 易混点三:贪婪匹配和懒惰匹配

前面提过量词默认贪婪,但很多人第一次遇到时还是会懵。比如要从<div>内容</div><div>更多</div>里提取第一个 div 的内容,用<div>.*</div>会一口气匹配到最后一个</div>,因为.*贪婪地吃掉了中间所有的东西。改成<div>.*?</div>,.*?变成懒惰模式,匹配到第一个</div>就停。这个?加在量词后面就是“别贪心,见好就收”的意思。处理 HTML 或 XML 片段时,懒惰匹配几乎是必备技巧,否则提取出来的东西会比你想要的长一大截。

4.4 易混点四:match和search的区别

Python 的re模块里,re.match和re.search长得像,行为却不同。re.match只从字符串开头开始尝试匹配,如果开头对不上就直接返回None。re.search则会扫描整个字符串,找到第一个匹配的位置就返回。举个例子,对字符串"abc123"用模式\d+,re.match返回None,因为开头是字母不是数字;re.search则返回123。很多人写代码时随手用了match,结果明明文本里有目标却死活匹配不到,排查半天才发现是起点问题。想在整个字符串里找,就用search或findall。

4.5 易混点五:转义字符的“套娃”

正则里用反斜杠\来转义元字符,比如\.表示真正的点号。但在 Python 字符串里,反斜杠本身也有转义含义。如果你写"\d",Python 会先处理字符串转义,发现\d不是合法转义序列,可能会报警告或直接保留。为了省心,写正则时一律用原始字符串r"\d",这样 Python 就不插手,直接把\d原样交给正则引擎。这个习惯能帮你避开很多莫名其妙的报错,尤其是模式里包含\b、\n这类既是正则元字符又是字符串转义符的情况。

5. 什么时候该用正则,什么时候该绕开

正则是个好工具,但不是万能锤。判断标准很简单:看你的文本有没有“规律”,以及这个规律复不复杂。

适合用的场景:格式验证,比如检查用户输入的邮箱、手机号、身份证号是否符合基本格式;批量提取,比如从一堆日志里抓出所有 IP 地址、时间戳、订单号;简单替换,比如把文本里所有连续多个空格换成一个空格。这些场景的共同点是规则清晰、文本结构不嵌套。

不适合用的场景:解析 HTML 或 JSON 这种嵌套结构。正则处理嵌套能力很弱,写出来的模式又长又难维护,还容易漏掉边界情况。这种活儿应该交给专门的解析库,比如解析 HTML 用 BeautifulSoup,解析 JSON 用json模块。另外,如果只是找固定字符串,比如在一段文字里找“你好”,直接用in判断或者str.find就够了,没必要上正则,杀鸡用牛刀反而让代码更难读。

我自己的习惯是:动手写正则之前,先问自己三个问题——文本有规律吗?规律能用一两行说清吗?需要提取或替换吗?三个都是“是”,就放心用正则;只要有一个“否”,就停下来想想有没有更合适的工具。有一次我图省事,用正则去解析一个结构稍微复杂的配置文件,结果模式越写越长,最后自己都看不懂了,调试花了两个小时。从那以后,我每次写超过一行的正则,都强制先拿几条真实数据跑一遍re.findall看看结果,确认没问题再放进代码里。希望这个习惯也能帮到你。

本文还有配套的精品资源,点击获取