str.extract()提取首匹配项并返回DataFrame,str.extractall()获取所有匹配项并返回MultiIndex DataFrame,str.findall()则返回每行匹配列表但不支持结构化解析。

用 str.extract() 提取第一个匹配组
当文本列中每行最多只关心一个匹配结果(比如从日志里抽 IP、从 URL 里抽域名),str.extract() 是最直接的选择。它返回一个 DataFrame,每列对应正则中的一个命名或未命名捕获组。
注意:它只取**第一个匹配项**,即使某行文本能匹配多次,也只返回首次出现的组内容。
- 必须用括号包裹要提取的部分,否则返回全空(
NaN) - 推荐用命名组(
(?P<name>...)),返回列名清晰,后续处理不易出错 - 如果正则没匹配上,对应位置是
NaN,不是空字符串
df['ip'] = df['log'].str.extract(r'(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
用 str.extractall() 获取所有匹配项
当一行文本可能含多个目标(比如一段话里有多个邮箱、多个电话号码),就得用 str.extractall()。它返回一个 MultiIndex DataFrame,索引包含原始行号和匹配序号。
关键点在于:结果不是和原 DataFrame 对齐的 Series,而是需要额外聚合(如 join、list 化)才能塞回原表。
立即学习“Python免费学习笔记(深入)”;
- 默认返回的是
pd.Series或pd.DataFrame,索引是(original_index, match_number) - 常用后续操作:
.groupby(level=0)['email'].apply(list)聚合成每行一个列表 - 如果正则没命名组,列名是
0;有命名组则列名即组名
emails = df['text'].str.extractall(r'([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})')
df['emails'] = emails.groupby(level=0)[0].apply(list)
为什么不用 str.findall()?它更轻量但功能受限
str.findall() 返回每行一个匹配字符串列表,不依赖捕获组,适合快速捞出所有子串,但没法做结构化解析(比如同时抽用户名和域名)。
它比 extractall 快,内存开销小,但一旦正则里用了多个括号(比如 (\d+)-(\w+)),返回的是元组列表,不是扁平字符串,容易引发后续类型错误。
- 纯匹配场景(只要值,不要字段名)优先考虑
findall - 返回结果是 list of strings —— 如果正则有多个捕获组,就变成 list of tuples,需额外解包
- 不能直接生成多列,想拆成多字段得配合
pd.DataFrame()构造再合并
df['dates'] = df['content'].str.findall(r'\d{4}-\d{2}-\d{2}')
常见报错和兼容性注意点
正则本身写错、Pandas 版本差异、空值处理不当,是三个最高频的卡点。
-
ValueError: pattern contains no capture groups:忘加括号了,extract和extractall强制要求至少一个捕获组 -
AttributeError: Can only use .str accessor with string values:列里混了None、int或float,先用df['col'] = df['col'].astype(str)统一转字符串 - Pandas 1.4+ 默认启用
regex=True,但老版本(如 1.0)对某些 Unicode 模式支持弱,遇到中文路径或 emoji 建议显式加flags=re.U
真正麻烦的不是语法,而是原始文本的脏乱程度——比如换行符干扰、HTML 实体残留、编码不一致。正则前先做 str.replace('\n', ' ')、str.strip() 往往比调正则本身更省时间。


















