ARTICLE · 1005664
我让Python帮我把200个Excel合并了,只用了3行代码
【嗨翻Python】零基础入门系列第12篇基础语法最后一篇。
学会文件操作,Python就从"玩具"变成了"工具"。
· · ·
01
PART
一个让我少加了3天班的故事
月底,领导发来200个部门的Excel报表,让我"汇总成一张表,周一交"。
用Excel手动复制粘贴?保守估计3天。
我写了3行Python:
python
import pandas as pd
import glob
files = glob.glob("reports/*.xlsx")
df = pd.concat([pd.read_excel(f) for f in files])
df.to_excel("汇总.xlsx", index=False)
10分钟后,搞定了。
这就是文件操作的价值。Python不是用来考试的,是用来帮你干活的。
· · ·
02
PART
读写文本文件——最基础的操作
写文件
python
# 写入文件
# with 语句 = 自动关闭文件,不用手动 f.close()
# encoding="utf-8" = 中文不会乱码(这两行缺一不可)
with open("test.txt", "w", encoding="utf-8") as f:
f.write("Hello, Python!\n")
f.write("这是第二行\n")
f.write("这是第三行\n")
print("文件写入成功!")
`with` + `encoding="utf-8"`,这两行写进DNA里,永远不会出错。
读文件——3种读法
python
# 读法1:一次读完(小文件用这个)
with open("test.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
# 读法2:逐行读取(大文件用这个,不撑爆内存)
with open("test.txt", "r", encoding="utf-8") as f:
for line in f:
print(line.strip()) # strip()去掉换行符
# 读法3:读成列表(需要对行做操作时用)
with open("test.txt", "r", encoding="utf-8") as f:
lines = f.readlines()
print(lines) # ['Hello, Python!\n', '这是第二行\n', ...]
print(f"共{len(lines)}行")
踩坑记录: 新手最常犯的错——不写 encoding="utf-8"。Windows默认用GBK编码,写中文进去读出来就是乱码。文件操作永远、一定、必须指定 encoding。
追加内容
python
# "a"模式 = 追加(不覆盖原有内容)
with open("test.txt", "a", encoding="utf-8") as f:
f.write("这是追加的内容\n")
文件模式速查表
| 模式 | 说明 | 文件不存在时 | 实际场景 |
|---|---|---|---|
| "r" | 读取(默认) | 报错 | 读配置文件 |
| "w" | 写入(覆盖!) | 创建 | 生成报告 |
| "a" | 追加 | 创建 | 写日志 |
| "r+" | 读写 | 报错 | 更新文件内容 |
| "x" | 创建(已存在则报错) | 创建 | 防止覆盖重要文件 |
踩坑记录:"w" 模式会清空文件!如果你 open("important.txt", "w") 然后忘了写内容,文件就空了。想追加用 `"a"`,想覆盖用 `"w"`,别搞混。
· · ·
03
PART
读写CSV——数据分析的起点
CSV(逗号分隔值)是最通用的表格数据格式。Excel可以打开,数据库可以导入,几乎所有系统都支持。
写CSV
python
import csv
# 数据
students = [
["姓名", "年龄", "分数"], # 表头
["张三", 20, 88],
["李四", 21, 55],
["王五", 22, 92],
]
# 写入CSV
# 注意:encoding="utf-8-sig" 让Excel打开中文不乱码
# newline="" 防止出现空行(Windows专属坑)
with open("students.csv", "w", encoding="utf-8-sig", newline="") as f:
writer = csv.writer(f)
writer.writerows(students) # 一次写入多行
print("CSV写入成功!")
CSV写Excel能打开的,记住两个参数:`encoding="utf-8-sig"` 和 `newline=""`。少一个都会踩坑。
读CSV
python
import csv
with open("students.csv", "r", encoding="utf-8-sig") as f:
reader = csv.reader(f)
header = next(reader) # 先读表头
print(f"表头:{header}")
for row in reader:
print(f"{row[0]},{row[1]}岁,{row[2]}分")
字典方式读写(更优雅)
python
import csv
# 用字典写CSV——不用记字段顺序
students = [
{"name": "张三", "age": 20, "score": 88},
{"name": "李四", "age": 21, "score": 55},
{"name": "王五", "age": 22, "score": 92},
]
with open("students_dict.csv", "w", encoding="utf-8-sig", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["name", "age", "score"])
writer.writeheader() # 写表头
writer.writerows(students)
# 用字典读CSV——直接用列名访问
with open("students_dict.csv", "r", encoding="utf-8-sig") as f:
reader = csv.DictReader(f)
for row in reader:
# 注意:读出来的都是字符串,需要转类型
name = row["name"]
score = int(row["score"])
status = "及格" if score >= 60 else "不及格"
print(f"{name}: {score}分 ({status})")
踩坑记录:csv.reader 读出来的数字也是字符串!row["score"] 拿到的是 "88" 不是 88。需要 int(row["score"]) 转换。
· · ·
04
PART
pathlib——现代路径处理(告别os.path)
pathlib 是Python 3推荐的路径处理方式。用 / 拼接路径,比 os.path.join 优雅10倍。
python
from pathlib import Path
# 当前目录
cwd = Path.cwd()
print(cwd)
# 拼接路径(用 / 运算符,超直觉)
data_dir = Path("data") / "2026" / "reports"
print(data_dir) # data\2026\reports(Windows自动用反斜杠)
# 创建目录(parents=True 自动创建父目录,exist_ok=True 存在也不报错)
data_dir.mkdir(parents=True, exist_ok=True)
# 列出目录下所有.py文件
for file in Path(".").glob("*.py"):
print(file)
# 递归查找所有.txt文件
for file in Path(".").rglob("*.txt"):
print(file)
# 获取文件名、扩展名(超级实用)
p = Path("report_2026.pdf")
print(p.name) # report_2026.pdf(完整文件名)
print(p.stem) # report_2026(不含扩展名)
print(p.suffix) # .pdf(扩展名)
print(p.parent) # .(父目录)
路径拼接用 `Path("a") / "b" / "c"`,比字符串拼接安全100倍。跨平台、不乱码、不踩坑。
文件判断和操作
python
from pathlib import Path
import shutil
path = Path("report.txt")
# 判断
print(path.exists()) # 存在吗?
print(path.is_file()) # 是文件吗?
print(path.is_dir()) # 是目录吗?
# 文件信息
if path.exists():
info = path.stat()
print(f"大小:{info.st_size} 字节")
print(f"修改时间:{info.st_mtime}")
# 复制文件
shutil.copy("report.txt", "report_backup.txt")
# 移动文件
shutil.move("report_backup.txt", "backup/report.txt")
# 删除文件(小心使用!)
# path.unlink()
# 删除整个目录(更小心!)
# shutil.rmtree("backup")
· · ·
05
PART
实战案例——直接拿去用
案例1:批量重命名文件(告别手动改名)
python
from pathlib import Path
def batch_rename(folder, old_str, new_str, dry_run=True):
"""
批量替换文件名中的字符串
Args:
folder: 目录路径
old_str: 要被替换的字符串
new_str: 替换成什么
dry_run: True=只预览不执行(安全!),False=真正执行
真实场景:同事把200个文件命名为 "IMG_001.jpg"...,
领导要求改成 "团建照片_001.jpg"
"""
folder = Path(folder)
count = 0
for file in folder.iterdir():
if old_str in file.name:
new_name = file.name.replace(old_str, new_str)
if dry_run:
# 预览模式——先看效果,别急着改
print(f" {file.name} → {new_name}")
else:
new_path = file.parent / new_name
file.rename(new_path)
print(f" 已改名:{file.name} → {new_name}")
count += 1
if dry_run:
print(f"\n预览完成,共{count}个文件需要改名。")
print("确认无误后,运行 dry_run=False 执行改名。")
else:
print(f"\n完成!共重命名{count}个文件。")
# 使用步骤:先预览,再执行
# 第1步:看看哪些文件会被改
batch_rename("./photos", "IMG", "团建照片", dry_run=True)
# 第2步:确认没问题,真正执行
# batch_rename("./photos", "IMG", "团建照片", dry_run=False)
踩坑记录: 批量操作文件之前一定要先 dry_run 预览。我曾经一次把50个文件名改错了,没有备份,花了2小时手动改回来。
案例2:数据统计并导出CSV报告
python
import csv
from pathlib import Path
# 模拟数据(实际中可能是从数据库或API拿到的)
scores = {
"张三": [88, 92, 85, 90, 87],
"李四": [55, 60, 58, 52, 61],
"王五": [92, 95, 90, 93, 96],
"赵六": [70, 68, 72, 65, 74],
"孙七": [45, 38, 50, 42, 40],
}
def get_grade(avg):
"""根据平均分判断等级"""
if avg >= 90: return "优秀"
if avg >= 80: return "良好"
if avg >= 70: return "中等"
if avg >= 60: return "及格"
return "不及格"
# 生成报告
output = Path("score_report.csv")
with open(output, "w", encoding="utf-8-sig", newline="") as f:
writer = csv.writer(f)
writer.writerow(["姓名", "最高分", "最低分", "平均分", "等级", "波动幅度"])
for name, scores_list in scores.items():
max_s = max(scores_list)
min_s = min(scores_list)
avg_s = sum(scores_list) / len(scores_list)
grade = get_grade(avg_s)
swing = max_s - min_s # 成绩波动幅度
writer.writerow([name, max_s, min_s, f"{avg_s:.1f}", grade, swing])
print(f"报告已生成:{output.absolute()}")
# 输出效果:
# 姓名,最高分,最低分,平均分,等级,波动幅度
# 张三,92,85,88.4,良好,7
# 李四,61,52,57.2,不及格,9
# ...
案例3:简单的日志记录器(每个项目都该有)
python
from datetime import datetime
from pathlib import Path
class SimpleLogger:
"""
简易日志记录器
为什么不用 print?因为 print 完就没了。
日志写到文件里,出了问题可以回溯。
真实场景:线上程序崩了,没有日志 = 盲人摸象。
有了日志,直接看 app.log 就知道哪一步出了问题。
"""
# 日志级别(从低到高)
LEVELS = {
"DEBUG": "调试",
"INFO": "信息",
"WARNING": "警告",
"ERROR": "错误",
"CRITICAL": "致命",
}
def __init__(self, log_file="app.log"):
self.log_file = Path(log_file)
def log(self, level, message):
"""写一条日志"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"[{timestamp}] [{level}] {message}\n"
# 追加模式写入
with open(self.log_file, "a", encoding="utf-8") as f:
f.write(log_entry)
# 同时打印到屏幕
print(log_entry.strip())
def info(self, msg):
self.log("INFO", msg)
def warning(self, msg):
self.log("WARNING", msg)
def error(self, msg):
self.log("ERROR", msg)
# 使用
logger = SimpleLogger("my_app.log")
logger.info("程序启动")
logger.info("加载了100条数据")
logger.warning("磁盘空间不足20%")
logger.error("连接数据库失败:超时")
# 日志文件内容:
# [2026-08-19 14:30:00] [INFO] 程序启动
# [2026-08-19 14:30:01] [INFO] 加载了100条数据
# [2026-08-19 14:30:01] [WARNING] 磁盘空间不足20%
# [2026-08-19 14:30:02] [ERROR] 连接数据库失败:超时
案例4:批量提取多个文本文件的关键信息
python
import csv
from pathlib import Path
from datetime import datetime
def extract_logs(log_dir, keyword="ERROR"):
"""
从多个日志文件中提取包含关键词的行
真实场景:运维同事每天要翻20个日志文件找报错,
这个脚本3秒搞定。
"""
log_dir = Path(log_dir)
results = []
for log_file in log_dir.glob("*.log"):
with open(log_file, "r", encoding="utf-8") as f:
for line_num, line in enumerate(f, 1):
if keyword in line:
results.append({
"文件": log_file.name,
"行号": line_num,
"内容": line.strip(),
"提取时间": datetime.now().strftime("%Y-%m-%d %H:%M"),
})
# 导出结果
if results:
output = Path(f"error_report_{datetime.now():%Y%m%d}.csv")
with open(output, "w", encoding="utf-8-sig", newline="") as f:
writer = csv.DictWriter(f, fieldnames=results[0].keys())
writer.writeheader()
writer.writerows(results)
print(f"找到{len(results)}条'{keyword}'记录,已导出到 {output}")
else:
print(f"没有找到包含'{keyword}'的记录")
return results
# extract_logs("./logs", "ERROR")
· · ·
06
PART
为什么文件操作要这样设计?
很多人觉得文件操作就是 open() 读写数据,没什么特别的。但Python的设计里有几个关键选择,背后都有深意:
1. 为什么必须有 `with` 语句?
因为文件是"系统资源"。你打开一个文件,操作系统就分配了一块内存给它。如果你不关闭,内存就一直占着。打开10个文件不关,没什么感觉。打开1000个文件不关,程序卡死。with 语句保证不管代码有没有出错,文件都会被正确关闭。这不是"好习惯",是"必须"。
2. 为什么 `encoding="utf-8"` 不能省?
因为不同操作系统默认编码不同:Windows用GBK,Mac/Linux用UTF-8。你写了一个中文文件,没指定编码,在Windows上正常,发给Mac同事就乱码。指定 encoding="utf-8" 是"跨平台安全"的保障。
3. 为什么CSV要用 `utf-8-sig` 而不是 `utf-8`?
因为Excel打开UTF-8文件时不认中文。加了BOM头(sig = Signature)的UTF-8文件,Excel一看就知道"这是UTF-8编码",中文就正常了。这是微软的"锅",但我们得适应。
4. 为什么pathlib用 `/` 拼路径?
因为 "data/" + "2026" + "/report.xlsx" 这种字符串拼接,在Windows上用反斜杠,在Mac上用正斜杠,一改环境就报错。Path("data") / "2026" / "report.xlsx" 自动处理分隔符,跨平台零bug。
· · ·
07
PART
效率对比:手动做 vs Python做
| 场景 | 手动做法 | Python做法 | 节省时间 |
|---|---|---|---|
| 200个Excel合并 | 逐个打开、复制粘贴 | pandas+glob,3行代码 | 3天→10分钟 |
| 500个文件批量改名 | 右键重命名,逐个改 | pathlib批量替换,10行代码 | 2小时→10秒 |
| 从日志中找报错 | 翻20个日志文件,肉眼找 | 逐行扫描+关键词匹配 | 1小时→3秒 |
| 生成月度CSV报告 | 手动整理Excel、导出CSV | csv.writer自动写入 | 每月1小时→5秒 |
共同模式:只要是"打开文件→处理数据→保存结果"的重复劳动,Python文件操作都能自动化。 别人加班到10点手动复制粘贴,你5分钟写完脚本回家。
· · ·
08
PART
常见翻车现场——新手踩坑排行榜
| 翻车现场 | 为什么会翻车 | 正确做法 |
|---|---|---|
| 不写 encoding="utf-8" | Windows默认GBK,中文变乱码 | 文件操作永远加 encoding="utf-8" |
| 用 "w" 打开重要文件 | 文件被清空,内容全丢 | 先确认文件名,或用 "a" 追加模式 |
| 不用 with 语句 | 文件忘关闭,内存泄漏 | 永远用 with open(...) as f: |
| CSV用Excel打开乱码 | 没加BOM头,Excel不认UTF-8 | 用 encoding="utf-8-sig" |
| Windows CSV多空行 | 每行数据之间多了空行 | 加 newline="" |
| 读CSV数字当字符串用 | "88" + 1 报TypeError | 读出来用 int() 或 float() 转类型 |
最致命的错误: 用 "w" 模式不小心清空了重要文件。open("report.txt", "w") 这一行执行完,report.txt就空了。没有撤销,没有回收站。操作重要文件前,先备份一份。
· · ·
09
PART
可以直接抄走的工具:多Excel文件合并器
python
"""
多Excel文件合并器 —— 行政/财务/运营必备工具
真实场景:每个月收到各部门的Excel报表,
要合并成一张总表。手动复制粘贴200个文件要3天。
这个工具10分钟搞定(含数据校验)。
使用方法:
把要合并的Excel文件放在 reports/ 文件夹下,
运行这个脚本,自动生成 汇总.xlsx。
"""
import csv
from pathlib import Path
from datetime import datetime
def merge_csv_files(folder, output="merged_report.csv", has_header=True):
"""
合并文件夹下所有CSV文件
参数:
folder: CSV文件所在文件夹
output: 输出文件名
has_header: 是否有表头(第一个文件的表头会被保留)
返回:
(总行数, 文件数, 输出路径)
"""
folder = Path(folder)
if not folder.exists():
print(f"文件夹不存在:{folder}")
return 0, 0, None
csv_files = list(folder.glob("*.csv"))
if not csv_files:
print(f"文件夹中没有CSV文件:{folder}")
return 0, 0, None
total_rows = 0
header_saved = False
output_path = Path(output)
with open(output_path, "w", encoding="utf-8-sig", newline="") as out_f:
for csv_file in csv_files:
with open(csv_file, "r", encoding="utf-8-sig") as in_f:
lines = in_f.readlines()
if not lines:
continue
if has_header and not header_saved:
# 第一个文件:保留表头
out_f.writelines(lines)
total_rows += len(lines) - 1
header_saved = True
else:
# 后续文件:跳过表头
if has_header:
out_f.writelines(lines[1:])
total_rows += len(lines) - 1
else:
out_f.writelines(lines)
total_rows += len(lines)
print(f"合并完成!")
print(f" 文件数:{len(csv_files)}")
print(f" 数据行:{total_rows}")
print(f" 输出到:{output_path.absolute()}")
return total_rows, len(csv_files), output_path
def generate_daily_report(data, output_dir="reports"):
"""
自动生成日报CSV文件(按日期命名)
真实场景:运营每天要出一份数据日报,
格式固定、内容每天不同。这个函数自动
按当天日期命名,保存到指定文件夹。
参数:
data: 列表,每行是一个字典
output_dir: 输出文件夹
示例:
data = [
{"日期": "2026-08-19", "新增用户": 120, "活跃用户": 3500},
{"日期": "2026-08-19", "订单数": 89, "金额": 15600},
]
generate_daily_report(data)
→ 生成 reports/daily_20260819.csv
"""
output_dir = Path(output_dir)
output_dir.mkdir(exist_ok=True)
if not data:
print("没有数据可导出")
return None
today = datetime.now().strftime("%Y%m%d")
output_file = output_dir / f"daily_{today}.csv"
# 自动从第一条数据获取表头
fieldnames = list(data[0].keys())
with open(output_file, "w", encoding="utf-8-sig", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(data)
print(f"日报已生成:{output_file.absolute()}")
print(f" 数据行数:{len(data)}")
print(f" 字段列表:{fieldnames}")
return output_file
# ===== 直接运行看效果 =====
if __name__ == "__main__":
# 示例:生成今日运营日报
today_data = [
{"指标": "新增用户", "数值": 120, "环比": "+5%"},
{"指标": "活跃用户", "数值": 3500, "环比": "+2%"},
{"指标": "订单数", "数值": 89, "环比": "-3%"},
{"指标": "营收(元)", "数值": 15600, "环比": "+8%"},
]
report = generate_daily_report(today_data)
· · ·
10
PART
踩坑记录——文件操作的6个坑
| 坑 | 后果 | 正确做法 |
|---|---|---|
| 不写 encoding="utf-8" | 中文乱码 | 永远加 encoding="utf-8" |
| 用 "w" 打开重要文件 | 文件被清空 | 重要文件先用 "r" 确认,或用备份 |
| 不用 with 语句 | 文件忘了关,内存泄漏 | 永远用 with open(...) as f: |
| CSV中文乱码(Excel打开) | 一堆问号 | 用 encoding="utf-8-sig" |
| Windows CSV出现空行 | 每行数据之间多了空行 | 加 newline="" |
| 读CSV拿到的数字是字符串 | int("88") 能跑,"88" + 1 报错 | 读出来记得转类型 |
· · ·
11
PART
行动清单
创建一个测试文件,用 with open 写入你的名字、年龄、职业,然后读出来打印
创建一份CSV,录入5个同事的信息(姓名、部门、工资),用Excel打开验证中文不乱码
用 pathlib 扫描桌面,列出所有 .pdf 文件的文件名和大小
改造上面的日志记录器,加一个 debug() 方法和 critical() 方法
找一个真实需求:你工作中有没有"手动复制粘贴"的重复劳动?用文件操作自动化它
· · ·
12
PART
基础语法阶段完结!
到这里,Python基础语法全部学完了:
变量和数据类型
字符串
列表和元组
字典和集合
条件判断和循环
函数
模块和包
文件操作
你已经具备了用Python写实用脚本的能力。接下来进入进阶阶段:面向对象、异常处理、装饰器。
· · ·
13
PART
下篇预告
第13篇:面向对象——用类来组织代码
前面的代码都是"一锅炖",代码多了就乱。面向对象教你把代码分成一个个"小盒子",每个盒子管好自己的事。这是从"写脚本"到"写工程"的分水岭。
· · ·