电脑里躺着几十个”报告1.csv、最终版.csv、最终版2.csv”,一个一个点开改要一下午。今天学两个新朋友——
glob负责”找到所有文件”,os负责”动文件”,从此一句话收拾整个文件夹
一、一个文件夹引发的惨案
先说一个真实到让人想哭的场景。
月底报销,财务说:”把发票扫描件都按’日期_金额’格式重命名,不规范的不收。”你打开文件夹一看——IMG_2041.jpg、IMG_2042.jpg、微信图片_20260930.png、新建文件夹、新建文件夹(2)……一百三十七个文件。你咬咬牙开始一个个改名,改到第二十个,把IMG_2051改成了IMG_2050的名字,覆盖了,找不回来了。
再比如:二十个分店各交来一份销售.csv,你得逐个打开、加上”分店”这一列、再合成总表。或者:把整个文件夹的.txt笔记全部转成.md;把照片/下所有超过5MB的图片挑出来……
这类活有个共同点:文件多、操作重复、规则明确。重复且有规则,正是程序最擅长的事。人做这种活的出错率,跟文件数量成正比;程序的出错率,跟文件数量没关系——它改第1个和改第300个,一样稳。
今天就让程序当你的文件收纳师:进门先”盘点”(找出所有目标文件),再”逐件整理”(一个个处理),需要的话还能”贴新标签”(批量改名)。
盘点要用新朋友glob,动文件要用新朋友os。这是两个Python自带的工具箱,不用安装,import就能用。今天就这两个新概念,而且都是”查手册型”的——记住常用那几个就够使。
二、新朋友一号:glob——按”长相”找文件
想处理一批文件,第一步是让程序知道要处理哪些。最笨的办法是把文件名一个个写进列表:
文件列表 = ["报告1.csv", "报告2.csv", "报告3.csv"] # 一百个呢?一千个呢?
显然不行。我们需要的是”把文件夹里所有.csv都给我”这种说法。glob干的就是这个——按通配符模式找文件。通配符你其实天天在用:在文件管理器的搜索框里打*.jpg,就是在用通配符。
*= 任意一串字符(可以是空)?= 任意一个字符
import glob
csv文件 = glob.glob("*.csv") # 当前文件夹下所有.csv文件
print(csv文件)
# ['月报.csv', '销售.csv', '最终版.csv', '最终版2.csv']
一行代码,四个文件全找到了。glob.glob()返回一个列表,每个元素是一个文件路径字符串。返回的是列表意味着什么?意味着你早就会的for循环、len()、in全都能直接用:
import glob
csv文件 = glob.glob("*.csv")
print(f"找到{len(csv文件)}个CSV文件")
for 文件 in csv文件:
print(处理(文件)) # 处理()是你写的函数,逐个喂文件
“先glob盘点,再for循环逐个处理”——这就是批量文件处理的骨架,今天一整天都围着它转。
几个高频模式
import glob
glob.glob("*.csv") # 当前文件夹的.csv(不管名字多长,后缀对上就行)
glob.glob("报告*.csv") # 以"报告"开头的csv → 报告1.csv、报告终版.csv
glob.glob("数据/*.csv") # "数据"文件夹下的csv(注意路径要写上)
glob.glob("**/*.csv", recursive=True) # 连子文件夹一起找(挖地三尺)
最后一个要特别记:表示”这一层以及下面所有层”,但必须搭配recursive=True这个参数,不然就当两个星号的普通字符用,什么都找不到。找”整个文件夹树里的所有图片”时,就靠它。
坑点疫苗先打一针:glob找不到文件时不报错,安安静静返回一个空列表[]。这既是好事(程序不会崩),也是坏事(你可能以为在干活其实啥也没干)。所以正经代码里盘点完先看一眼数量:
文件们 = glob.glob("*.csv")
if not 文件们:
print("没找到任何CSV文件,检查一下路径写对没?")
三、新朋友二号:os——文件系统的”遥控器”
找到文件之后,下一步是”动”它们:改名、挪位置、建文件夹、看文件多大。这些操作在Python里归os模块管——它是操作系统(Operating System)的缩写,是Python伸向文件系统的一只手。
今天只学最常用的五件事,别的用到再查:
import os
os.rename("旧名字.csv", "新名字.csv") # 改名(也能用来挪位置)
os.remove("没用的.csv") # 删除文件,删了就没了,慎用!
os.makedirs("输出/2026年10月", exist_ok=True) # 建文件夹(连中间层一起建)
os.path.getsize("月报.csv") # 文件大小(字节)
os.path.exists("月报.csv") # 这个文件/文件夹存在吗?True/False
再加两个”拆路径”的小工具,批量改名时离不开:
import os
路径 = "数据/月报.csv"
os.path.basename(路径) # '月报.csv' —— 只要文件名部分
os.path.splitext("月报.csv") # ('月报', '.csv') —— 拆成(名字, 后缀)
splitext返回一个两个元素的元组,正好能接住:
名字, 后缀 = os.path.splitext("月报.csv")
print(名字) # 月报
print(后缀) # .csv
注意splitext拆的是”最后一段点”:最终版.2026.csv拆出来是('最终版.2026', '.csv')——名字里的点不算,只有最后一个点是”后缀分界线”。这正是我们要的行为。
glob和os怎么配合
典型流水线:glob找到文件 → os处理文件。比如”把文件夹里所有.txt改名成.md“:
import glob
import os
for 文件 in glob.glob("*.txt"):
名字, _ = os.path.splitext(文件) # _ 表示"这个值我要扔掉"
新名字 = 名字 + ".md"
os.rename(文件, 新名字)
print(f"{文件} → {新名字}")
跑完三行输出:
笔记1.txt → 笔记1.md
笔记2.txt → 笔记2.md
读书心得.txt → 读书心得.md
八行代码,几十个文件一眨眼改完。你手动做这件事的时间,够把今天这篇读三遍。
四、实战:把一堆”报表”收拾利索
来个综合实战。场景:报表/文件夹里躺着一堆乱七八糟的CSV——报表1.csv、最终版.csv、最终版2.csv、老王发的.csv。老板说:”都改名成报表_序号.csv这种规范格式,另外给我一份清单,说明每个文件多大、多少行。”
想三十秒,用你自己的话说给AI听(或者先想清楚自己要什么)。然后看实现:
import glob
import os
import csv
import shutil
os.makedirs("报表_整理后", exist_ok=True) # 新文件放新文件夹,原文件不动
清单 = []
序号 = 1
for 文件 in sorted(glob.glob("报表/*.csv")):
名字, 后缀 = os.path.splitext(os.path.basename(文件))
新名字 = f"报表_{序号:02d}{后缀}" # 报表_01.csv、报表_02.csv……
新路径 = os.path.join("报表_整理后", 新名字)
# 数一下有多少行数据
with open(文件, encoding="utf-8") as f:
行数 = sum(1 for _ in csv.reader(f)) - 1 # 减1是减掉表头
清单.append({
"原名": 名字 + 后缀,
"新名": 新名字,
"大小KB": round(os.path.getsize(文件) / 1024, 1),
"数据行数": max(行数, 0),
})
shutil.copy(文件, 新路径) # 复制一份到新文件夹,原文件原地不动
序号 += 1
注意这里用的是shutil.copy(复制)而不是os.rename(改名挪走)——这是捎带认识的三号朋友shutil,批量文件的”复制/移动”都归它管。为什么先复制不直接改名?因为直接rename会把原文件挪走,万一清单还没存就出错了呢?万一新名字撞车覆盖了呢?整理到新文件夹、原文件先留着,确认没问题再手动删,永远比”一步到位然后祈祷”稳。
最后把清单存成一份”整理报告.csv”:
with open("报表_整理后/整理清单.csv", "w", encoding="utf-8", newline="") as f:
写手 = csv.DictWriter(f, fieldnames=["原名", "新名", "大小KB", "数据行数"])
写手.writeheader()
写手.writerows(清单)
print(f"整理完成,共{len(清单)}个文件,清单已生成")
for 项 in 清单:
print(f" {项['原名']} → {项['新名']}({项['大小KB']}KB,{项['数据行数']}行)")
跑完一看:
整理完成,共4个文件,清单已生成
老王发的.csv → 报表_01.csv(12.3KB,88行)
最终版.csv → 报表_02.csv(45.1KB,512行)
最终版2.csv → 报表_03.csv(45.2KB,513行)
报表1.csv → 报表_09.csv(8.0KB,60行)
(报表1.csv排到最后是因为sorted按字符串比大小,”1″排在”老””最”后面——中英混排的排序问题,练习里让你收拾它。)
回头盘点这段代码干了什么:glob盘点文件 → 逐个拆名字、数行数、记清单 → 复制到新文件夹规范命名 → 清单存档。用到的全是学过的东西(循环、字典、csv写入),新朋友只有glob找文件、os拆路径和看大小、shutil复制。批量处理的本体,其实就是”一个for循环+每件小事写成函数“。
五、四个必踩的坑(先打疫苗)
坑1:改名把文件弄丢。 os.rename("a.csv", "b.csv")——如果b.csv已经存在会怎样?在Windows上可能直接覆盖(旧的b.csv没了),在Linux/Mac上通常也是覆盖。改名前先探路:
if os.path.exists(新名字):
print(f"跳过:{新名字}已存在")
else:
os.rename(旧名字, 新名字)
这也是”复制式整理”比”原地改名”安全的原因:覆盖了还能从原文件再来一份。
坑2:在循环里改名,越改越乱。 比如”把所有文件名里的’最终版’换成’正式版’”,如果你先glob出列表、循环里rename,一般是安全的(列表是开工前的快照)。但如果你边遍历边重新glob,或者改名规则会让两个文件互换名字(a改b、b改a),就会打架。铁律:先glob出完整列表存进变量,再对着这个列表干活——就像清点库存先抄一份清单,别一边盘点一边挪货。
坑3:路径写死,换台电脑就崩。 glob.glob("C:/Users/老王/Desktop/报表/*.csv")这种写法,到了别人电脑上就是空列表。两个改进:路径写成配置(第28篇刚学的config.json),以及用os.path.join拼路径,别手写斜杠:
路径 = os.path.join("报表", "数据", "月报.csv") # 自动用对系统的分隔符
Windows用,Mac/Linux用/,手写的"报表/数据"在Windows上多数时候能凑合,但遇到open()较真的场合就翻车。os.path.join永远拼对,交给它。
坑4:glob的”当前文件夹”不是你想的那个。 glob.glob("*.csv")找的是程序运行时的当前文件夹,不是.py文件所在的文件夹!双击运行、IDE里点运行、命令行里跑,当前文件夹可能各不相同。症状就是”我明明写了*.csv,它说找不到”。解法:要么路径写全,要么用**递归找,要么在代码开头先print(os.getcwd())(显示当前文件夹)自查一眼。
六、跟AI协作的正确姿势(批量处理特供)
批量处理是AI的拿手好戏,但指挥有三个要点:
- 把文件夹的真实样子贴给AI。别说”帮我批量改名”,说”文件夹里有
IMG_2041.jpg、微信图片_20260930.png、截图 2026-10-06 上午10.02.33.png共137个文件,想统一改成20261006_001.jpg这种带日期和序号的格式,序号按文件修改时间排”——贴真实文件名样本,AI才能把微信图片_和截图这些前缀都考虑进去 - 先要”只读预览”,再要”真动手”。这是批量处理最值钱的一句需求:”第一版只打印’旧名→新名’的对照表,不要真的改“。看一遍对照表确认没误伤,再让它加执行代码。文件操作不比打印数字——改错了没有撤销键
- 让它把”每件小事”写成函数。”改名规则写成独立函数,输入原文件名,输出新文件名”——规则变了只改一个函数,这个结构红利你已经吃过不止一次了
高频句式送你:”遍历文件夹里所有X文件,对每个文件先打印处理前后的对照表,确认规则后再执行,执行前检查目标文件是否已存在,已存在就跳过并记录“——把这句丢给AI,出来的就是今天实战代码的骨架。
七、今日小练习
练习1:修好那个排序问题
实战里报表1.csv排到了最后,因为sorted按字符串排。想让报表1.csv、报表2.csv、报表10.csv按数字顺序来,怎么办?(提示:sorted有个key参数,sorted(文件们, key=lambda f: ...)——lambda是个”临时小函数”,让它返回文件名里的数字部分。re.findall(r"d+", 文件名)能抽出所有数字串,取第一个转成int返回。re模块是”正则表达式”工具箱,这里先照抄用着,后面会有它的正式戏份。)
练习2:文件体检报告
写程序扫描一个文件夹(含子文件夹),输出体检报告:一共多少个文件、按后缀分类各多少个(.csv多少、.jpg多少……)、最大的文件是哪个多大、空文件(0字节)有几个。(提示:glob.glob("**/*", recursive=True)能找到所有东西(含文件夹),用os.path.isfile(路径)筛掉文件夹;分类统计就是”字典计数”的老套路:计数[后缀] = 计数.get(后缀, 0) + 1——第26篇数销量时用过的同一招。)
练习3:图片瘦身挑选器
把文件夹里所有大于2MB的图片,改名后统一挪到待压缩/文件夹,文件名前缀加胖_(如胖_微信图片.png),并打印一行”胖图清单”。(提示:os.path.getsize看大小,2 * 1024 * 1024就是2MB(字节换算);”挪动”用shutil.move(原路径, 新路径)——它比rename更能干,跨盘符也能挪。注意os.makedirs("待压缩", exist_ok=True)先建好文件夹,不然挪进去会报”目标文件夹不存在”。)
写在后面:你已经有了”批处理思维”
盘点今天:glob按长相找文件(*通配、递归找)、os动文件(改名、看大小、拆路径、建文件夹)、shutil复制搬运,骨架是”先盘点成列表,再for循环逐个处理**”。
更重要的是今天你拿到了批处理思维:以后再遇到”几十个文件要怎么怎么样”,你的第一反应不再是”开干”,而是”等等,这活有规则吗?有规则就让程序干”。改名有规则、转格式有规则、挑大文件有规则——有规则的重复劳动,一分钟都不该由人来做。
下一篇进入第三阶段的收官区,也是数据分析的正餐:pandas——一个专门对付表格数据的重量级工具箱。第25到29篇我们用csv模块手搓了读写和清洗,pandas会把这一切变成”一行顶十行”。先把Excel和CSV的读取学会了,你会发现:原来表格还能这么玩。
下篇预告
第三十一篇:Excel读取——pandas登场!一个函数读进整张表格,列名直接当变量用,筛选、求和都不用写循环。数据分析的大门,下一篇正式推开。
发表回复