在做表格数据处理时经常遇到一种需求在一张表里同时筛选多个关键词只要某一列包含其中任意一个关键词就把这一行保留下来。很多人第一反应是手动一列一列筛、一个词一个词查再复制粘贴结果。数据量小还好一旦表格有几千行、关键词有十几个手动操作不仅容易漏而且效率极低。这篇文章就来系统梳理“全表同时筛选多个关键词”的完整方案以 Python pandas 为核心覆盖单列多关键词筛选、全表多关键词筛选、关键词自动转义、空值处理、大小写处理等场景。文中所有代码都可以直接复制运行看完之后你可以把方案封装成自己的工具函数以后遇到同类需求一次解决。适合读者经常处理表格数据、Excel 筛选效率太低、想用 Python 提升数据处理效率的同学以及刚接触 pandas 想找实战案例的新手。1. 背景与核心概念1.1 什么是全表多关键词筛选全表多关键词筛选指的是在一张二维表结构中不限定某一列而是对整个表格范围进行检索只要某一个关键词出现在任意一列的任意一个单元格中就认为这一行数据与关键词匹配。举个例子假设有一张客户反馈表包含“客户姓名”“反馈内容”“处理人”三列。现在需要筛选出所有提到“退款”“投诉”“差评”这三个词的记录。这三个词可能出现在“反馈内容”里也可能出现在“处理人”备注里甚至客户姓名里。手动操作时需要分别对每一列执行包含筛选再把多列结果合并逻辑繁琐而且多个关键词之间是“或”关系时特别容易漏掉数据。pandas 提供了字符串向量化操作可以在一行代码内完成这种多关键词匹配不需要写 for 循环也不需要一个词一个词地查。1.2 适用的典型场景这种筛选方式在以下场景中非常常见日志分析从大量日志行中筛出包含 error、exception、failed 的记录。客户反馈分类按关键词归类投诉、建议、咨询内容。商品信息清洗筛选描述中命中多个品牌词或品类词的商品。运营数据质检快速定位异常备注、特殊标记的记录。多表合并前的脏数据排查找出包含缺失标记、异常字符的行。1.3 常用的实现思路对比方案优点缺点Excel 自动筛选逐列操作不需要写代码多列、多关键词时效率低容易遗漏Excel 高级筛选支持条件区域配置复杂条件区域维护麻烦pandas 正则筛选灵活、可复用、支持复杂条件需要基础正则知识pandas 逐行遍历判断逻辑直观数据量大时性能差这篇文章重点讲 pandas 正则筛选方案因为在“多关键词 全表范围”这个组合条件下它的代码最短、可维护性最好、执行效率也最高。2. 环境准备与数据说明2.1 运行环境本文示例代码基于以下环境操作系统Windows / macOS / Linux 均可Python3.8 及以上版本pandas1.3 及以上版本如果你的电脑还没安装 pandas可以在命令行中执行pip install pandas如果你使用的是 Anacondapandas 通常已经预装。可以通过下面命令确认版本python -c import pandas; print(pandas.__version__)版本不同可能导致个别 API 行为有差异但本文用到的方法在 pandas 1.x 各版本中表现一致可以放心使用。2.2 示例数据结构为了方便演示先构造一张模拟表内容是一份简化的工单记录import pandas as pd data { 单号: [A001, A002, A003, A004, A005], 客户: [张三, 李四, 王五, 赵六, 孙七], 反馈内容: [ 产品使用过程中遇到了闪退问题, 物流配送速度很快服务态度好, 申请退款希望尽快处理, 客服电话一直打不通投诉, 收到货后发现有破损差评 ], 处理人: [小明, 小红, 小刚, 小丽, 小华] } df pd.DataFrame(data) print(df)运行结果单号 客户 反馈内容 处理人 0 A001 张三 产品使用过程中遇到了闪退问题 小明 1 A002 李四 物流配送速度很快服务态度好 小红 2 A003 王五 申请退款希望尽快处理 小刚 3 A004 赵六 客服电话一直打不通投诉 小丽 4 A005 孙七 收到货后发现有破损差评 小华接下来所有的筛选需求都会基于这张表展开。后面第 4 章还会扩展一张更接近真实业务的三列表格用来演示全表检索。3. 核心方法拆解3.1 str.contains 方法字符串筛选的基础pandas 的 Series 对象有一个str.contains方法用于判断字符串中是否包含指定内容。它返回一个布尔型 Series可以当作筛选条件的索引。基本语法df[反馈内容].str.contains(退款)返回结果0 False 1 False 2 True 3 False 4 False Name: 反馈内容, dtype: bool这个布尔 Series 可以直接传给 DataFrame 的索引位置result df[df[反馈内容].str.contains(退款)] print(result)只保留“反馈内容”中包含“退款”的行。str.contains有几个关键参数日常使用频率很高参数作用默认值pat匹配模式可以传普通字符串也可以传正则表达式无case是否区分大小写Truena遇到 NaN 空值时返回什么值False 会报错建议显式指定regex是否把 pat 当作正则表达式解析Trueflags正则表达式标志位如 re.IGNORECASE0这里特别提醒na参数非常重要。当某列存在空值时如果不指定naFalse筛选会直接把空值行排除掉并抛出一个ValueError导致程序中断。后面常见问题部分会演示这个报错。3.2 正则表达式中的竖线多关键词的天然表达str.contains的pat参数支持正则表达式。正则表达式中竖线|表示“或”关系。因此要判断一列是否包含多个关键词中的任意一个只需要把关键词用竖线拼接成一个字符串。例如df[反馈内容].str.contains(退款|投诉|差评, naFalse)返回结果0 False 1 False 2 True 3 True 4 True Name: 反馈内容, dtype: bool这一行代码就完成了三个关键词的“或”匹配。关键词再多也只是往竖线拼接的字符串里继续追加而已。这是“多关键词同时筛选”最核心的原理用正则表达式的或运算把多个关键词简化成一个匹配模式然后交给 pandas 一次执行。3.3 动态拼接关键词模式实际业务中关键词通常不是写死在代码里的而是来自一个列表。比如从配置文件中读取或者从另一张表中读取。这时候需要把列表动态拼接成正则模式。keywords [退款, 投诉, 差评] pattern |.join(keywords) print(pattern)输出退款|投诉|差评拼接完成后再传给str.containsresult df[df[反馈内容].str.contains(pattern, naFalse)]这样做的好处是以后增减关键词只需要改keywords列表不用改动筛选逻辑本身。3.4 特殊字符转义关键词里有括号、点号、竖线怎么办这是很多人容易踩坑的地方。比如关键词是“v1.0”这里的点号在正则表达式中表示“匹配任意字符”而不是字面上的小数点。又比如关键词本身包含竖线“|”会和正则表达式的“或”语法冲突。解决方法是使用re.escape对关键词做转义import re keywords [v1.0, C, Windows|Linux] pattern |.join(re.escape(k) for k in keywords) print(pattern)输出v1\.0|C\\|Windows\|Linux转义之后点号、加号、竖线都变成了普通字符匹配时不会再被解析成正则语法。这一点在关键词包含版本号、文件路径、括号、星号时尤其重要。3.5 全表检索如何把布尔判断扩展到整张表上面说的都是对“某一列”做匹配。全表多关键词筛选需要把“每一列是否包含某个关键词”的判断结果组合起来。pandas 中有一个简单做法对每一列调用str.contains再用逻辑或把各列结果合并。如果只有三列可以这样写mask ( df[客户].str.contains(pattern, naFalse) | df[反馈内容].str.contains(pattern, naFalse) | df[处理人].str.contains(pattern, naFalse) )但列数多了以后这种写法非常冗余。更通用的做法是用apply对每一列执行str.contains得到一个由布尔 Series 组成的 DataFrame再沿行方向取“任意一个为 True”mask df.apply( lambda col: col.astype(str).str.contains(pattern, naFalse) ).any(axis1)这里分两步理解df.apply(lambda col: ...)遍历每一列返回一个布尔 Series。any(axis1)沿行方向判断只要这一行的任意一列匹配成功就保留这一行。这是全表检索最简单的实现方式。3.6 使用 reduce 合并多列条件除了any(axis1)还有一种写法使用 Python 内置的functools.reduce把多个列的布尔条件逐层合并from functools import reduce mask_list [ df[col].astype(str).str.contains(pattern, naFalse) for col in df.columns ] mask reduce(lambda a, b: a | b, mask_list)这种写法适合后续还想对条件做二次加工的场景比如有的列用“或”关系、有的列用“与”关系。不过对大多数全表检索需求来说applyany(axis1)已经足够清晰。4. 完整实战案例4.1 创建项目结构先建立一个工作目录例如multi_keyword_filter/目录下放一个 Python 脚本文件filter_demo.py。后续所有代码都可以写在这个脚本里也可以直接在 Jupyter Notebook 中逐段运行。multi_keyword_filter/ └── filter_demo.py4.2 案例一单列多关键词筛选先实现最简单、最常用的需求只针对“反馈内容”一列筛选出包含“退款”“投诉”“差评”任意一个关键词的行。完整代码如下import pandas as pd import re data { 单号: [A001, A002, A003, A004, A005], 客户: [张三, 李四, 王五, 赵六, 孙七], 反馈内容: [ 产品使用过程中遇到了闪退问题, 物流配送速度很快服务态度好, 申请退款希望尽快处理, 客服电话一直打不通投诉, 收到货后发现有破损差评 ], 处理人: [小明, 小红, 小刚, 小丽, 小华] } df pd.DataFrame(data) keywords [退款, 投诉, 差评] pattern |.join(re.escape(k) for k in keywords) result df[df[反馈内容].str.contains(pattern, naFalse)] print(result)运行结果单号 客户 反馈内容 处理人 2 A003 王五 申请退款希望尽快处理 小刚 3 A004 赵六 客服电话一直打不通投诉 小丽 4 A005 孙七 收到货后发现有破损差评 小华这里有两个细节需要说明第一re.escape(k)对关键词做转义。虽然“退款”“投诉”“差评”本身没有特殊字符但养成转义的习惯可以避免以后关键词变化时踩正则的坑。第二naFalse处理空值。如果“反馈内容”列存在 NaN不写这个参数程序会直接报错。4.3 案例二全表多关键词筛选现在把需求升级不限定任何一列全表只要出现“退款”“投诉”“差评”“打不通”任意一个关键词就保留该行。假设有一张新的业务表包含“客户姓名”“联系备注”“对话内容”“分配客服”四列import pandas as pd import re business_data { 客户姓名: [张三, 李四, 王五, 赵六, 孙七, 周八], 联系备注: [老客户, 新客户, VIP, 投诉重点客户, 普通客户, 老客户], 对话内容: [ 询问产品价格, 反馈物流慢, 要求更换配送地址, 电话投诉客服态度, 咨询优惠活动, 申请退款 ], 分配客服: [小明, 小红, 小明, 小丽, 小红, 小华] } df pd.DataFrame(business_data) print(df)原始数据客户姓名 联系备注 对话内容 分配客服 0 张三 老客户 询问产品价格 小明 1 李四 新客户 反馈物流慢 小红 2 王五 VIP 要求更换配送地址 小明 3 赵六 投诉重点客户 电话投诉客服态度 小丽 4 孙七 普通客户 咨询优惠活动 小红 5 周八 老客户 申请退款 小华现在用全表检索方式过滤keywords [退款, 投诉, 打不通, 更换] pattern |.join(re.escape(k) for k in keywords) mask df.apply( lambda col: col.astype(str).str.contains(pattern, naFalse) ).any(axis1) result df[mask] print(result)运行结果客户姓名 联系备注 对话内容 分配客服 2 王五 VIP 要求更换配送地址 小明 3 赵六 投诉重点客户 电话投诉客服态度 小丽 5 周八 老客户 申请退款 小华逐行分析一下这些命中的逻辑第 2 行“王五”关键词“更换”命中了“要求更换配送地址”。第 3 行“赵六”关键词“投诉”在“投诉重点客户”和“电话投诉客服态度”两个单元格中都有出现属于多重命中。第 5 行“周八”关键词“退款”命中了“申请退款”。“打不通”在全表中没有出现因此不影响结果。这里的col.astype(str)非常关键。它把每一列都先转换成字符串目的是防止某些列本身就是数字类型。如果不做转换数字列调用str.contains虽然不会报错但行为可能和预期不一致。统一转换后所有列都以字符串方式参与匹配逻辑一致。4.4 案例三关键词来源于列表文件真实项目中关键词往往需要经常调整。把关键词维护在 Python 列表里可读性好但需要改代码。更规范的方案是把关键词放在独立的配置文件中程序启动时读入。这里给出一个把关键词放在文本文件里的示例。先创建keywords.txt每行一个关键词退款 投诉 差评 打不通读取并拼接模式的代码import pandas as pd import re def load_keywords(path): with open(path, r, encodingutf-8) as f: keywords [line.strip() for line in f if line.strip()] return keywords keywords load_keywords(keywords.txt) pattern |.join(re.escape(k) for k in keywords) print(关键词列表, keywords) print(正则模式, pattern)输出关键词列表 [退款, 投诉, 差评, 打不通] 正则模式 退款|投诉|差评|打不通后续筛选逻辑不变只是关键词不再硬编码在脚本中维护人员只需要编辑keywords.txt即可。这种方式的工程价值在于筛选条件和代码逻辑完全解耦。业务人员改关键词不需要碰代码也不会因为改错缩进或引号导致程序崩溃。4.5 案例四所有关键词必须同时出现前面讲的需求都是“或”关系任意一个关键词命中即保留。还有一些场景要求“与”关系多个关键词必须同时出现在同一行中。比如筛选“对话内容”中同时包含“退款”和“希望”的行。实现方式是把多个条件用连接condition_1 df[对话内容].str.contains(退款, naFalse) condition_2 df[对话内容].str.contains(希望, naFalse) result df[condition_1 condition_2] print(result)如果关键词数量不固定可以用循环动态拼接from functools import reduce keywords_and [退款, 希望] conditions [ df[对话内容].str.contains(k, naFalse) for k in keywords_and ] mask reduce(lambda a, b: a b, conditions) result df[mask] print(result)运行结果为空是正常的因为原始数据里没有同时包含“退款”和“希望”的记录。你可以往数据里加一行测试数据再验证。这里强调一点是位运算符用于 pandas 布尔 Series 的逻辑与Python 里的and不能直接作用于 pandas Series这是新手经常犯的错误。4.6 案例五全表所有列同时满足多个关键词“全表筛选”和“与关系”可以结合要求整行的任意列中既出现关键词 A又出现关键词 B。比如筛选出“联系备注”或“对话内容”或“分配客服”中同时含“投诉”和“客户”的行。实现方式import pandas as pd import re df pd.DataFrame(business_data) keyword_a 投诉 keyword_b 客户 mask_a df.apply( lambda col: col.astype(str).str.contains(keyword_a, naFalse) ).any(axis1) mask_b df.apply( lambda col: col.astype(str).str.contains(keyword_b, naFalse) ).any(axis1) result df[mask_a mask_b] print(result)运行结果客户姓名 联系备注 对话内容 分配客服 3 赵六 投诉重点客户 电话投诉客服态度 小丽这一行中关键词 A“投诉”出现在“联系备注”和“对话内容”中关键词 B“客户”出现在“联系备注”和“客户姓名”中两个条件均满足所以被保留。这种写法的意义在于它把“全表范围”和“多关键词组合”两个维度解耦开来。每个关键词先在全表范围内生成一个布尔掩码最后用组合所有关键词掩码。无论网页端也好后端也好只需要维护一个关键词掩码列表逻辑可以复用。4.7 封装成通用函数上面的案例已经覆盖了主要场景。为了以后使用方便可以把最常用的“全表多关键词筛选”封装成一个函数。import pandas as pd import re from typing import List, Optional def filter_rows_by_keywords( df: pd.DataFrame, keywords: List[str], match: str any, columns: Optional[List[str]] None ) - pd.DataFrame: 全表或指定列多关键词筛选。 参数说明 df: 输入 DataFrame keywords: 关键词列表 match: any 表示任意关键词命中即保留all 表示所有关键词都必须命中 columns: 指定参与筛选的列默认 None 表示全表检索 if columns is None: columns df.columns.tolist() df_subset df[columns] pattern_list [re.escape(k) for k in keywords] masks [] for pat in pattern_list: col_mask df_subset.apply( lambda col: col.astype(str).str.contains(pat, naFalse) ).any(axis1) masks.append(col_mask) if match any: combined_mask masks[0] for m in masks[1:]: combined_mask combined_mask | m else: combined_mask masks[0] for m in masks[1:]: combined_mask combined_mask m return df[combined_mask]使用示例keywords [退款, 投诉, 差评] result filter_rows_by_keywords(df, keywords, matchany) print(result)这个函数可以应对三种常见需求matchany且columnsNone全表任意关键词命中。matchall且columnsNone全表所有关键词都命中。指定columns只在某些列中检索。函数参数化之后后续业务只需要调用函数传入不同的关键词和匹配模式即可。4.8 运行与验证把 4.7 的函数和 4.3 的数据放进同一个脚本中运行预期输出如下客户姓名 联系备注 对话内容 分配客服 2 王五 VIP 要求更换配送地址 小明 3 赵六 投诉重点客户 电话投诉客服态度 小丽 5 周八 老客户 申请退款 小华如果输出与预期不一致优先检查关键词是否拼写正确、文本文件编码是否为 UTF-8、以及naFalse是否添加。5. 常见问题与排查思路以下问题都是实际使用中高频出现的整理成表格方便检索问题现象常见原因解决思路re.error: missing ), unterminated subpattern关键词包含括号、星号等正则特殊字符未转义对关键词使用re.escape再拼接筛选结果为空关键词大小写不匹配设置caseFalse或使用flagsre.IGNORECASE报错ValueError: cannot mask with array containing NA / NaN values某一列存在空值na未设置在str.contains中指定naFalse关键词本身包含竖线|筛选结果异常竖线被识别为正则“或”语法使用re.escape转义关键词数字列筛选不到内容数字列未转为字符串使用astype(str)后再匹配全表筛选时只返回部分列匹配的结果any(axis1)用法错误取了axis0确认any的 axis 参数沿行方向应为axis1数据量大时筛选很慢正则模式复杂或逐行循环处理优先使用向量化str.contains避免 Python 原生 for 循环代码中使用and连接两个 boolean Series 报错pandas 的布尔 Series 不支持and/or使用和|并注意加括号下面单独展开两个高频问题。5.1 str.contains 报错如何处理错误场景df pd.DataFrame({备注: [正常, None, 异常]}) df[df[备注].str.contains(异常)]运行后会直接抛错因为 None 值导致了 NaN。解决方案是加naFalsedf[df[备注].str.contains(异常, naFalse)]建议在写筛选逻辑时无条件加上naFalse这是一个成本极低但收益很高的习惯。5.2 正则特殊字符导致匹配失败假设关键词里有C不转义时会被解析为“前面的字符重复一次或多次”效果完全不符合预期。正确做法import re keywords [C] pattern |.join(re.escape(k) for k in keywords) print(pattern)输出C\\加上转义之后str.contains(pattern)才能正确匹配字面意义上的C。6. 最佳实践与工程建议6.1 关键词统一做转义无论是单列筛选还是全表筛选建议所有关键词在拼接正则模式之前统一经过re.escape处理。这样可以避免因关键词变化引入的正则语法冲突尤其是当关键词来自业务人员维护的配置文件时转义能显著减少线上问题。import re keywords [v1.0, C, 退款(部分)] pattern |.join(re.escape(k) for k in keywords)6.2 关键词独立维护不要把关键词硬编码在核心筛选逻辑中。推荐放在独立的.txt或.yaml、.json配置文件中程序启动时加载。以 JSON 配置为例{ positive_keywords: [退款, 投诉, 差评], negative_keywords: [测试, 模拟] }后续可以扩展出“正向关键词”“负向关键词”组合过滤的逻辑比如保留包含正向关键词、但不包含负向关键词的行。这种设计在运营数据清洗任务中非常实用。6.3 大数据量下避免 for 循环很多人第一次看到“多关键词”会本能地写 for 循环一个词一个词地筛选再合并结果。在小数据集上没问题但数据量大时效率很低。pandas 的向量化str.contains底层是 C 实现的性能远优于逐行 Python 循环。如果数据量达到百万行级别还可以先用str.contains做一次粗筛再用更精确的逻辑处理尽量减少正则引擎的处理范围。6.4 使用日志记录筛选条件在一个长期运行的脚本或服务中建议把筛选使用的关键词列表、匹配模式、命中行数记录下来。这样即使筛选结果异常也可以快速定位是关键词写错、数据源变化还是逻辑调整导致的问题。import logging logging.basicConfig(levellogging.INFO) logging.info(筛选关键词%s, keywords) logging.info(匹配模式%s, pattern) logging.info(命中行数%d, len(result))6.5 结果导出时注意编码筛选结果如果需要保存为 CSV 并用 Excel 打开推荐使用utf-8-sig编码避免中文乱码result.to_csv(filter_result.csv, indexFalse, encodingutf-8-sig)6.6 区分业务语义包含还是模糊匹配str.contains默认做的是“子串包含”判断。如果业务要求更复杂的匹配比如忽略标点符号、拼音匹配、同义词匹配、分词匹配那str.contains就不够用了需要考虑引入分词工具或正则的加强匹配。一句话建议简单需求用str.contains快速解决复杂需求要先明确匹配口径再选择技术方案不要从一开始就陷入复杂的正则表达式。7. 总结与下一步学习方向本文围绕“全表同时筛选多个关键词”这一需求把 pandas 的str.contains、正则表达式的|或运算、re.escape特殊字符转义、apply全表遍历、布尔掩码组合等技术点完整串通了一遍。从实际用途来看读完这篇文章后你应该至少掌握以下能力针对单列做多关键词“或”筛选。针对全表做多关键词“或”筛选。针对全表做多关键词“与”筛选。把关键词从代码中抽离出来放到配置文件维护。封装自己的多关键词筛选函数方便项目复用。遇到空值、大小写、特殊字符导致的筛选异常时能够独立排查并解决。如果之后实际项目中还需要更复杂的文本匹配能力可以继续了解这几个方向df.query()的字符串表达式用法适合写简单查询但不如布尔掩码灵活。pandas 正则的flags参数比如re.IGNORECASE做忽略大小写匹配。中文分词工具例如 jieba配合关键词表做更细粒度的文本匹配。pyqt 或 streamlit 等工具把筛选能力做成一个小型可视化工具非技术同事也能使用。无论以后表格规模多大、关键词数量多少解决问题的核心思路是不变的把多个关键词构造成一个正则模式用 pandas 的向量化方法一次性匹配全表数据。掌握了这个思路下次再遇到同类需求你就可以直接把方案拿出来用不用再手动一列一列筛选了。
