65_Python正则表达式入门

65_Python正则表达式入门

Python正则表达式入门:从模式匹配到数据提取实战

文章目录

  • Python正则表达式入门:从模式匹配到数据提取实战
    • 前言
    • 一、`re` 模块快速上手
      • 1.1 第一个正则示例
      • 1.2 原生字符串 `r"..."`
    • 二、核心匹配函数
      • 2.1 `search()`:搜索第一个匹配
      • 2.2 `match()`:从字符串开头匹配
      • 2.3 `findall()`:查找所有匹配
      • 2.4 `finditer()`:返回迭代器(推荐大文本使用)
      • 2.5 `sub()`:替换匹配内容
    • 三、常用正则模式速查
      • 3.1 字符类
      • 3.2 量词
      • 3.3 边界与锚点
      • 3.4 贪婪与非贪婪
    • 四、捕获组与高级用法
      • 4.1 分组捕获
      • 4.2 命名分组
      • 4.3 前瞻与后顾
      • 4.4 常用模式速查表
    • 五、编译正则:提升性能
    • 六、实战案例:简易网页爬虫辅助工具
    • 总结
    • ✅ 亮点总结
    • 适用场景
    • 扩展方向

前言

正则表达式(Regular Expression)是处理文本的瑞士军刀。无论是验证用户输入、提取网页数据、清洗日志文件,还是批量替换文本,正则表达式都能用几行代码完成看似复杂的任务。Python内置的re模块提供了完整的正则表达式支持。

学习正则表达式的现实意义:在面试中,正则表达式是常见考点;在爬虫开发中,它是数据提取的核心工具;在日志分析中,它是错误排查的利器。然而,正则表达式也有一个"名声"——它看起来像天书,容易把简单问题复杂化。本文的目标是让你掌握正则的核心套路,学会"用合适的工具做合适的事",避免陷入过度依赖正则的陷阱。本文将从零带你掌握正则表达式的核心用法。

一、re模块快速上手

1.1 第一个正则示例

正则表达式最简单的入门方式就是从一个具体的例子开始。下面这个例子演示了如何从一段文本中提取手机号码——这是正则表达式最经典的应用场景之一。

importre text="我的手机号是13812345678,请尽快联系我"# 匹配中国大陆手机号pattern=r"1[3-9]\d{9}"match=re.search(pattern,text)ifmatch:print(f"找到手机号:{match.group()}")# 找到手机号: 13812345678print(f"起始位置:{match.start()}")# 7print(f"结束位置:{match.end()}")# 18

关键解析re.search()在整个字符串中搜索第一个匹配,返回一个Match对象。如果没找到匹配,它返回None——所以在生产代码中务必先检查返回值是否为None,否则直接调用.group()会抛出AttributeError

1.2 原生字符串r"..."

正则表达式中大量使用反斜杠,使用原生字符串r"..."可以避免转义混乱:

# 繁琐的普通字符串pattern1="\\d+\\.\\d+"# 简洁的原生字符串(推荐)pattern2=r"\d+\.\d+"print(pattern1==pattern2)# True

二、核心匹配函数

re模块提供了多个匹配函数,各自有不同的语义和使用场景。理解它们的区别是正则入门的关键。常见面试题:“match()search()有什么区别?”——答案很简单,match()只从字符串开头匹配,search()搜索整个字符串。

2.1search():搜索第一个匹配

text="价格:¥19.9,折扣价:¥9.9"pattern=r"¥(\d+\.\d+)"match=re.search(pattern,text)print(match.group())# ¥19.9print(match.group(1))# 19.9 (第一个捕获组)

2.2match():从字符串开头匹配

# match() 只匹配开头print(re.match(r"\d+","123abc"))# <re.Match object>print(re.match(r"\d+","abc123"))# None (不在开头)# search() 搜索任意位置print(re.search(r"\d+","abc123"))# <re.Match object>

2.3findall():查找所有匹配

text="我有苹果5个,橘子12个,香蕉8个"pattern=r"(\w+)(\d+)个"# 有捕获组时返回元组列表result=re.findall(pattern,text)print(result)# [('苹果', '5'), ('橘子', '12'), ('香蕉', '8')]forfruit,countinresult:print(f"{fruit}:{count}个")

2.4finditer():返回迭代器(推荐大文本使用)

text="Python 3.9, Python 3.10, Python 3.11"pattern=r"Python (\d+\.\d+)"formatchinre.finditer(pattern,text):print(f"版本{match.group(1)}位于位置{match.start()}")

2.5sub():替换匹配内容

# 脱敏手机号text="联系人: 张三 13812345678, 李四 13987654321"masked=re.sub(r"(\d{3})\d{4}(\d{4})",r"\1****\2",text)print(masked)# 联系人: 张三 138****5678, 李四 139****4321# 使用函数进行替换defcensor(match):word=match.group()returnword[0]+"*"*(len(word)-1)text="这个项目简直太棒了,效率非常高"result=re.sub(r"[了得]",censor,text)print(result)# 这个项目简直太棒*,效率非常高

三、常用正则模式速查

正则表达式的元字符是构建匹配模式的"积木"。这些符号初看可能很抽象,但每个都有明确的使用场景。学习技巧:不要试图一次记住所有元字符——先熟练\d\w.*+?这几个最常用的,剩下的需要时再查即可。

3.1 字符类

patterns={r"\d":"匹配任意数字 [0-9]",r"\D":"匹配任意非数字",r"\w":"匹配字母数字下划线 [a-zA-Z0-9_]",r"\W":"匹配非字母数字下划线",r"\s":"匹配空白字符(空格、制表符、换行等)",r"\S":"匹配非空白字符",r".":"匹配任意字符(除换行符)",}test="A 1 _ 中 @"print(re.findall(r"\d",test))# ['1']print(re.findall(r"\w",test))# ['A', '1', '_', '中']print(re.findall(r"\s",test))# [' ', ' ', ' ', ' ']

3.2 量词

test_cases={r"a*":"a出现0次或多次",r"a+":"a出现1次或多次",r"a?":"a出现0次或1次",r"a{3}":"a恰好出现3次",r"a{2,4}":"a出现2到4次",r"a{2,}":"a出现至少2次",}print(re.findall(r"a*","baaac"))# ['', 'aaa', '', '']print(re.findall(r"a+","baaac"))# ['aaa']print(re.findall(r"a{2,3}","baaaaac"))# ['aaa']print(re.findall(r"\d{3,4}","电话: 010-1234567"))# ['010', '1234']

3.3 边界与锚点

边界匹配是正则表达式中的重要概念,它不消耗字符,只匹配"位置"。^匹配行首,$匹配行尾,\b匹配单词边界。理解边界匹配对于精确匹配非常重要——比如你想匹配独立的单词 “cat” 而不是 “category” 中的 “cat”,就需要用到\bcat\bre.MULTILINE标志让^$匹配每行的开头和结尾(而非整个字符串的开头和结尾)。

test="""第一行 第二行 第三行"""# ^ 匹配行首,$ 匹配行尾print(re.findall(r"^第.",test,re.MULTILINE))# ['第一', '第二', '第三']# \b 匹配单词边界text="cat category scatter cat"print(re.findall(r"\bcat\b",text))# ['cat', 'cat']

3.4 贪婪与非贪婪

这是正则表达式中最经典的"坑"之一。贪婪匹配是正则的默认行为——量词(*,+,?,{})会尽可能多地匹配字符。这在很多场景下会导致意料之外的结果,比如在HTML中使用.*匹配时可能跨过多个标签。非贪婪匹配通过在量词后加?实现(如*?,+?,??),它会尽可能少地匹配。一个实用的经验法则:在HTML/XML解析、引号内容提取等场景中,几乎总是应该使用非贪婪匹配。

html="<div>内容1</div><div>内容2</div>"# 贪婪匹配(默认):匹配尽可能多的内容greedy=re.findall(r"<div>.*</div>",html)print(greedy)# ['<div>内容1</div><div>内容2</div>']# 非贪婪匹配:加 ? 匹配尽可能少的内容lazy=re.findall(r"<div>.*?</div>",html)print(lazy)# ['<div>内容1</div>', '<div>内容2</div>']

四、捕获组与高级用法

4.1 分组捕获

# 提取日期text="今天是2024-01-15,明天是2024-01-16"pattern=r"(\d{4})-(\d{2})-(\d{2})"formatchinre.finditer(pattern,text):year,month,day=match.groups()print(f"{year}{month}{day}日")

4.2 命名分组

# 使用 (?P<name>...) 给分组命名pattern=r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})"text="出生日期: 1995-08-22"match=re.search(pattern,text)ifmatch:print(match.group("year"))# 1995print(match.group("month"))# 08print(match.group("day"))# 22print(match.groupdict())# {'year': '1995', 'month': '08', 'day': '22'}

4.3 前瞻与后顾

# 正向肯定前瞻 (?=...):后面必须跟着...text="100元 200美元 300元 400欧元"# 匹配后面跟着"元"的数字result=re.findall(r"\d+(?=元)",text)print(result)# ['100', '300']# 正向肯定后顾 (?<=...):前面必须是...# 匹配前面是"$"的数字text="商品: $199, $299, ¥599"result=re.findall(r"(?<=\$)\d+",text)print(result)# ['199', '299']# 负向前瞻 (?!...):后面不能跟着...text="Python3 Python2 Python"result=re.findall(r"Python(?!\d)",text)print(result)# ['Python'] (不匹配后面有数字的)

4.4 常用模式速查表

模式含义示例
\d{3,4}3-4位数字区号
[\u4e00-\u9fa5]中文字符匹配中文
[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}Email地址基本邮箱匹配
1[3-9]\d{9}中国大陆手机号13812345678
\d{17}[\dXx]18位身份证身份证号
https?://[^\s]+URL网址

五、编译正则:提升性能

频繁使用的正则表达式应该预编译re.compile()将正则模式编译为一个正则对象,后续可以反复使用,避免每次调用re.match()时重复解析和编译。在大批量文本处理中,预编译能带来显著的性能提升——在循环内重复使用同一个正则表达式时,编译版本比未编译版本快30%-50%。最佳实践:将编译后的正则对象定义为模块级别的常量,在整个程序中复用。

importtime# 预编译email_pattern=re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')emails=["user@example.com","invalid-email","test@company.co.uk","no@domain","good@email.org",]foremailinemails:is_valid=bool(email_pattern.match(email))status="✓"ifis_validelse"✗"print(f"{status}{email}")# 性能测试test_text="hello@world.com "*10000start=time.time()for_inrange(1000):re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',"test@example.com")print(f"未编译耗时:{time.time()-start:.3f}s")start=time.time()for_inrange(1000):email_pattern.match("test@example.com")print(f"编译后耗时:{time.time()-start:.3f}s")

六、实战案例:简易网页爬虫辅助工具

下面的实战案例将前面学到的知识整合成几个实用的函数——邮箱提取、URL提取、HTML表格解析、敏感信息脱敏。这些函数都是实际爬虫和数据清洗项目中可以直接复用的工具代码。

importredefextract_emails(text):"""从文本中提取所有邮箱地址"""pattern=re.compile(r'\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b')returnpattern.findall(text)defextract_urls(text):"""从文本中提取所有URL"""pattern=re.compile(r'https?://[^\s<>"\']+|www\.[^\s<>"\']+')returnpattern.findall(text)defparse_html_table(html):"""解析简易HTML表格"""# 提取所有行rows=re.findall(r'<tr>(.*?)</tr>',html,re.DOTALL)table_data=[]forrowinrows:# 提取每行中的单元格cells=re.findall(r'<t[dh]>(.*?)</t[dh]>',row,re.DOTALL)# 清理HTML标签clean_cells=[re.sub(r'<.*?>','',cell).strip()forcellincells]table_data.append(clean_cells)returntable_datadefmask_sensitive_info(text):"""脱敏处理:隐藏身份证号、手机号、银行卡号"""text=re.sub(r'(\d{3})\d{4}(\d{4})',r'\1****\2',text)text=re.sub(r'(\d{6})\d{8}(\d{4})',r'\1********\2',text)text=re.sub(r'(\d{4})\s?(\d{4})\s?(\d{4})\s?(\d{4})',r'\1 **** **** \4',text)returntext# 测试sample=""" 联系人: 张三, 邮箱: zhang@test.com 官网: https://www.example.com 手机: 13812345678 身份证: 320102199001011234 <table> <tr><th>姓名</th><th>分数</th></tr> <tr><td>张三</td><td>95</td></tr> <tr><td>李四</td><td>88</td></tr> </table> """print("提取邮箱:",extract_emails(sample))print("提取URL:",extract_urls(sample))print("解析表格:",parse_html_table(sample))print("脱敏后:",mask_sensitive_info(sample))

总结

正则表达式是文本处理的利器,Pythonre模块的核心API:

  • search()查找第一个匹配,findall()查找所有匹配
  • sub()替换匹配内容,split()按模式分割字符串
  • 使用r"..."原生字符串避免转义困扰
  • 频繁使用的正则用re.compile()预编译提升性能
  • 非贪婪匹配*?+?在HTML解析等场景中非常实用

正则表达式初看可能有些晦涩,但一旦掌握,处理文本的效率和优雅程度都会显著提升。建议收藏本文用作速查参考。下一篇我们将进入多线程与并发编程的世界。

✅ 亮点总结

  • 系统讲解re模块五大核心函数(search/findall/sub/split/match),附带速查表
  • 元字符、量词、分组、零宽断言循序渐进,从基础模式到复杂匹配
  • 预编译re.compile()提升性能,非贪婪匹配解决 HTML 解析中的常见坑
  • 实战案例:邮箱/URL 提取、敏感信息脱敏、HTML 表格解析,即学即用

适用场景

  • 数据清洗:从日志/爬虫结果中提取结构化字段(日期、IP、金额等)
  • 表单验证:校验用户输入的手机号、邮箱、身份证号等格式合法性
  • 代码重构:批量替换项目中的旧 API 调用、统一代码风格

扩展方向

  • 学习正则表达式的性能优化,了解回溯陷阱和原子组
  • 结合re的 flags 参数掌握多行匹配、忽略大小写等高级模式
  • 探索regex第三方库,支持更丰富的正则特性(如递归匹配、模糊匹配)