🔎大家好,我是ZTLJQ,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流
📝个人主页-ZTLJQ的主页
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝📣系列果你对这个系列感兴趣的话
专栏 - Python从零到企业级应用:短时间成为市场抢手的程序员
✔说明⇢本人讲解主要包括Python爬虫、JS逆向、Python的企业级应用
如果你对这个系列感兴趣的话,可以关注订阅哟👋
为什么需要正则表达式?
想象一下,你需要完成以下任务:
- 验证用户输入的电话号码格式是否正确。
- 从一篇包含成百上千个单词的文章中,找出所有以“http”或“https”开头的网址链接。
- 将一段文本中所有的日期格式从
MM/DD/YYYY统一转换为YYYY-MM-DD。 - 清理日志文件,移除其中所有的IP地址。
使用传统的字符串方法(如find(),split(),replace())来实现这些需求,代码会变得极其复杂和脆弱。例如,要找网址,你可能需要写一个循环,检查每个子串是否以“http”开头,然后找到下一个空格...
正则表达式(Regex)提供了一种强大的“模式”语言。你可以用一个紧凑的字符串(称为“模式”)来描述你想要匹配的文本规则,然后让引擎去自动搜索、替换或分割文本。
Python通过内置的re模块提供了完整的正则表达式支持。
第一部分:核心概念与基本语法
1.1 基础元字符
| 元字符 | 含义 | 示例 |
|---|---|---|
. | 匹配除换行符外的任意单个字符 | a.c匹配 "abc", "a2c", "a c" |
^ | 匹配字符串的开始 | ^Hello匹配 "Hello World" 的开头 |
$ | 匹配字符串的结束 | world$匹配 "Hello world" 的结尾 |
* | 匹配前面的字符零次或多次 | ab*c匹配 "ac", "abc", "abbc", "abbbc"... |
+ | 匹配前面的字符一次或多次 | ab+c匹配 "abc", "abbc",但不匹配 "ac" |
? | 匹配前面的字符零次或一次 | ab?c匹配 "ac", "abc",但不匹配 "abbc" |
{m,n} | 匹配前面的字符m到n次 | a{2,4}匹配 "aa", "aaa", "aaaa" |
\ | 转义字符,将特殊字符变为普通字符 | \.匹配字面量的点号 "." |
1.2 字符集 ([]) 与预定义字符类
字符集
[...]: 匹配方括号内的任意一个字符。[aeiou]: 匹配任何一个元音字母。[a-z]: 匹配任何一个小写字母。[0-9A-F]: 匹配任何十六进制数字。[^0-9]:^在开头表示否定,匹配任何非数字字符。
预定义字符类 (更简洁):
\d: 等价于[0-9],匹配任何数字。\D: 等价于[^0-9],匹配任何非数字。\w: 匹配任何“单词字符”,等价于[a-zA-Z0-9_]。\W: 匹配任何“非单词字符”。\s: 匹配任何空白字符(空格、制表符\t、换行符\n等)。\S: 匹配任何非空白字符。
1.3 分组 (()) 与捕获
圆括号()用于创建分组,可以对分组应用量词,并且可以“捕获”匹配到的文本以便后续使用。
import re # 使用分组和量词 pattern = r"(ab)+" # 匹配一个或多个连续的 "ab" text = "ababab cd ab" match = re.search(pattern, text) if match: print(f"找到: {match.group()}") # 找到: ababab # group(0) 或 group() 是整个匹配 # group(1) 是第一个分组的内容 # 注意: 这里只有一个分组 (ab),所以 group(1) 返回最后一次成功的捕获 "ab"注意:
re.search()只返回第一个匹配项。re.findall()返回所有匹配项的列表。
第二部分:re模块实战案例
我们将通过一系列实际案例来展示re模块的强大功能。
案例一:验证与匹配 (re.match,re.search,re.findall)
import re # ---- 验证邮箱地址 ---- def is_valid_email(email): """ 一个简化的邮箱验证模式。 注意:真正的邮箱验证非常复杂,这里仅作演示。 """ pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' return re.match(pattern, email) is not None print(is_valid_email("user@example.com")) # True print(is_valid_email("invalid.email")) # False # ---- 从文本中提取所有邮箱 ---- text = """ Contact us at support@company.com or sales@company.org. For jobs, email hr@company.io. """ # findall 返回所有匹配的字符串 emails = re.findall(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', text) print(emails) # ['support@company.com', 'sales@company.org', 'hr@company.io'] # ---- 查找所有以 http/https 开头的URL ---- web_text = "Visit https://www.python.org or http://example.com for more info." urls = re.findall(r'https?://[^\s]+', web_text) # \S+ 匹配非空白字符直到遇到空白 print(urls) # ['https://www.python.org', 'http://example.com']解析:
r'': 使用原始字符串(raw string),避免反斜杠被Python解释器转义。https?:?表示s是可选的,因此能匹配"http"和"https"。[^\s]+:[^...]表示否定字符集,[^\s]匹配任何非空白字符,+表示一个或多个。
案例二:搜索与替换 (re.sub)
这是最常用的功能之一。
import re # ---- 格式化日期:将 MM/DD/YYYY 转换为 YYYY-MM-DD ---- date_text = "The event is on 12/25/2023 and 01/01/2024." # 使用分组捕获 # (\d{2}) 捕获月份,(\d{2}) 捕获日期,(\d{4}) 捕获年份 pattern = r'(\d{2})/(\d{2})/(\d{4})' replacement = r'\3-\1-\2' # \3是年份,\1是月份,\2是日期 formatted_text = re.sub(pattern, replacement, date_text) print(formatted_text) # The event is on 2023-12-25 and 2024-01-01. # ---- 移除文本中的所有HTML标签 ---- html_text = "<p>This is <b>bold</b> and <i>italic</i> text.</p>" # <[^>]+> 匹配一个左尖括号<,然后是非>的任意字符一个或多次,最后是一个右尖括号> clean_text = re.sub(r'<[^>]+>', '', html_text) print(clean_text) # This is bold and italic text. # ---- 将多个连续空格替换为单个空格 ---- messy_text = "Too many spaces here." cleaned_spaces = re.sub(r'\s+', ' ', messy_text).strip() print(cleaned_spaces) # Too many spaces here.解析:
re.sub(pattern, replacement, string)会将string中所有匹配pattern的部分替换为replacement。在replacement中,可以用\1,\2...来引用前面分组捕获的内容。
案例三:分割字符串 (re.split)
比str.split()更强大,可以使用复杂的模式作为分隔符。
import re # ---- 使用多种分隔符分割 ---- text = "apple,banana;cherry|date" # [,;|] 是一个字符集,匹配 , 或 ; 或 | fruits = re.split(r'[,;|]', text) print(fruits) # ['apple', 'banana', 'cherry', 'date'] # ---- 分割句子,保留标点符号 ---- sentence = "Hello world! How are you? I'm fine." # (?<=[.!?]) 是一个“后行断言”,意思是“在 . 或 ! 或 ? 之后” # 这样分割时,分隔符(标点)会被保留在前面的元素中 parts = re.split(r'(?<=[.!?])\s*', sentence) print(parts) # ['Hello world!', "How are you?", "I'm fine."]解析:
(?<=...)是“正向后行断言”(positive lookbehind assertion)。它确保了分割只发生在句号、感叹号或问号之后的空白处,而不会吃掉这些标点符号。
第三部分:高级特性与技巧
3.1 编译模式 (re.compile)
如果你要重复使用同一个正则表达式,将其编译成一个Pattern对象可以提高效率。
import re # 编译一个模式,用于匹配有效的IPv4地址段(简化版) ip_pattern = re.compile(r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b') text_with_ips = "Server1: 192.168.1.1, Server2: 10.0.0.5, Invalid: 999.999.999.999" # 多次使用编译后的模式 valid_ips = [] for ip in ip_pattern.findall(text_with_ips): # 进一步验证每个数字是否在0-255之间 if all(0 <= int(part) <= 255 for part in ip.split('.')): valid_ips.append(ip) print(valid_ips) # ['192.168.1.1', '10.0.0.5']解析:
re.compile()返回一个Pattern对象,它拥有search(),findall(),sub()等方法。
3.2 标志 (Flags)
可以改变正则表达式的匹配行为。
import re text = "Hello\nWorld\nHELLO" # ---- 忽略大小写 (re.IGNORECASE 或 re.I) ---- matches = re.findall(r'hello', text, re.IGNORECASE) print(matches) # ['Hello', 'HELLO'] # ---- 让 ^ 和 $ 匹配每一行的开始和结束 (re.MULTILINE 或 re.M) ---- # 默认情况下,^ 和 $ 只匹配整个字符串的开始和结束 line_matches = re.findall(r'^\w+', text, re.MULTILINE) # 找出每行的第一个单词 print(line_matches) # ['Hello', 'World', 'HELLO'] # ---- 让 . 匹配包括换行符在内的所有字符 (re.DOTALL 或 re.S) ---- multiline_text = "Line 1\nLine 2\nLine 3" # .* 默认不能跨行 single_line_match = re.search(r'Line 1.*Line 3', multiline_text) # None # 使用 re.DOTALL multi_line_match = re.search(r'Line 1.*Line 3', multiline_text, re.DOTALL) if multi_line_match: print("Found across lines!") # Found across lines!3.3 非贪婪匹配
默认情况下,*,+,?,{m,n}是“贪婪”的,它们会尽可能多地匹配字符。
import re text = "<div>Content 1</div><div>Content 2</div>" # 贪婪匹配:会匹配从第一个 <div> 到最后一个 </div> 的所有内容 greedy_pattern = r'<div>.*</div>' greedy_match = re.search(greedy_pattern, text) if greedy_match: print("Greedy:", greedy_match.group()) # Greedy: <div>Content 1</div><div>Content 2</div> # 非贪婪匹配:在量词后加 ?,会尽可能少地匹配 non_greedy_pattern = r'<div>.*?</div>' non_greedy_matches = re.findall(non_greedy_pattern, text) print("Non-greedy:", non_greedy_matches) # Non-greedy: ['<div>Content 1</div>', '<div>Content 2</div>']解析:
.*?中的?让*变成非贪婪(或“懒惰”)模式。它一旦找到第一个</div>就停止匹配,而不是继续寻找最后一个。
第四部分:最佳实践与陷阱
- 保持简单: 正则表达式很容易变得过于复杂而难以维护。如果一个简单的
str方法就能解决,就不要用正则。 - 充分测试: 正则表达式是“写时容易,读时难”。务必用各种边界情况测试你的模式。
- 使用原始字符串: 总是在正则表达式前加上
r,如r'\d+',避免反斜杠问题。 - 善用在线工具: 使用 regex101.com 或 pythex.org 等在线工具来编写和调试正则表达式,它们提供实时的匹配结果和解释。
- 性能考量: 复杂的正则表达式,尤其是涉及大量回溯(backtracking)的,可能会很慢。对于简单的查找,
in或str.find()通常更快。 - 不要过度依赖: 试图用一个正则表达式解析HTML或JSON通常是错误的选择。应使用专门的解析器(如
BeautifulSoup或json模块)。
结语
正则表达式是一把双刃剑。它功能强大,能让你在几行代码内完成复杂的文本处理任务;但它也晦涩难懂,写出的“天书”会让未来的自己都头疼。
通过本篇博客的学习,你应该已经掌握了:
- 正则表达式的核心语法和元字符。
- 如何使用
re模块进行搜索、匹配、替换和分割。 - 编译模式、标志和非贪婪匹配等高级技巧。
- 实际应用场景和最佳实践。