50 个 Excel 文件找一个客户?一个脚本扫全文件夹按关键字汇总命中行

2026-10-06 · 喵窝资源铺

「客户 A 的订单到底记在哪个表里?」——文件名五花八门(9月订单.xlsx、客户台账、导出表…),每个里面还可能多个 sheet,Ctrl+F 开一个查一个,50 个文件查到怀疑人生。这篇给一个 openpyxl 脚本:扫整个文件夹的所有 xlsx,按关键字搜每个 sheet 的表头和全文,命中的行连同来源文件名、工作表名、匹配方式一起汇总到一个结果表。代码实跑验证,真实输出贴在下面。

输出长什么样

构造 5 个文件的测试目录,故意埋了公式表、坏文件、单列表、表头前有说明行的表,搜关键字「喵喵」:

扫描 5 个文件,命中 2 行,结果已写入 汇总.xlsx
跳过: 坏文件.xlsx: 打不开(File is not a zip file)
跳过: 备注表.xlsx/Sheet: 找不到表头

来源文件        工作表  匹配方式  订单号  客户名称        金额
9月订单.xlsx    Sheet  内容包含  A002   北京喵喵科技    200
导出表.xlsx     Sheet  内容包含  B001   喵喵科技上海    (补空列)

注意「导出表」那行——它的表头(编号/公司)和第一个命中的文件(订单号/客户名称/金额)列数不一样,脚本自动补了空列对齐,Excel 里打开不会串列。连跑三次输出文件 md5 完全一致。

用法

python multi_search.py 文件夹路径 关键字 输出结果.xlsx

搜索不区分大小写;表头行自动识别(表头前面有几行说明文字、空行也能跳过);打不开的文件和找不到表头的 sheet 不会中断,会列在结尾的「跳过」清单里。

完整脚本

# -*- coding: utf-8 -*-
"""多文件关键字查找汇总:在一个文件夹的所有 xlsx 里搜关键字,命中行汇总到一个结果表。"""
import os, sys, glob
from openpyxl import load_workbook, Workbook
import datetime as dt

folder, keyword, dst = sys.argv[1], sys.argv[2], sys.argv[3]

def cell_text(v):
    if v is None:
        return ""
    if isinstance(v, dt.datetime):
        return v.strftime("%Y-%m-%d %H:%M:%S")
    return str(v).strip()

# 找出有表头的第一行(有的表上面有几行说明文字)
def find_header(ws):
    for r in range(1, min(ws.max_row, 20) + 1):
        vals = [cell_text(c.value) for c in ws[r]]
        if sum(1 for v in vals if v) >= 1 and any(v for v in vals):
            return r, vals
    return None, []

out = Workbook()
now = dt.datetime(2026, 1, 1)
out.properties.created = now
out.properties.modified = now
s1 = out.active
s1.title = "汇总"

files = sorted(glob.glob(os.path.join(folder, "*.xlsx")))
total_hit = 0
skipped = []
for fp in files:
    fname = os.path.basename(fp)
    try:
        wb = load_workbook(fp, data_only=True)   # data_only: 公式表拿计算值而不是公式串
    except Exception as e:
        skipped.append(f"{fname}: 打不开({e})")
        continue
    for wsname in wb.sheetnames:
        ws = wb[wsname]
        hr, header = find_header(ws)
        if hr is None:
            skipped.append(f"{fname}/{wsname}: 找不到表头")
            continue
        loc = [i for i, h in enumerate(header) if keyword.lower() in h.lower()]
        col_match = len(loc) > 0
        hits = []
        for row in ws.iter_rows(min_row=hr + 1, values_only=True):
            joined = " ".join(cell_text(v) for v in row)
            if keyword.lower() in joined.lower():
                hits.append([cell_text(v) for v in row])
        if hits or col_match:
            if total_hit == 0:
                s1.append(["来源文件", "工作表", "匹配方式"] + (header if header else ["(无表头)"]))
            for h in hits:
                s1.append([fname, wsname, "内容包含"] + h + [""] * (len(header) - len(h)))
            if not hits and col_match:
                s1.append([fname, wsname, "仅表头含关键字"] + [""] * len(header))
            total_hit += len(hits)

out.save(dst)
print(f"扫描 {len(files)} 个文件,命中 {total_hit} 行,结果已写入 {dst}")
for s in skipped:
    print("跳过:", s)

实跑时踩到的 3 个真坑

  1. 不加 data_only=True,公式表搜到的是公式串不是值。测试里那张「备注表」单元格里写的是 =UPPER("miao tech order")——不传 data_only 时读到的字符串就是公式本身,你搜「miao tech」永远搜不到计算结果。加上 load_workbook(fp, data_only=True) 才拿缓存计算值(注意:如果文件从没被 Excel 打开计算过,缓存值可能是空,这是 openpyxl 的已知边界)。
  2. 单列表会被「≥2 个非空才算表头」的判定漏掉。第一版表头判定要求一行至少 2 个非空单元格,结果「备注」这种只有一列的表直接报「找不到表头」。改成 ≥1 即可。反过来,如果目标文件夹里有很多「首行只是标题文字、真表头在下面」的表,这个判定又会把标题行误认成表头——按你的真实文件情况调这个数,没有万能值。
  3. 跨文件列数不齐,命中行会串列。第一个命中文件 3 列、第二个 2 列时,openpyxl 直接 append 会把短行原样写进去,结果表里第二行的「金额」列其实是空的、第三列错位到别处——肉眼看是「对齐的假象」,排序或筛选就露馅。命中行后面补 [""] * (len(header) - len(h)) 强制对齐。

相关文章

🎈 没时间自己跑脚本?喵窝资源铺提供代跑服务:把文件发给我们,AI 执行 + 人工核验后把结果文件发回。查看全部工具与服务 →(虚拟物品,交付后不退款,下单前请确认需求)

← 返回博客合集