正则表达式的核心哲学

正则表达式本质上是一种描述文本模式的特殊语法。它独立于编程语言,你在 Python、Java、Scala 甚至是 Linux 的 grep 命令里看到的逻辑几乎是一通百通的。

  • 普通搜索:我要找“xxx”。(只能找固定的词)

  • 正则搜索:我要找“一个以 T 开头,后面跟着几个小写字母,最后以 i 结尾的词”。(能找一类符合规则的词)

元字符:正则的建筑材料

元字符是构成正则最基础的符号,就像是建筑里的砖块。

符号 含义 形象理解
. 匹配除换行符以外的任意单个字符 万能代金券
\d 匹配一个数字 (0-9) "d" 代表 digit
\D 匹配一个非数字 大写字母通常表示“反义”
\w 匹配字母、数字或下划线 "w" 代表 word
\s 匹配空白字符(空格、制表符等) "s" 代表 space
^ 匹配字符串的开头 守门员
$ 匹配字符串的结尾 扫尾员
[abc] 匹配 a、b 或 c 中的任意一个 选秀池
[^abc] 匹配除了 a、b、c 以外的字符 排除名单

量词:控制出现的次数

量词决定了前面的字符“连续出现几次”,这在匹配手机号、时间戳时非常有用。

符号 含义 例子
* 出现 0 次或多次 \d* (可能有数字,也可能没有)
+ 出现 1 次或多次 \d+ (至少得有一个数字)
? 出现 0 次或 1 次 https? (匹配 http 或 https)
{n} 精确出现 n 次 \d{11} (匹配手机号)
{n,m} 出现 n 到 m 次 \w{6,12} (匹配 6-12 位密码)

Python re 模块实战

Python 通过内置的 re 模块来支持正则。作为大数据开发者,你需要熟练掌握这几个核心方法。

import re
​
# 待处理的原始字符串
text = "Timi's phone is 138-1234-5678, his ID is T888."
​
# 1. re.search(): 在全文找第一个匹配项
# pattern: 匹配规则 (str 类型,建议加 r 前缀防止转义)
# string: 待搜字符串
# 返回值: Match 对象(找到)或 None(没找到)
result = re.search(r"\d{3}-\d{4}-\d{4}", text)
if result:
    # .group() 用于提取匹配到的字符串内容
    print(f"搜到了电话: {result.group()}")
​
# 2. re.findall(): 找全文所有的匹配,直接返回列表
## 注意事项: 全文直接进内存!! 大数据改用 finditer
# 适合提取日志里所有的报错代码或 IP 地址
all_nums = re.findall(r"\d+", text)
print(f"提取的所有数字部分: {all_nums}") # ['138', '1234', '5678', '888']
​
# 3. re.match(): 仅匹配首字符
fisrt = re.match(r'/w','text')
print(f'首字母是: {first.group()}')
​
# 4. re.sub(): 替换(数据脱敏、文本清洗常用)
# replacement: 替换后的内容
# 把电话中间四位换成 ****
# \1 和 \2 代表引用后面括号里捕获的分组
secret_text = re.sub(r"(\d{3})-\d{4}-(\d{4})", r"\1-****-\2", text)
print(f"脱敏后: {secret_text}")
​

贪婪与非贪婪:面试重灾区

这是正则里最容易翻车的地方,也是 AI 爬虫处理 HTML 时的必考点。

  • 贪婪模式(默认):尽可能多地匹配。

    • 正则:a.*b

    • 文本:axxxbxxxb

    • 结果:axxxbxxxb(一口气吞到最后一个 b)

  • 非贪婪模式(加个 ?:见好就收,尽可能少地匹配。

    • 正则:a.*?b

    • 结果:axxxb(遇到第一个 b 就停下)

AI 场景应用:在提取标签内容时(如 <div>内容</div>),如果不加 ? 变成非贪婪,你可能会把整个网页的所有 div 连在一起一次性全抓出来。

分组与捕获:精准手术刀

用小括号 () 可以把正则分成几个小组,方便我们精准提取。

log = "2023-10-01 ERROR: Connection Timeout"
​
# 提取日期和错误等级
# (?P<name>...) 是给小组起名字,让代码更易读
pattern = r"(?P<date>\d{4}-\d{2}-\d{2}) (?P<level>\w+):"
match = re.search(pattern, log)
if match:
    # 通过名字提取,比通过索引 [1] 更有工程美感
    print(match.group("date"))  # 2023-10-01
    print(match.group("level")) # ERROR

大数据与 AI 场景下的实战

日志清洗与过滤

当你流式读取那个 10G 的文件时,可以用正则快速过滤掉无效信息。

def log_filter(file_path):
    # 预编译正则:在大规模循环中,提前编译速度快 10% 以上
    # re.compile 会生成一个正则对象,多次使用时效率极高
    error_pattern = re.compile(r"ERROR|CRITICAL")
​
    with open(file_path, 'r', encoding='utf-8') as f:
        for line in f:
            if error_pattern.search(line):
                yield line.strip()

AI 语料清洗

在给大模型(LLM)喂数据前,需要剔除 HTML 标签、特殊符号或隐私信息。

raw_html = "<p>Hello <b>Timi</b>!</p>"
# 剔除所有 HTML 标签:匹配 < 开头,中间非 > 的内容,> 结尾
clean_text = re.sub(r"<[^>]+>", "", raw_html)
print(clean_text) # Hello Timi!

性能优化与面试避坑指南

关于 ReDoS(正则表达式拒绝服务攻击)

面试官可能会问:正则会有性能问题吗?

  • 回答:会有。如果编写了糟糕的正则(比如嵌套了大量的 +*,如 (a+)+$),会导致回溯(Backtracking)次数指数级增长,消耗大量 CPU。在大数据处理中,我会优先使用 re.compile() 预编译模式,并尽量使用非贪婪匹配确定的字符集来减少回溯风险。

原生字符串的使用

在写正则时,永远记得在字符串前加 r(如 r"\d")。否则 Python 的转义字符(如 \n 代表换行)会和正则的转义冲突。

替代方案

正则虽然强大,但可读性较差。如果能用内置的 str.startswith()str.endswith() 或者 json.loads() 解决的问题,就不要动用正则这门“大炮”,这体现了你对代码可维护性的考量。

 

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐