正则表达式(Regular Expression)是一种用于匹配、查找、替换文本的强大工具,它通过特定的字符和规则来描述文本模式。在 Python 中,正则表达式常被用于数据清洗、文本处理、网页爬虫等场景。比如,在数据清洗时,从杂乱的文本数据中提取出符合特定格式的手机号码、邮箱地址;网页爬虫中,从 HTML 页面里精准提取出链接、标题等信息 。可以说,掌握正则表达式,能让 Python 编程在处理文本相关任务时如虎添翼。
Python 正则表达式基础语法
(一)元字符
正则表达式中的元字符是具有特殊含义的字符,它们赋予了正则表达式强大的匹配能力。比如,. 可以匹配除换行符之外的任意单个字符。假如有字符串 text = "apple, orange, banana",想找出其中以 a 开头,后面跟任意一个字符,再以 e 结尾的单词,就可以使用正则表达式 a.e,使用 re.findall('a.e', text) 就能得到 ['apple'] 。
* 表示匹配前面的字符零次或多次。比如,要匹配字符串 text = "ca, cat, catt, cattt" 中以 c 开头,t 结尾,中间有任意多个 a 的单词 ,正则表达式 ca*t 就可以派上用场,re.findall('ca*t', text) 的结果是 ['ca', 'cat', 'catt', 'cattt'] 。
+ 匹配前面的字符一次或多次。在字符串 text = "1, 11, 111, 1111" 中找由多个 1 组成的数字,1+ 这个正则表达式就很合适,re.findall('1+', text) 会返回 ['1', '11', '111', '1111'] 。
? 匹配前面的字符零次或一次。比如在字符串 text = "color, colour" 中匹配这两个单词,使用 colou?r 这个正则表达式,re.findall('colou?r', text) 能得到 ['color', 'colour'] 。
^ 匹配字符串的开头 ,$ 匹配字符串的结尾。对于字符串 text = "hello world",如果想判断是否以 hello 开头,可以用 ^hello,用 re.match('^hello', text) 能得到匹配对象;如果判断是否以 world 结尾,使用 world$,re.search('world$', text) 也能得到匹配对象 。
(二)字符集
字符集是用 [] 表示的,它可以匹配其中包含的任意一个字符。比如,[abc] 能匹配 a、b、c 中的任意一个字符。在字符串 text = "apple, banana, cherry" 中找包含 a、b、c 这几个字母的单词,re.findall('[abc]\w+', text) 就能返回 ['apple', 'banana', 'cherry'] 。
范围字符集,像 [a-z] 表示匹配任意一个小写字母,[0-9] 匹配任意一个数字。在字符串 text = "a1b2c3" 中提取所有数字,re.findall('[0-9]', text) 会得到 ['1', '2', '3'] 。
取反字符集用 [^字符集] 表示,匹配不在字符集中的任意一个字符。在字符串 text = "apple, 123, banana" 中找不是数字的字符组成的单词,re.findall('[^0-9]\w+', text) 就能返回 ['apple', 'banana'] 。
(三)转义字符
在正则表达式中,反斜杠 \ 是转义字符,用于将特殊字符转义为普通字符,或者赋予普通字符特殊含义。例如,要匹配邮箱地址,邮箱地址中包含特殊字符 @ 和 . ,这时候就需要对它们进行转义。对于字符串 text = "
test@example.com" ,使用正则表达式 \w+@\w+\.\w+ 来匹配,re.search('\w+@\w+\.\w+', text) 就能找到这个邮箱地址。
再比如匹配网址,像 text = "
https://www.example.com" ,使用正则表达式 https?://\w+\.\w+\.\w+ ,这里对 :、/ 等特殊字符进行了处理,re.search('https?://\w+\.\w+\.\w+', text) 就能成功匹配到网址 。转义字符在处理复杂字符串匹配时非常关键,能帮助我们准确地定义匹配模式。
Python 的 re 模块
在 Python 中,re 模块是用于处理正则表达式的标准库,它提供了一系列函数来实现正则表达式的匹配、查找、替换等操作 。熟练掌握 re 模块,能极大地提升文本处理的效率和灵活性。
(一)re 模块常用函数
- re.search(pattern, string, flags=0):在字符串中搜索匹配正则表达式的第一个位置,返回一个匹配对象(Match Object),如果没有找到匹配项则返回 None 。比如在字符串 text = "I have 10 apples and 20 oranges" 中查找数字,result = re.search(r'\d+', text) ,result.group() 就能得到 10 。这里 r'\d+' 是正则表达式,表示匹配一个或多个数字,r 表示这是一个原始字符串,其中的反斜杠不会被转义。
- re.match(pattern, string, flags=0):从字符串的起始位置匹配正则表达式,如果匹配成功则返回一个匹配对象,否则返回 None 。例如 text = "Hello world" ,result = re.match(r'Hello', text) 能匹配成功返回匹配对象,而 result = re.match(r'world', text) 则会返回 None,因为 world 不在字符串起始位置。
- re.findall(pattern, string, flags=0):查找字符串中所有符合正则表达式的子串,并以列表形式返回。在字符串 text = "I have 10 apples and 20 oranges" 中找所有数字,results = re.findall(r'\d+', text) ,results 就是 ['10', '20'] 。
- re.sub(pattern, repl, string, count=0, flags=0):用于替换字符串中所有匹配正则表达式的子串。比如把字符串 text = "I have 10 apples" 中的数字替换成 NUMBER ,new_text = re.sub(r'\d+', 'NUMBER', text) ,new_text 就变成了 I have NUMBER apples 。count 参数可以指定替换的次数,默认是 0,表示全部替换。
- re.split(pattern, string, maxsplit=0, flags=0):根据正则表达式匹配的子串来分割字符串,返回一个列表。对于字符串 text = "1+2-3*4/5" ,result = re.split(r'[+\-*/]', text) ,result 是 ['1', '2', '3', '4', '5'] ,这里正则表达式 [+\-*/] 表示匹配 +、-、*、/ 这些运算符,maxsplit 参数可以指定最大分割次数。
(二)编译正则表达式
使用 re.compile(pattern, flags=0) 可以将正则表达式编译成一个 Pattern 对象,这样做有两个主要好处。一是提高效率,当需要多次使用同一个正则表达式进行匹配时,编译后的 Pattern 对象可以直接使用,避免了每次都对正则表达式进行解析,从而提升匹配速度 。比如在一个循环中要多次匹配邮箱地址,如果不编译,每次都要重新解析正则表达式;而编译后,只需要解析一次。二是方便复用,编译后的 Pattern 对象可以在不同的函数或代码块中重复使用。
下面是一个编译后使用 Pattern 对象进行匹配的示例:
import re
# 编译正则表达式
pattern = re.compile(r'\d+')
text1 = "I have 10 apples"
text2 = "There are 20 people"
# 使用编译后的Pattern对象进行匹配
result1 = pattern.findall(text1)
result2 = pattern.search(text2)
print(result1)
print(result2.group())
在这个例子中,先将正则表达式 r'\d+' 编译成 Pattern 对象,然后用这个对象分别对 text1 和 text2 进行匹配操作,这样代码更加简洁,也提高了执行效率 。
正则表达式的高级应用
(一)分组与捕获
在正则表达式中,分组是用圆括号 () 括起来的子表达式,它可以将多个字符视为一个整体进行操作,而捕获则是指将分组匹配到的内容提取出来 。比如,要从字符串中提取邮箱地址的用户名和域名部分,可以这样做:
import re
text = "My email is test@example.com"
pattern = r'(\w+)@(\w+\.\w+)'
match = re.search(pattern, text)
if match:
username = match.group(1)
domain = match.group(2)
print(f"Username: {username}, Domain: {domain}")
在这个例子中,(\w+) 和 (\w+\.\w+) 就是两个分组,match.group(1) 会返回第一个分组匹配到的内容,也就是用户名 test ;match.group(2) 返回第二个分组匹配到的内容,即域名
example.com 。通过分组和捕获,我们能更灵活地处理和提取文本中的特定信息 。
(二)贪婪与非贪婪模式
贪婪模式和非贪婪模式是正则表达式中量词的两种匹配方式 。贪婪模式下,量词会尽可能多地匹配字符,直到无法匹配为止;而非贪婪模式下,量词会尽可能少地匹配字符 。在 Python 中,默认是贪婪模式,在量词后面加上 ? 就可以开启非贪婪模式。
以匹配 HTML 标签内的内容为例,假设字符串 html = "<div class='content'>Hello <span>World</span></div>" ,使用贪婪模式的正则表达式 <div.*?>.*?</div> ,re.search('<div.*?>.*?</div>', html).group() 会匹配整个 <div class='content'>Hello <span>World</span></div> ,因为 .*? 虽然是非贪婪模式,但前面的 <div.*?> 是贪婪模式,它会一直匹配到最后一个 </div> 。
如果使用非贪婪模式的正则表达式 <div.*?>(.*?)</div> ,re.search('<div.*?>(.*?)</div>', html).group(1) 只会匹配到 Hello <span>World</span> ,这里两个 .*? 都是非贪婪模式,会尽可能少地匹配字符,只匹配到第一个 </div> 就停止 。在实际应用中,要根据具体需求选择贪婪模式还是非贪婪模式,以确保能准确地提取到所需的文本内容 。
(三)零宽断言
零宽断言是一种特殊的正则表达式,它用于匹配一个位置,而不是具体的字符,并且这个位置满足一定的条件,但该条件部分不会被包含在匹配结果中 。零宽断言分为正向先行断言 (?=exp) 、负向先行断言 (?!exp) 、正向后行断言 (?<=exp) 、负向后行断言 (?<!exp) 。
正向先行断言 (?=exp) 匹配一个位置,该位置之后的字符能匹配表达式 exp 。比如,要在字符串 text = "apple, banana, orange" 中找出以 a 开头,后面跟着 p 的单词,可以用 \b\w+(?=p) ,re.findall('\b\w+(?=p)', text) 会得到 ['appl'] ,这里 (?=p) 表示匹配的位置后面必须是 p ,但 p 不会被包含在匹配结果中 。
负向先行断言 (?!exp) 匹配一个位置,该位置之后的字符不能匹配表达式 exp 。在字符串 text = "apple, banana, orange" 中找不以 a 开头的单词,使用 \b(?!a)\w+ ,re.findall('\b(?!a)\w+', text) 能得到 ['banana', 'orange'] ,表示匹配的位置后面不能是 a 。
正向后行断言 (?<=exp) 匹配一个位置,该位置之前的字符能匹配表达式 exp 。例如,在字符串 text = "10 apples, 20 oranges" 中找前面是数字的单词,用 (?<=\d+)\s+\w+ ,re.findall('(?<=\d+)\s+\w+', text) 会得到 [' apples', ' oranges'] ,表示匹配的位置前面必须是数字 。
负向后行断言 (?<!exp) 匹配一个位置,该位置之前的字符不能匹配表达式 exp 。在字符串 text = "apple, banana, orange" 中找前面不是 b 的单词,使用 (?<!b)\b\w+ ,re.findall('(?<!b)\b\w+', text) 能得到 ['apple', 'orange'] ,表示匹配的位置前面不能是 b 。零宽断言在处理复杂的文本匹配场景时非常有用,能帮助我们更精确地定位和提取所需信息 。
实战案例分析
(一)数据清洗
在数据处理过程中,原始数据往往包含各种特殊字符和多余空格,这些噪声数据会影响后续的分析和建模。使用正则表达式可以高效地对其进行清洗 。
假设有这样一个字符串列表:
data = [" apple,123 ", "banana!@#456", "cherry 789 ", "date%^&012"]
要去除其中的特殊字符和多余空格,可以这样操作:
import re
cleaned_data = []
for item in data:
# 去除特殊字符,只保留字母和数字
clean_item = re.sub(r'[^a-zA-Z0-9]', '', item)
# 去除多余空格
clean_item = re.sub(r'\s+', '', clean_item)
cleaned_data.append(clean_item)
print(cleaned_data)
在这个例子中,re.sub(r'[^a-zA-Z0-9]', '', item) 使用正则表达式 [^a-zA-Z0-9] 匹配除了字母和数字之外的所有字符,并将其替换为空字符串,从而去除了特殊字符 。re.sub(r'\s+', '', clean_item) 则使用正则表达式 \s+ 匹配一个或多个空白字符(包括空格、制表符、换行符等),并将其替换为空字符串,达到去除多余空格的目的 。通过这样的处理,数据变得更加规范,便于后续的分析和处理 。
(二)网页爬虫
在网页爬虫中,提取 HTML 代码中的链接是一个常见任务 。假设我们要爬取一个网页中所有的链接,可以使用正则表达式来实现 。
首先,使用 requests 库获取网页的 HTML 内容:
import requests
import re
url = "https://example.com"
response = requests.get(url)
html_content = response.text
然后,使用正则表达式提取链接:
link_pattern = r'<a\s+href="(.*?)"'
links = re.findall(link_pattern, html_content)
print(links)
这里的正则表达式 <a\s+href="(.*?)" 用于匹配 <a> 标签中 href 属性的值 。\s+ 表示匹配一个或多个空白字符,(.*?) 以非贪婪模式匹配任意字符,直到遇到下一个双引号,这样就能准确地提取出链接 。
在实际应用中,可能会遇到一些问题。比如,有些链接可能是相对路径,需要将其转换为绝对路径 。可以通过以下方式解决:
from urllib.parse import urljoin
base_url = "https://example.com"
absolute_links = []
for link in links:
absolute_link = urljoin(base_url, link)
absolute_links.append(absolute_link)
print(absolute_links)
另外,HTML 页面结构可能比较复杂,正则表达式可能会匹配到一些不需要的内容 。这时候可以结合其他方法,比如使用 BeautifulSoup 库来解析 HTML,它能提供更方便、准确的方式来提取特定元素 。但正则表达式在一些简单场景下,依然是快速提取链接的有效工具 。
(三)日志分析
服务器日志文件记录了系统运行的各种信息,通过分析日志可以了解系统的运行状态、排查故障等 。使用正则表达式可以从日志文件中提取关键信息,为后续的分析和处理提供基础 。
假设日志文件的每一行格式如下:
2023-12-01 10:23:45 INFO This is an info log
要提取时间戳、日志级别、日志内容,可以这样做:
import re
log = '2023-12-01 10:23:45 INFO This is an info log'
pattern = r'(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+(\w+)\s+(.*)'
match = re.search(pattern, log)
if match:
timestamp = match.group(1)
log_level = match.group(2)
log_content = match.group(3)
print(f"Timestamp: {timestamp}, Log Level: {log_level}, Log Content: {log_content}")
在这个例子中,正则表达式 (\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+(\w+)\s+(.*) 被用于匹配日志行 。(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}) 匹配时间戳,(\w+) 匹配日志级别,(.*) 匹配日志内容 。通过 match.group(1)、match.group(2) 和 match.group(3) 分别提取出相应的信息 。这样,我们就可以对提取到的信息进行进一步的分析,比如统计不同日志级别的出现次数、根据时间戳分析系统的运行趋势等 。
Python 正则表达式是文本处理领域的强大工具,通过元字符、字符集、转义字符等基础语法,结合 re 模块的各种函数,能实现复杂的文本匹配、查找、替换等操作 。从基础的匹配单个字符,到高级的分组、断言应用,再到实际的数据清洗、网页爬虫、日志分析等场景,正则表达式都展现出了其高效和灵活的特性 。
对于想要深入学习 Python 正则表达式的读者,推荐一些在线教程,如菜鸟教程的正则表达式教程,它对基础语法和应用有详细讲解,适合初学者入门 ;还有正则表达式 30 分钟入门教程,能帮助快速掌握正则表达式的核心概念和用法 。书籍方面,《Python 核心编程》中有关于正则表达式的章节,深入剖析了 re 模块的原理和应用;《Python Cookbook》里也有很多正则表达式在实际场景中的应用案例,能帮助提升实践能力 。