热线电话:13121318867

登录
首页大数据时代Python re 提取中括号内内容:从入门到嵌套括号的完整解法
Python re 提取中括号内内容:从入门到嵌套括号的完整解法
2026-11-09
收藏

一、需求看起来简单,实际坑不少

"把文本里所有中括号 [] 中间的内容提取出来"——这是文本清洗里最常见的需求之一。日志里的 [INFO]、文献引用里的 [1][2]、模板占位符 [姓名]、配置里的 [section],都要这么处理。

但第一次写的人几乎都会踩坑。看一个真实例子:

import re
text = "错误 [E001] 发生,详见 [manual] 第3节"
print(re.findall("[.*]", text))

输出是 ['[E001] 发生,详见 [manual]'] —— 把两个括号之间的全部内容一起抓了,还顺手把中间的普通文字也带进来了。这就是典型的"以为 [] 就是字面中括号"的错误。

二、第一个必须搞清的概念:[] 在正则里不是括号

正则在 Python 里有一套自己的元字符,[ 和 ] 是其中两个,它们表示字符集:

  • [abc]:匹配 a、b、c 中任意一个字符
  • [0-9]:匹配任意一个数字
  • [^abc]:匹配除了 a、b、c 之外的任意一个字符

所以 [.*] 的真实含义是:匹配"点号或星号"这一个字符,后面跟一个 * 表示重复零次或多次……完全不是你想的意思。

2.1 要匹配字面中括号,必须转义

import re

text = "错误 [E001] 发生"
print(re.findall(r"[.*]", text))
# 输出: ['[E001]']

用 [ 和 ] 把中括号转义,它们才表示真正的字符"["和"]"。

注意:这里用了 raw string r"...",因为在普通字符串里 [ 会被 Python 当成无效转义。raw string 保证反斜杠原样传给正则引擎。

”

三、贪婪模式的陷阱:一次吃掉全部

继续看:

text = "[A] and [B] and [C]"
print(re.findall(r"[.*]", text))
# 输出: ['[A] and [B] and [C]']  ← 只有一个结果!

原因:.* 是贪婪匹配,它会尽可能往后吃,一直吃到最后一个 ]。

3.1 解法一:非贪婪模式

print(re.findall(r"[.*?]", text))
# 输出: ['[A]', '[B]', '[C]']  ← 正确

在 * 后面加一个 ?,变成 .*?,表示"尽可能少吃",遇到第一个 ] 就停。这是最常用、最直观的写法。

3.2 解法二:排除字符集

print(re.findall(r"[[^]]*]", text))
# 输出: ['[A]', '[B]', '[C]']

[^]]* 的含义是:匹配"除了 ] 之外的任意字符"零次或多次。因为中间内容不可能是 ],所以自然会在第一个 ] 处停下。

3.3 两种写法怎么选

写法 优点 缺点 适用
[.*?] 直观、易读 遇到跨行内容会失败 单行短文本
[[^]]*] 语义清晰、不跨行 写法略长 推荐,绝大多数场景
[.+] 能保证非空 仍然贪婪 不适合多括号
[(.+?)] 带分组,便于取内容 同非贪婪 需要分组时

推荐优先用 [[^]]*]:它的意图更明确——"括号内不可能有右括号",而且不会跨行误抓。

四、真正要的是括号里的内容,不是整个括号

上面 findall 返回的都是带括号的完整片段。如果只要中间内容,用捕获分组:

text = "[E001] 和 [manual] 都出现了"
print(re.findall(r"[([^]]*)]", text))
# 输出: ['E001', 'manual']

关键:在 [ 和 ] 之间加一对小括号 (...),形成捕获组。findall 在有分组时,返回的是分组内容而不是整体匹配。

对比一下:

正则 findall 返回
[[^]]*] ['[E001]', '[manual]'] 带括号
[([^]]*)] ['E001', 'manual'] 不带括号
[([^]]*)] 且有多个分组 返回元组列表

五、re 模块的几个函数该用哪个

5.1 re.findall:要全部结果

re.findall(r"[([^]]*)]", text)   # 返回列表,全部匹配

最常见,适合"把所有括号内容都提取出来"。

5.2 re.finditer:要位置信息

for m in re.finditer(r"[([^]]*)]", text):
    print(m.group(1), m.start(), m.end())

finditer 返回迭代器,每个 match 对象能拿到 group()、start()、end()、span()。需要替换、定位、按位置处理时用这个。

5.3 re.search:只要第一个

m = re.search(r"[([^]]*)]", text)
if m:
    print(m.group(1))   # 只取第一个匹配

注意 search 返回的 match 对象,必须用 group(1) 取值,直接 print 会得到 match 对象描述。

5.4 re.match:只从开头匹配

re.match(r"[([^]]*)]", "[E001] 发生")   # 匹配成功
re.match(r"[([^]]*)]", "错误 [E001]")   # 返回 None,因为不是开头

match 只从字符串起始位置找,不扫描全文。用在"确认字符串是否以 [xxx] 开头"的场景。

5.5 函数选择速查

需求 函数 返回值
提取全部 findall 列表 / 元组列表
需要位置、逐个处理 finditer match 迭代器
只找第一个 search match 或 None
只从开头匹配 match match 或 None
完全匹配整串 fullmatch match 或 None
替换 sub 新字符串

六、几个高频实战场景

6.1 提取文献引用编号

text = "该方法已有研究[1],后续改进见[2][3]。"
nums = re.findall(r"[(d+)]", text)
# ['1', '2', '3']
nums = [int(n) for n in nums]
# [1, 2, 3]

关键:(d+) 限定只能数字,避免抓到其他方括号内容。

6.2 提取日志级别

log = "2026-11-09 10:23:11 [INFO] 服务启动成功"
level = re.search(r"[([A-Z]+)]", log).group(1)
# 'INFO'

6.3 提取键值对形式的占位符

text = "尊敬的 [姓名],您的订单 [订单号] 已发货"
result = re.findall(r"[([^]]+)]", text)
# ['姓名', '订单号']

这里用 + 而不是 *,是为了过滤掉空括号 []。

6.4 一次提取多组结构

text = "[name:张三][age:28][city:北京]"
pairs = re.findall(r"[([^:]]+):([^]]+)]", text)
# [('name', '张三'), ('age', '28'), ('city', '北京')]

两个分组,返回元组列表,可以直接转字典:

dict(pairs)   # {'name': '张三', 'age': '28', 'city': '北京'}

6.5 中文全角括号也要兼容

中文文本里除了 [],还常有全角 [] 和书名号式 【】:

text = "【重要】请阅读 [附件1] 和 [备注]"
# 同时匹配三种
re.findall(r"[[[【]([^]]】]*)[]]】]", text)
# ['重要', '附件1', '备注']

这里用字符集 [[[【] 把三种左括号都放进候选,右括号同理。

七、跨行内容怎么办

默认情况下 . 不匹配换行符,[^]] 也不匹配换行。如果括号内容可能跨行:

text = """[说明:这是
多行内容]"""


# 方案一:DOTALL 让 . 匹配换行
re.findall(r"[(.*?)]", text, re.S)
# ['说明:这是n多行内容']

# 方案二:显式允许换行
re.findall(r"[([^]]*)]", text, re.S)   # 注意:需要 . 配合,见下

⚠️ 注意:[^]] 这个字符集本身默认就不排除换行(它只排除 ]),所以 [([^]]*)] 在跨行时本来就能匹配,不需要 re.S。真正需要 re.S 的是用 . 的写法。

re.findall(r"[([^]]*)]", text)   # 跨行也能匹配,因为 [^]] 包含 n

这是 [^]] 写法相对 .*? 的一个隐性优势。

八、真正的难点:嵌套中括号

正则表达式是基于有限状态自动机的,它天生无法处理任意深度的嵌套结构。这是理论限制,不是写法问题。

比如要提取 [a[b]c] 里的完整内容,正则会在这里翻车:

text = "外层[内层[A]结束]"
re.findall(r"[([^]]*)]", text)
# ['内层[A']  ← 错了,第一个 ] 就停了

8.1 如果嵌套层级固定(比如只有一层)

可以针对性写:

# 只匹配不含任何括号的内容
re.findall(r"[([^[]]*)]", text)
# ['A']  ← 只拿到最内层,但外层内容丢了

8.2 用计数器手动扫描(通用解法)

def extract_brackets(s):
    """提取所有最外层中括号内容,支持嵌套"""
    results = []
    depth = 0
    start = -1
    for i, ch in enumerate(s):
        if ch == '[':
            if depth == 0:
                start = i + 1
            depth += 1
        elif ch == ']':
            depth -= 1
            if depth == 0 and start >= 0:
                results.append(s[start:i])
                start = -1
    return results

extract_brackets("外层[内层[A]结束],第二组[B[C[D]]]")
# ['内层[A]结束', 'B[C[D]]']

这个方案用栈/计数器维护深度,能处理任意嵌套,是正则解决不了的问题的正经解法。

8.3 第三方库方案

  • regex 模块(pip install regex)支持递归正则 (?R),但语法复杂、性能差
  • pyparsing:专业的解析器库,处理嵌套结构更规范

实际工程里,遇到嵌套建议直接上计数器或 pyparsing,不要在正则上死磕。

九、常见错误清单

9.1 忘记转义

re.findall("[.*]", text)      # ❌ 把 [] 当字符集
re.findall(r"[.*]", text)   # ✅

9.2 用贪婪模式抓多组

re.findall(r"[.*]", "[A][B]")     # ❌ ['[A][B]']
re.findall(r"[.*?]", "[A][B]")    # ✅ ['[A]', '[B]']

9.3 该用分组取内容时没用

re.findall(r"[[^]]*]", text)    # 拿到带括号的
re.findall(r"[([^]]*)]", text)  # 拿到纯内容

9.4 空括号被算进去

re.findall(r"[([^]]*)]", "[] [A]")    # ['', 'A']
re.findall(r"[([^]]+)]", "[] [A]")    # ['A']  ✅ 用 + 排除空

9.5 忽略全角括号

中文资料里 []、【】 很常见,只写 [] 会漏抓。

9.6 直接用 search 就 print

m = re.search(r"[([^]]*)]", text)
print(m)          # ❌ <re.Match object; span=(..), match='[A]'>
print(m.group(1)) # ✅ 'A'

9.7 没处理 None

re.search(r"...", text).group(1)   # ❌ 没匹配到会 AttributeError

应先判断 if m: 再取。

十、完整可复用的工具函数

import re

def extract_in_brackets(text, keep_empty=False, pattern=r"[([^]]*)]"):
    """
    提取中括号内的内容

    参数:
        text: 输入字符串
        keep_empty: 是否保留空括号
        pattern: 自定义正则(默认匹配 [])
    返回:
        内容列表
    """

    matches = re.findall(pattern, text)
    if not keep_empty:
        matches = [m for m in matches if m.strip()]
    return matches

# 使用
text = "错误 [E001],参考 [manual],空括号 [],中文 【重要】"
print(extract_in_brackets(text))
# ['E001', 'manual']

# 兼容全角
print(extract_in_brackets(text, pattern=r"[[[【]([^]]】]*)[]]】]"))
# ['E001', 'manual', '重要']

十一、总结

提取中括号内容这件事,核心就三句话:

  1. [] 在正则里是字符集,要匹配字面括号必须转义成 [ 和 ]
  2. .* 是贪婪的,多组括号必须用 .*? 或 [^]]* 两种写法之一
  3. 要中间内容而不是整段,就在括号间加上捕获分组 (...)

进一步的三条经验:

  • 优先用 [([^]]*)] 这种写法,语义清晰、不会跨行误抓
  • 要排除空括号就把 * 换成 +
  • 嵌套结构不要用正则硬碰,上计数器或 pyparsing

正则不难,难的是记住 [] 有自己的一套语法——这是所有初学者最容易栽的第一个坑。

推荐学习书籍 《CDA一级教材》适合CDA一级考生备考,也适合业务及数据分析岗位的从业者提升自我。完整电子版已上线CDA网校,累计已有10万+在读~ !

免费加入阅读:https://edu.cda.cn/goods/show/3151?targetId=5147&preview=0

数据分析师资讯
更多

OK
客服在线
立即咨询
客服在线
立即咨询