杰瑞科技汇

Python正则表达式如何精准提取字符串?

re 模块

Python 内置了 re 模块,它提供了对正则表达式的支持,所有操作都围绕这个模块展开。

Python正则表达式如何精准提取字符串?-图1
(图片来源网络,侵删)
  • re.match(): 从字符串的开头进行匹配,如果开头不匹配,则返回 None
  • re.search(): 扫描整个字符串,返回第一个匹配到的结果。
  • re.findall(): 扫描整个字符串,返回所有匹配到的结果,以列表形式。
  • re.finditer(): 扫描整个字符串,返回一个迭代器,每次迭代返回一个匹配到的对象(更节省内存)。
  • re.sub(): 替换字符串中匹配到的内容。

对于提取字符串,最常用的是 re.findall()re.finditer()


基础提取:re.findall()

这是最简单、最直接的方法,当你只需要获取匹配到的文本本身时,用它最方便。

语法

re.findall(pattern, string, flags=0)
  • pattern: 正则表达式模式。
  • string: 要搜索的字符串。
  • flags: 可选,用于控制正则表达式的匹配方式,如忽略大小写等。

示例 1:提取所有数字

import re
text = "我的电话是13812345678,办公室电话是010-87654321,备用号码是9988。"
# \d 匹配任意一个数字(0-9)
pattern = r"\d+"
# + 表示匹配前面的元素(这里是\d)一次或多次
matches = re.findall(pattern, text)
print(matches)
# 输出: ['13812345678', '010', '87654321', '9988']

示例 2:提取所有邮箱地址

import re
text = "联系我们:邮箱1是zhangsan@example.com,邮箱2是li.si@work.org。"
# \w 匹配单词字符(a-z, A-Z, 0-9, _)
# . 在正则中是特殊字符,用\.来匹配字面上的点号
# + 表示匹配前面的元素一次或多次
# [a-z]+ 匹配一个或多个小写字母
pattern = r"[\w\.-]+@[\w\.-]+\.\w+"
matches = re.findall(pattern, text)
print(matches)
# 输出: ['zhangsan@example.com', 'li.si@work.org']

进阶提取:使用捕获组

当你需要从匹配到的复杂模式中提取特定部分时,就需要用到捕获组,也就是括号 。

捕获组的工作原理是:正则表达式引擎会匹配整个模式,但同时会把括号内的内容单独保存起来。

Python正则表达式如何精准提取字符串?-图2
(图片来源网络,侵删)

示例 3:提取带区号的电话号码,并分离区号和号码

假设我们有这样的文本 "北京: 010-12345678",我们想分别得到区号 010 和号码 12345678

import re
text = "北京: 010-12345678, 上海: 021-87654321"
# () 创建了两个捕获组
# 第一个捕获组 (\d+) 用于匹配区号
# 第二个捕获组 (\d+) 用于匹配电话号码
pattern = r"(\d+)-(\d+)"
matches = re.findall(pattern, text)
print(matches)
# 输出: [('010', '12345678'), ('021', '87654321')]

解释

  • re.findall() 在有多个捕获组时,会返回一个元组的列表,每个元组对应一次完整匹配,元组中的元素就是各个捕获组匹配到的内容。

最强大的提取:re.finditer() + Match 对象

re.finditer() 结合 Match 对象是功能最全、信息最丰富的提取方式,当你不仅需要提取文本,还需要知道文本在原字符串中的位置(索引)时,这个方法是无价的。

Match 对象的常用方法

  • .group(): 返回匹配到的字符串。
  • .group(1), .group(2): 返回指定捕获组匹配到的字符串。
  • .groups(): 返回一个包含所有捕获组的元组。
  • .start(): 返回匹配的起始索引。
  • .end(): 返回匹配的结束索引。
  • .span(): 返回一个包含起始和结束索引的元组 (start, end)

示例 4:提取所有标题,并获取其位置

假设我们要从一个 HTML 片段中提取所有 <h1> 标签内的内容。

Python正则表达式如何精准提取字符串?-图3
(图片来源网络,侵删)
import re
html_text = """
<h1>欢迎来到我的网站</h1>
<p>这是一个段落。</p>
<h1>关于我们</h1>
<div>一些其他内容</div>
<h1>联系方式</h1>
"""
# <h1>(.*?)</h1>
# <h1> 匹配字面上的 "<h1>"
# (.*?) 这是一个捕获组
#   . 匹配除换行符外的任意字符
#   * 匹配前面的元素零次或多次
#   ? 使 * 变成“非贪婪模式”,即匹配尽可能少的字符,这样可以正确处理多个标签
# </h1> 匹配字面上的 "</h1>"
pattern = r"<h1>(.*?)</h1>"
# 使用 finditer 获取迭代器
for match in re.finditer(pattern, html_text):
    # 获取整个匹配的文本
    full_match = match.group(0)
    print(f"完整匹配: {full_match}")
    # 获取第一个捕获组的内容(也就是标题文本)text = match.group(1)
    print(f"提取标题: {title_text}")
    # 获取标题在原文中的位置
    start_pos = match.start()
    end_pos = match.end()
    print(f"位置: {start_pos} - {end_pos}")
    print("-" * 20)
# 输出:
# 完整匹配: <h1>欢迎来到我的网站</h1> 欢迎来到我的网站
# 位置: 0 - 19
# --------------------
# 完整匹配: <h1>关于我们</h1> 关于我们
# 位置: 33 - 43
# --------------------
# 完整匹配: <h1>联系方式</h1> 联系方式
# 位置: 62 - 72
# --------------------

编译正则表达式:re.compile()

如果你的同一个正则表达式要在循环中被多次使用(例如处理一个大文件的每一行),为了提高效率,可以预先将正则表达式编译成一个模式对象。

import re
# 预编译正则表达式
# 编译后的对象可以重复使用,效率更高
phone_pattern = re.compile(r"(\d{3})-(\d{8})")
text_lines = [
    "我的号码是 010-12345678",
    "请拨打 021-87654321 联系我们",
    "这个不是电话 123-456"
]
for line in text_lines:
    # 使用编译后的对象进行匹配
    match = phone_pattern.search(line)
    if match:
        # match 对象的方法和之前一样
        area_code = match.group(1)
        number = match.group(2)
        print(f"在行 '{line}' 中找到电话: 区号 {area_code}, 号码 {number}")
# 输出:
# 在行 '我的号码是 010-12345678' 中找到电话: 区号 010, 号码 12345678
# 在行 '请拨打 021-87654321 联系我们' 中找到电话: 区号 021, 号码 87654321

重要技巧与注意事项

原始字符串 r"..."

在 Python 中定义正则表达式时,强烈建议在字符串前加上 rr"\d+"

  • 原因:反斜杠 \ 在普通字符串中是转义字符(如 \n 换行),在正则表达式中,\ 也是特殊字符(如 \d 表示数字),如果不用原始字符串,你就需要写成 "\\d+",第一个 \ 转义了第二个 \,才能表示一个字面上的 \,使用原始字符串可以避免这种混乱。

贪婪模式 vs. 非贪婪模式

  • 贪婪模式(默认):, , , {m,n} 等量词会匹配尽可能多的字符。

    text = "<div>内容1</div><div>内容2</div>"
    pattern = r"<div>(.*)</div>" # 贪婪模式
    print(re.findall(pattern, text))
    # 输出: ['内容1</div><div>内容2']  # 错误!它匹配了从第一个 <div> 到最后一个 </div> 之间的所有内容
  • 非贪婪模式:在量词后加上 ,会匹配尽可能少的字符。

    text = "<div>内容1</div><div>内容2</div>"
    pattern = r"<div>(.*?)</div>" # 非贪婪模式
    print(re.findall(pattern, text))
    # 输出: ['内容1', '内容2'] # 正确!

    在处理嵌套标签或结构时,非贪婪模式 () 通常是更安全的选择。

常用正则表达式速查表

模式 描述 示例
匹配除换行符外的任意单个字符 a.c 匹配 "abc", "aac"
\d 匹配任意数字,等同于 [0-9] \d{3} 匹配 "123"
\D 匹配任意非数字字符 \D 匹配 "a", " "
\w 匹配“单词”字符,[a-zA-Z0-9_] \w+ 匹配 "hello", "Python3"
\W 匹配非“单词”字符 \W 匹配 "!", "@"
\s 匹配任意空白字符(空格, tab, 换行等) \s+ 匹配 " ", "\t\n"
\S 匹配任意非空白字符 \S 匹配 "a", "1"
[...] 定义一个字符集 [aeiou] 匹配任意一个元音字母
[^...] 定义一个反向字符集(不匹配其中的字符) [^0-9] 匹配任意非数字字符
匹配前面的元素 0 次或多次 ab*c 匹配 "ac", "abc", "abbc"
匹配前面的元素 1 次或多次 ab+c 匹配 "abc", "abbc" (不匹配 "ac")
匹配前面的元素 0 次或 1 次 ab?c 匹配 "ac", "abc"
{n} 精确匹配 n 次 \d{3} 匹配 3 个数字
{n,} 至少匹配 n 次 \d{2,} 匹配 2 个或更多数字
{n,m} 匹配 n 到 m 次 \d{2,4} 匹配 2, 3 或 4 个数字
捕获组,用于提取部分内容 (\d{3})-(\d{4})
^ 匹配字符串的开头 ^Hello 匹配 "Hello world"
匹配字符串的结尾 world$ 匹配 "Hello world"
\| 或(逻辑或) cat\|dog 匹配 "cat" 或 "dog"
你的需求 推荐方法 示例
提取所有匹配的简单文本 re.findall() re.findall(r"\d+", text)
从匹配项中提取特定部分 re.findall() + 捕获组 re.findall(r"(\w+)@(\w+)", text)
需要获取匹配项的位置信息 re.finditer() + Match 对象 for m in re.finditer(...): m.span()
在循环中重复使用同一个正则 re.compile() 预编译 pattern = re.compile(r"..."); pattern.search(text)

希望这份详细的指南能帮助你熟练掌握 Python 中使用正则表达式提取字符串的技巧!

分享:
扫描分享到社交APP
上一篇
下一篇